From 79b48ed5a42a6244ce3570c6548daf6d5500ac27 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 31 May 2016 16:36:56 +0200 Subject: [PATCH 0001/1201] Replace char::encode_utf8 by encode_unicode crate. --- Cargo.toml | 1 + src/lib.rs | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9f419772ff..2298d2c115 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ license = "MIT" libc = "0.2.7" nix = "0.5.0" unicode-width = "0.1.3" +encode_unicode = "0.1.3" clippy = {version = "~0.0.58", optional = true} [dev-dependencies] diff --git a/src/lib.rs b/src/lib.rs index 05e96bce6d..3bdaf9d918 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,13 +16,13 @@ //! ``` #![feature(io)] #![feature(iter_arith)] -#![feature(unicode)] #![cfg_attr(feature="clippy", feature(plugin))] #![cfg_attr(feature="clippy", plugin(clippy))] extern crate libc; extern crate nix; extern crate unicode_width; +extern crate encode_unicode; pub mod completion; #[allow(non_camel_case_types)] @@ -42,6 +42,7 @@ use std::sync::atomic; use nix::errno::Errno; use nix::sys::signal; use nix::sys::termios; +use encode_unicode::CharExt; use completion::Completer; use consts::{KeyPress, char_to_key_press}; @@ -327,9 +328,7 @@ fn edit_insert(s: &mut State, ch: char) -> Result<()> { if push { if s.cursor.col + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) < s.cols { // Avoid a full update of the line in the trivial case. - let bits = ch.encode_utf8(); - let bits = bits.as_slice(); - write_and_flush(s.out, bits) + write_and_flush(s.out, ch.to_utf8().as_bytes()) } else { s.refresh_line() } From e5a45c3fc8fe1bb81d4e4514cec1d2175819044f Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 31 May 2016 16:39:36 +0200 Subject: [PATCH 0002/1201] Replace Iterator::sum by a fold. --- src/lib.rs | 1 - src/line_buffer.rs | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3bdaf9d918..14b8de749a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,6 @@ //! } //! ``` #![feature(io)] -#![feature(iter_arith)] #![cfg_attr(feature="clippy", feature(plugin))] #![cfg_attr(feature="clippy", plugin(clippy))] diff --git a/src/line_buffer.rs b/src/line_buffer.rs index e1c4602ae9..479c686564 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,5 +1,5 @@ //! Line buffer with current cursor position -use std::ops::Deref; +use std::ops::{Add, Deref}; /// Maximum buffer size for the line read pub static MAX_LINE: usize = 4096; @@ -259,7 +259,7 @@ impl LineBuffer { .rev() .take_while(|ch| test(*ch)) .map(char::len_utf8) - .sum(); + .fold(0, Add::add); if pos > 0 { // eat any non-spaces on the left pos -= self.buf[..pos] @@ -267,7 +267,7 @@ impl LineBuffer { .rev() .take_while(|ch| !test(*ch)) .map(char::len_utf8) - .sum(); + .fold(0, Add::add); } Some(pos) } @@ -305,7 +305,7 @@ impl LineBuffer { .chars() .take_while(|ch| !ch.is_alphanumeric()) .map(char::len_utf8) - .sum(); + .fold(0, Add::add); let start = pos; if pos < self.buf.len() { // eat any non-spaces @@ -313,7 +313,7 @@ impl LineBuffer { .chars() .take_while(|ch| ch.is_alphanumeric()) .map(char::len_utf8) - .sum(); + .fold(0, Add::add); } Some((start, pos)) } else { From 4776bfc291c310e75ba04d39bf476013c5a29523 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 31 May 2016 16:49:20 +0200 Subject: [PATCH 0003/1201] Replace Read::chars() by a private copy. --- src/char_iter.rs | 106 +++++++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 8 ++-- src/lib.rs | 16 ++++--- 3 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 src/char_iter.rs diff --git a/src/char_iter.rs b/src/char_iter.rs new file mode 100644 index 0000000000..7d0c46de3b --- /dev/null +++ b/src/char_iter.rs @@ -0,0 +1,106 @@ +//! An iterator over the `char`s of a reader. +//! +//! A copy of the unstable code from the stdlib's std::io::Read::chars. + +use std::error; +use std::fmt; +use std::io; +use std::io::Read; +use std::str; + +pub fn chars(read: R) -> Chars where R: Sized { + Chars { inner: read } +} + +// https://tools.ietf.org/html/rfc3629 +static UTF8_CHAR_WIDTH: [u8; 256] = [ + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x1F + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x3F + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x5F + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x7F + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x9F + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xBF + 0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xDF + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // 0xEF + 4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0, // 0xFF +]; + +/// Given a first byte, determine how many bytes are in this UTF-8 character +#[inline] +fn utf8_char_width(b: u8) -> usize { + return UTF8_CHAR_WIDTH[b as usize] as usize; +} + +pub struct Chars { + inner: R, +} + +#[derive(Debug)] +pub enum CharsError { + NotUtf8, + Other(io::Error), +} + +impl Iterator for Chars { + type Item = Result; + + fn next(&mut self) -> Option> { + let mut buf = [0]; + let first_byte = match self.inner.read(&mut buf) { + Ok(0) => return None, + Ok(..) => buf[0], + Err(e) => return Some(Err(CharsError::Other(e))), + }; + let width = utf8_char_width(first_byte); + if width == 1 { return Some(Ok(first_byte as char)) } + if width == 0 { return Some(Err(CharsError::NotUtf8)) } + let mut buf = [first_byte, 0, 0, 0]; + { + let mut start = 1; + while start < width { + match self.inner.read(&mut buf[start..width]) { + Ok(0) => return Some(Err(CharsError::NotUtf8)), + Ok(n) => start += n, + Err(e) => return Some(Err(CharsError::Other(e))), + } + } + } + Some(match str::from_utf8(&buf[..width]).ok() { + Some(s) => Ok(s.chars().next().unwrap()), + None => Err(CharsError::NotUtf8), + }) + } +} + +impl error::Error for CharsError { + fn description(&self) -> &str { + match *self { + CharsError::NotUtf8 => "invalid utf8 encoding", + CharsError::Other(ref e) => error::Error::description(e), + } + } + fn cause(&self) -> Option<&error::Error> { + match *self { + CharsError::NotUtf8 => None, + CharsError::Other(ref e) => e.cause(), + } + } +} + +impl fmt::Display for CharsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + CharsError::NotUtf8 => { + "byte stream did not contain valid utf8".fmt(f) + } + CharsError::Other(ref e) => e.fmt(f), + } + } +} diff --git a/src/error.rs b/src/error.rs index d4654a09a2..844805b52d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,6 +4,8 @@ use std::error; use std::fmt; use nix; +use char_iter; + /// The error type for Rustyline errors that can arise from /// I/O related errors or Errno when using the nix-rust library #[derive(Debug)] @@ -13,7 +15,7 @@ pub enum ReadlineError { /// Error from syscall Errno(nix::Error), /// Chars Error - Char(io::CharsError), + Char(char_iter::CharsError), /// EOF (Ctrl-d) Eof, /// Ctrl-C @@ -56,8 +58,8 @@ impl From for ReadlineError { } } -impl From for ReadlineError { - fn from(err: io::CharsError) -> ReadlineError { +impl From for ReadlineError { + fn from(err: char_iter::CharsError) -> ReadlineError { ReadlineError::Char(err) } } diff --git a/src/lib.rs b/src/lib.rs index 14b8de749a..2c717294c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ //! Err(_) => println!("No input"), //! } //! ``` -#![feature(io)] #![cfg_attr(feature="clippy", feature(plugin))] #![cfg_attr(feature="clippy", plugin(clippy))] @@ -30,9 +29,10 @@ pub mod error; pub mod history; mod kill_ring; pub mod line_buffer; +mod char_iter; use std::fmt; -use std::io::{self, Read, Write}; +use std::io::{self, Write}; use std::mem; use std::path::Path; use std::result; @@ -525,7 +525,7 @@ fn edit_history_next(s: &mut State, history: &History, prev: bool) -> Result<()> } /// Completes the line/word -fn complete_line(chars: &mut io::Chars, +fn complete_line(chars: &mut char_iter::Chars, s: &mut State, completer: &Completer) -> Result> { @@ -578,7 +578,7 @@ fn complete_line(chars: &mut io::Chars, /// Incremental search #[cfg_attr(feature="clippy", allow(if_not_else))] -fn reverse_incremental_search(chars: &mut io::Chars, +fn reverse_incremental_search(chars: &mut char_iter::Chars, s: &mut State, history: &History) -> Result> { @@ -655,7 +655,7 @@ fn reverse_incremental_search(chars: &mut io::Chars, Ok(Some(key)) } -fn escape_sequence(chars: &mut io::Chars) -> Result { +fn escape_sequence(chars: &mut char_iter::Chars) -> Result { // Read the next two bytes representing the escape sequence. let seq1 = try!(chars.next().unwrap()); if seq1 == '[' { @@ -733,7 +733,7 @@ fn readline_edit(prompt: &str, kill_ring.reset(); let mut s = State::new(&mut stdout, prompt, MAX_LINE, get_columns(), history.len()); let stdin = io::stdin(); - let mut chars = stdin.lock().chars(); + let mut chars = char_iter::chars(stdin.lock()); loop { let c = chars.next().unwrap(); if c.is_err() && SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { @@ -1162,12 +1162,10 @@ mod test { #[test] fn complete_line() { - use std::io::Read; - let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); let input = b"\n"; - let mut chars = input.chars(); + let mut chars = ::char_iter::chars(&input[..]); let completer = SimpleCompleter; let ch = super::complete_line(&mut chars, &mut s, &completer).unwrap(); assert_eq!(Some('\n'), ch); From 47c5f257e13a86f57adc25ea825bb66a375ff75a Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 1 Jun 2016 08:11:02 +0200 Subject: [PATCH 0004/1201] Let Travis run Rust stable/beta. --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 749cdbc369..0280c43c19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: rust rust: -- nightly + - 1.9.0 + - beta + - nightly script: - cargo build --verbose - cargo test --verbose From 7825809f25210035a6191dbe8ab20afacd5cac0f Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Thu, 2 Jun 2016 22:43:40 -0400 Subject: [PATCH 0005/1201] Add TODO note to remove char_iter once Read::Chars stabilizes --- src/char_iter.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/char_iter.rs b/src/char_iter.rs index 7d0c46de3b..129786d34d 100644 --- a/src/char_iter.rs +++ b/src/char_iter.rs @@ -1,6 +1,7 @@ //! An iterator over the `char`s of a reader. //! //! A copy of the unstable code from the stdlib's std::io::Read::chars. +//! TODO: Remove this once [Read::chars](https://github.com/rust-lang/rust/issues/27802) has been stabilized use std::error; use std::fmt; From 3f8acacda4d4f84a27fab694138d3ced4b27d5f0 Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Thu, 2 Jun 2016 22:51:07 -0400 Subject: [PATCH 0006/1201] Release 0.2.3 --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2298d2c115..476a1e0168 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustyline" -version = "0.2.2" +version = "0.2.3" authors = ["Katsu Kawakami "] description = "Rustyline, a readline implementation based on Antirez's Linenoise" documentation = "http://kkawakam.github.io/rustyline/rustyline" diff --git a/README.md b/README.md index 8fad9aacc8..604d7b0fc5 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ to your `Cargo.toml`: ```toml [dependencies] -rustyline = "0.2.2" +rustyline = "0.2.3" ``` ## Features From 41af211d3702599efb7dcf57d262947b0fcb772d Mon Sep 17 00:00:00 2001 From: Balint JB Date: Fri, 10 Jun 2016 22:32:07 +0200 Subject: [PATCH 0007/1201] fix musl --- src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2c717294c1..041957de11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,9 +220,13 @@ fn disable_raw_mode(original_termios: termios::Termios) -> Result<()> { #[cfg(any(target_os = "macos", target_os = "freebsd"))] const TIOCGWINSZ: libc::c_ulong = 0x40087468; -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "android"))] const TIOCGWINSZ: libc::c_ulong = 0x5413; +#[cfg(all(target_os = "linux", target_env = "musl"))] +const TIOCGWINSZ: libc::c_int = 0x5413; + + /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. #[cfg(any(target_os = "linux", From f422c1032ec04f2998cb0f2559cad2d4a1f49f1a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 2 Jul 2016 10:55:04 +0200 Subject: [PATCH 0008/1201] Remove clippy dependency Use `cargo clippy` instead. --- Cargo.toml | 1 - src/lib.rs | 2 -- 2 files changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 476a1e0168..31f659aa16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ libc = "0.2.7" nix = "0.5.0" unicode-width = "0.1.3" encode_unicode = "0.1.3" -clippy = {version = "~0.0.58", optional = true} [dev-dependencies] tempdir = "0.3.4" diff --git a/src/lib.rs b/src/lib.rs index 041957de11..d383f34d48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,8 +14,6 @@ //! Err(_) => println!("No input"), //! } //! ``` -#![cfg_attr(feature="clippy", feature(plugin))] -#![cfg_attr(feature="clippy", plugin(clippy))] extern crate libc; extern crate nix; From 7b5256d81a85df18aabf98f14987156d6e4464c6 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 2 Jul 2016 11:05:29 +0200 Subject: [PATCH 0009/1201] Add clippy and crate status --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 604d7b0fc5..48772e8d9c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # RustyLine [![Build Status](https://travis-ci.org/kkawakam/rustyline.svg?branch=master)](https://travis-ci.org/kkawakam/rustyline) +[![Clippy Linting Result](https://clippy.bashy.io/github/kkawakam/rustyline/master/badge.svg)](https://clippy.bashy.io/github/kkawakam/rustyline/master/log) +[![](http://meritbadge.herokuapp.com/rustyline)](https://crates.io/crates/rustyline) Readline implementation in Rust that is based on [Antirez' Linenoise](https://github.com/antirez/linenoise) From 5a59798e8c63824eefbe5b1630a9293e1539418a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 2 Jul 2016 11:05:29 +0200 Subject: [PATCH 0010/1201] Add clippy and crate status --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8fad9aacc8..7ccb49c2d5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # RustyLine [![Build Status](https://travis-ci.org/kkawakam/rustyline.svg?branch=master)](https://travis-ci.org/kkawakam/rustyline) +[![Clippy Linting Result](https://clippy.bashy.io/github/kkawakam/rustyline/master/badge.svg)](https://clippy.bashy.io/github/kkawakam/rustyline/master/log) +[![](http://meritbadge.herokuapp.com/rustyline)](https://crates.io/crates/rustyline) Readline implementation in Rust that is based on [Antirez' Linenoise](https://github.com/antirez/linenoise) From 295f1185a45ce3eae978f6a9270bb9574deaa0c3 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 7 Jul 2016 20:41:38 +0200 Subject: [PATCH 0011/1201] Remove clippy dependency Use `cargo clippy` instead. --- Cargo.toml | 1 - src/lib.rs | 2 -- 2 files changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9f419772ff..2fde663742 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ license = "MIT" libc = "0.2.7" nix = "0.5.0" unicode-width = "0.1.3" -clippy = {version = "~0.0.58", optional = true} [dev-dependencies] tempdir = "0.3.4" diff --git a/src/lib.rs b/src/lib.rs index 05e96bce6d..48b4bd3039 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,8 +17,6 @@ #![feature(io)] #![feature(iter_arith)] #![feature(unicode)] -#![cfg_attr(feature="clippy", feature(plugin))] -#![cfg_attr(feature="clippy", plugin(clippy))] extern crate libc; extern crate nix; From 55892b8fd41eee6779c86c5b41fd4ab620e2a439 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 8 Jul 2016 19:19:43 +0200 Subject: [PATCH 0012/1201] Fix multi-line prompt. --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 48b4bd3039..55be83bd9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,6 @@ //! } //! ``` #![feature(io)] -#![feature(iter_arith)] #![feature(unicode)] extern crate libc; @@ -193,14 +192,15 @@ fn from_errno(errno: Errno) -> error::ReadlineError { /// Enable raw mode for the TERM fn enable_raw_mode() -> Result { use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, - OPOST, VMIN, VTIME}; + /*OPOST, */VMIN, VTIME}; if !is_a_tty(libc::STDIN_FILENO) { return Err(from_errno(Errno::ENOTTY)); } let original_term = try!(termios::tcgetattr(libc::STDIN_FILENO)); let mut raw = original_term; raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); // disable BREAK interrupt, CR to NL conversion on input, input parity check, strip high bit (bit 8), output flow control - raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing + // we don't want raw output, it turns newlines into straight linefeeds + //raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits) raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); // disable echoing, canonical mode, extended input processing and signals raw.c_cc[VMIN] = 1; // One character-at-a-time input From 3e3db21f895bef5b7324d5aba971480a537d69d2 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 8 Jul 2016 19:19:43 +0200 Subject: [PATCH 0013/1201] Fix multi-line prompt. --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d383f34d48..20b7f5da76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -193,14 +193,15 @@ fn from_errno(errno: Errno) -> error::ReadlineError { /// Enable raw mode for the TERM fn enable_raw_mode() -> Result { use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, - OPOST, VMIN, VTIME}; + /*OPOST, */VMIN, VTIME}; if !is_a_tty(libc::STDIN_FILENO) { return Err(from_errno(Errno::ENOTTY)); } let original_term = try!(termios::tcgetattr(libc::STDIN_FILENO)); let mut raw = original_term; raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); // disable BREAK interrupt, CR to NL conversion on input, input parity check, strip high bit (bit 8), output flow control - raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing + // we don't want raw output, it turns newlines into straight linefeeds + //raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits) raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); // disable echoing, canonical mode, extended input processing and signals raw.c_cc[VMIN] = 1; // One character-at-a-time input From 91662ec60718b3164deafbbfd0eeae543df4186d Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Tue, 7 Jun 2016 00:06:03 -0400 Subject: [PATCH 0014/1201] Move from_errno to the Error module --- src/error.rs | 6 ++++++ src/lib.rs | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/error.rs b/src/error.rs index 844805b52d..755824cce1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -63,3 +63,9 @@ impl From for ReadlineError { ReadlineError::Char(err) } } + +impl ReadlineError { + pub fn from_errno(errno: nix::errno::Errno) -> ReadlineError { + ReadlineError::from(nix::Error::from_errno(errno)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 20b7f5da76..bcc1fb894a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -186,16 +186,13 @@ fn is_unsupported_term() -> bool { } } -fn from_errno(errno: Errno) -> error::ReadlineError { - error::ReadlineError::from(nix::Error::from_errno(errno)) -} /// Enable raw mode for the TERM fn enable_raw_mode() -> Result { use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, /*OPOST, */VMIN, VTIME}; if !is_a_tty(libc::STDIN_FILENO) { - return Err(from_errno(Errno::ENOTTY)); + return Err(error::ReadlineError::from_errno(Errno::ENOTTY)); } let original_term = try!(termios::tcgetattr(libc::STDIN_FILENO)); let mut raw = original_term; From 8a1f8fb2870a76acbac98c9feee078b4e0d7e06c Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Tue, 7 Jun 2016 23:27:39 -0400 Subject: [PATCH 0015/1201] Remove unneded pub mod declarations --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bcc1fb894a..32586487eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,10 +23,10 @@ extern crate encode_unicode; pub mod completion; #[allow(non_camel_case_types)] mod consts; -pub mod error; -pub mod history; +mod error; +mod history; mod kill_ring; -pub mod line_buffer; +mod line_buffer; mod char_iter; use std::fmt; From 18951612560f01b0cf86a6bce7141384f7d0c311 Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Wed, 8 Jun 2016 20:48:56 -0400 Subject: [PATCH 0016/1201] Ignore swap files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 94408df698..4173d064f7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ # Generated by Cargo /target/ Cargo.lock + +# vim swap file +*.swp From 056159fe9ac8b3eba43b29cdbd1e5e70ab16dadb Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Wed, 8 Jun 2016 20:50:43 -0400 Subject: [PATCH 0017/1201] Move platform specific logic for tty to different files In order to support the windows platform, we will split the platform specific logic into the tty directory. --- src/lib.rs | 79 +++++++++++----------------------------------- src/tty/common.rs | 7 ++++ src/tty/unix.rs | 52 ++++++++++++++++++++++++++++++ src/tty/windows.rs | 5 +++ 4 files changed, 83 insertions(+), 60 deletions(-) create mode 100644 src/tty/common.rs create mode 100644 src/tty/unix.rs create mode 100644 src/tty/windows.rs diff --git a/src/lib.rs b/src/lib.rs index 32586487eb..5aaaafd715 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,12 +23,22 @@ extern crate encode_unicode; pub mod completion; #[allow(non_camel_case_types)] mod consts; -mod error; -mod history; +pub mod error; +pub mod history; mod kill_ring; mod line_buffer; mod char_iter; +// Depending on the platform, load the correct +// tty modules +#[cfg(unix)] +#[path = "tty/unix.rs"] mod tty; + +#[cfg(windows)] +#[path = "tty/windows.rs"] mod tty; + +#[path = "tty/common.rs"] mod tty_common; + use std::fmt; use std::io::{self, Write}; use std::mem; @@ -36,7 +46,6 @@ use std::path::Path; use std::result; use std::sync; use std::sync::atomic; -use nix::errno::Errno; use nix::sys::signal; use nix::sys::termios; use encode_unicode::CharExt; @@ -163,56 +172,6 @@ impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> { } } -/// Unsupported Terminals that don't support RAW mode -static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"]; - -/// Check to see if `fd` is a TTY -fn is_a_tty(fd: libc::c_int) -> bool { - unsafe { libc::isatty(fd) != 0 } -} - -/// Check to see if the current `TERM` is unsupported -fn is_unsupported_term() -> bool { - use std::ascii::AsciiExt; - match std::env::var("TERM") { - Ok(term) => { - let mut unsupported = false; - for iter in &UNSUPPORTED_TERM { - unsupported = (*iter).eq_ignore_ascii_case(&term) - } - unsupported - } - Err(_) => false, - } -} - - -/// Enable raw mode for the TERM -fn enable_raw_mode() -> Result { - use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, - /*OPOST, */VMIN, VTIME}; - if !is_a_tty(libc::STDIN_FILENO) { - return Err(error::ReadlineError::from_errno(Errno::ENOTTY)); - } - let original_term = try!(termios::tcgetattr(libc::STDIN_FILENO)); - let mut raw = original_term; - raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); // disable BREAK interrupt, CR to NL conversion on input, input parity check, strip high bit (bit 8), output flow control - // we don't want raw output, it turns newlines into straight linefeeds - //raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing - raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits) - raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); // disable echoing, canonical mode, extended input processing and signals - raw.c_cc[VMIN] = 1; // One character-at-a-time input - raw.c_cc[VTIME] = 0; // with blocking read - try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &raw)); - Ok(original_term) -} - -/// Disable Raw mode for the term -fn disable_raw_mode(original_termios: termios::Termios) -> Result<()> { - try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &original_termios)); - Ok(()) -} - #[cfg(any(target_os = "macos", target_os = "freebsd"))] const TIOCGWINSZ: libc::c_ulong = 0x40087468; @@ -870,9 +829,9 @@ fn readline_edit(prompt: &str, } } KeyPress::CTRL_Z => { - try!(disable_raw_mode(original_termios)); + try!(tty::disable_raw_mode(original_termios)); try!(signal::raise(signal::SIGSTOP)); - try!(enable_raw_mode()); // TODO original_termios may have changed + try!(tty::enable_raw_mode()); // TODO original_termios may have changed try!(s.refresh_line()) } // TODO CTRL-_ // undo @@ -952,7 +911,7 @@ struct Guard(termios::Termios); impl Drop for Guard { fn drop(&mut self) { let Guard(termios) = *self; - disable_raw_mode(termios); + tty::disable_raw_mode(termios); } } @@ -963,7 +922,7 @@ fn readline_raw(prompt: &str, completer: Option<&Completer>, kill_ring: &mut KillRing) -> Result { - let original_termios = try!(enable_raw_mode()); + let original_termios = try!(tty::enable_raw_mode()); let guard = Guard(original_termios); let user_input = readline_edit(prompt, history, completer, kill_ring, original_termios); drop(guard); // try!(disable_raw_mode(original_termios)); @@ -996,9 +955,9 @@ impl<'completer> Editor<'completer> { // TODO check what is done in rl_initialize() // if the number of columns is stored here, we need a SIGWINCH handler... let editor = Editor { - unsupported_term: is_unsupported_term(), - stdin_isatty: is_a_tty(libc::STDIN_FILENO), - stdout_isatty: is_a_tty(libc::STDOUT_FILENO), + unsupported_term: tty::is_unsupported_term(), + stdin_isatty: tty_common::is_a_tty(libc::STDIN_FILENO), + stdout_isatty: tty_common::is_a_tty(libc::STDOUT_FILENO), history: History::new(), completer: None, kill_ring: KillRing::new(60), diff --git a/src/tty/common.rs b/src/tty/common.rs new file mode 100644 index 0000000000..a5a3e4485f --- /dev/null +++ b/src/tty/common.rs @@ -0,0 +1,7 @@ +extern crate libc; + +/// Check to see if `fd` is a TTY +pub fn is_a_tty(fd: libc::c_int) -> bool { + unsafe { libc::isatty(fd) != 0 } +} + diff --git a/src/tty/unix.rs b/src/tty/unix.rs new file mode 100644 index 0000000000..c62b5c6f3e --- /dev/null +++ b/src/tty/unix.rs @@ -0,0 +1,52 @@ +extern crate nix; +extern crate libc; + +use std; +use nix::sys::termios; +use nix::errno::Errno; +use super::Result; +use super::tty_common; +use super::error; + +/// Unsupported Terminals that don't support RAW mode +static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"]; + +/// Check to see if the current `TERM` is unsupported in unix +pub fn is_unsupported_term() -> bool { + use std::ascii::AsciiExt; + match std::env::var("TERM") { + Ok(term) => { + let mut unsupported = false; + for iter in &UNSUPPORTED_TERM { + unsupported = (*iter).eq_ignore_ascii_case(&term) + } + unsupported + } + Err(_) => false, + } +} + +/// Enable raw mode for the TERM +pub fn enable_raw_mode() -> Result { + use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, + OPOST, VMIN, VTIME}; + if !tty_common::is_a_tty(libc::STDIN_FILENO) { + return Err(error::ReadlineError::from_errno(Errno::ENOTTY)); + } + let original_term = try!(termios::tcgetattr(libc::STDIN_FILENO)); + let mut raw = original_term; + raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); // disable BREAK interrupt, CR to NL conversion on input, input parity check, strip high bit (bit 8), output flow control + raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing + raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits) + raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); // disable echoing, canonical mode, extended input processing and signals + raw.c_cc[VMIN] = 1; // One character-at-a-time input + raw.c_cc[VTIME] = 0; // with blocking read + try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &raw)); + Ok(original_term) +} + +/// Disable Raw mode for the term +pub fn disable_raw_mode(original_termios: termios::Termios) -> Result<()> { + try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &original_termios)); + Ok(()) +} diff --git a/src/tty/windows.rs b/src/tty/windows.rs new file mode 100644 index 0000000000..3a9772b154 --- /dev/null +++ b/src/tty/windows.rs @@ -0,0 +1,5 @@ + +/// Checking for an unsupported TERM in windows is a no-op +pub fn is_unsupported_term() -> bool { + false +} From e6d5d16cd2de44614cc4223ce5bdc3dd34b0423a Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Wed, 8 Jun 2016 23:59:38 -0400 Subject: [PATCH 0018/1201] CTRL-Z will only be supported on unix --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 5aaaafd715..deefe19854 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -828,6 +828,7 @@ fn readline_edit(prompt: &str, try!(edit_yank(&mut s, text)) } } + #[cfg(unix)] KeyPress::CTRL_Z => { try!(tty::disable_raw_mode(original_termios)); try!(signal::raise(signal::SIGSTOP)); From 0c6c5f352a981897651c47a6499b7bde35d1734e Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Thu, 9 Jun 2016 00:08:26 -0400 Subject: [PATCH 0019/1201] SIGNWINCH handler only needed for unix --- src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index deefe19854..b5faff9063 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1037,6 +1037,7 @@ impl<'completer> fmt::Debug for Editor<'completer> { static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT; static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; +#[cfg(unix)] fn install_sigwinch_handler() { SIGWINCH_ONCE.call_once(|| unsafe { let sigwinch = signal::SigAction::new(signal::SigHandler::Handler(sigwinch_handler), @@ -1045,6 +1046,13 @@ fn install_sigwinch_handler() { let _ = signal::sigaction(signal::SIGWINCH, &sigwinch); }); } + +// no-op on windows +#[cfg(windows)] +fn install_sigwinch_handler() { +} + + extern "C" fn sigwinch_handler(_: signal::SigNum) { SIGWINCH.store(true, atomic::Ordering::SeqCst); } From 31cc381e7c4c0405286b510f2215c2b57c4049d2 Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Sat, 11 Jun 2016 15:51:09 -0400 Subject: [PATCH 0020/1201] Revert "Remove unneded pub mod declarations" This reverts commit 715f658e4900cfd9a8c8f0f54a3a5cdc89c25363. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index b5faff9063..544759bd19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ mod consts; pub mod error; pub mod history; mod kill_ring; -mod line_buffer; +pub mod line_buffer; mod char_iter; // Depending on the platform, load the correct From e20f91c5f7028bfd3bd9050192bbd5536f7b21fd Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Sat, 11 Jun 2016 23:38:15 -0400 Subject: [PATCH 0021/1201] Created Terminal Trait for different platform TTYs Terminal trait is defined in tty/common.rs where it requires implementation of enable_raw_mode and disable_raw_mode. readline_edit will now move around a mutable reference to a Terminal instead of the platform specific struct. --- src/lib.rs | 28 ++++++------------- src/tty/common.rs | 12 +++++++++ src/tty/unix.rs | 69 ++++++++++++++++++++++++++++++++--------------- 3 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 544759bd19..ace4affc66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,9 +47,8 @@ use std::result; use std::sync; use std::sync::atomic; use nix::sys::signal; -use nix::sys::termios; use encode_unicode::CharExt; - +use tty_common::Terminal; use completion::Completer; use consts::{KeyPress, char_to_key_press}; use history::History; @@ -680,11 +679,11 @@ fn escape_sequence(chars: &mut char_iter::Chars) -> Result(prompt: &str, history: &mut History, completer: Option<&Completer>, kill_ring: &mut KillRing, - original_termios: termios::Termios) + mut term: T) -> Result { let mut stdout = io::stdout(); try!(write_and_flush(&mut stdout, prompt.as_bytes())); @@ -830,9 +829,9 @@ fn readline_edit(prompt: &str, } #[cfg(unix)] KeyPress::CTRL_Z => { - try!(tty::disable_raw_mode(original_termios)); + try!(term.disable_raw_mode()); try!(signal::raise(signal::SIGSTOP)); - try!(tty::enable_raw_mode()); // TODO original_termios may have changed + try!(term.enable_raw_mode()); // TODO term may have changed try!(s.refresh_line()) } // TODO CTRL-_ // undo @@ -906,16 +905,6 @@ fn readline_edit(prompt: &str, Ok(s.line.into_string()) } -struct Guard(termios::Termios); - -#[allow(unused_must_use)] -impl Drop for Guard { - fn drop(&mut self) { - let Guard(termios) = *self; - tty::disable_raw_mode(termios); - } -} - /// Readline method that will enable RAW mode, call the `readline_edit()` /// method and disable raw mode fn readline_raw(prompt: &str, @@ -923,10 +912,9 @@ fn readline_raw(prompt: &str, completer: Option<&Completer>, kill_ring: &mut KillRing) -> Result { - let original_termios = try!(tty::enable_raw_mode()); - let guard = Guard(original_termios); - let user_input = readline_edit(prompt, history, completer, kill_ring, original_termios); - drop(guard); // try!(disable_raw_mode(original_termios)); + let mut term = tty::get_terminal(); + try!(term.enable_raw_mode()); + let user_input = readline_edit(prompt, history, completer, kill_ring, term); println!(""); user_input } diff --git a/src/tty/common.rs b/src/tty/common.rs index a5a3e4485f..0a6e88c1d0 100644 --- a/src/tty/common.rs +++ b/src/tty/common.rs @@ -1,4 +1,16 @@ +//! This module implements and describes common TTY methods & traits extern crate libc; +use super::Result; + +/// Trait that should be for each TTY/Terminal on various platforms +/// (e.g. unix & windows) +pub trait Terminal { + /// Enable RAW mode for the terminal + fn enable_raw_mode(&mut self) -> Result<()>; + + /// Disable RAW mode for the terminal + fn disable_raw_mode(&self) -> Result<()>; +} /// Check to see if `fd` is a TTY pub fn is_a_tty(fd: libc::c_int) -> bool { diff --git a/src/tty/unix.rs b/src/tty/unix.rs index c62b5c6f3e..fec4110883 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -7,11 +7,16 @@ use nix::errno::Errno; use super::Result; use super::tty_common; use super::error; +use tty_common::Terminal; /// Unsupported Terminals that don't support RAW mode static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"]; -/// Check to see if the current `TERM` is unsupported in unix +/// Get UnixTerminal struct +pub fn get_terminal() -> UnixTerminal { + UnixTerminal{ original_termios: None } +} + pub fn is_unsupported_term() -> bool { use std::ascii::AsciiExt; match std::env::var("TERM") { @@ -26,27 +31,49 @@ pub fn is_unsupported_term() -> bool { } } -/// Enable raw mode for the TERM -pub fn enable_raw_mode() -> Result { - use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, - OPOST, VMIN, VTIME}; - if !tty_common::is_a_tty(libc::STDIN_FILENO) { - return Err(error::ReadlineError::from_errno(Errno::ENOTTY)); + +pub struct UnixTerminal { + original_termios: Option +} + +impl tty_common::Terminal for UnixTerminal { + /// Enable raw mode for the TERM + fn enable_raw_mode(&mut self) -> Result<()> { + use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, + OPOST, VMIN, VTIME}; + if !tty_common::is_a_tty(libc::STDIN_FILENO) { + return Err(error::ReadlineError::from_errno(Errno::ENOTTY)); + } + let original_termios = try!(termios::tcgetattr(libc::STDIN_FILENO)); + let mut raw = original_termios; + // disable BREAK interrupt, CR to NL conversion on input, + // input parity check, strip high bit (bit 8), output flow control + raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing + raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits) + // disable echoing, canonical mode, extended input processing and signals + raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; // One character-at-a-time input + raw.c_cc[VTIME] = 0; // with blocking read + try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &raw)); + + // Set the original terminal to the struct field + self.original_termios = Some(original_termios); + Ok(()) + } + + /// Disable Raw mode for the term + fn disable_raw_mode(&self) -> Result<()> { + try!(termios::tcsetattr(libc::STDIN_FILENO, + termios::TCSAFLUSH, + &self.original_termios.expect("RAW was not enabled previously"))); + Ok(()) } - let original_term = try!(termios::tcgetattr(libc::STDIN_FILENO)); - let mut raw = original_term; - raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); // disable BREAK interrupt, CR to NL conversion on input, input parity check, strip high bit (bit 8), output flow control - raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing - raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits) - raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); // disable echoing, canonical mode, extended input processing and signals - raw.c_cc[VMIN] = 1; // One character-at-a-time input - raw.c_cc[VTIME] = 0; // with blocking read - try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &raw)); - Ok(original_term) } -/// Disable Raw mode for the term -pub fn disable_raw_mode(original_termios: termios::Termios) -> Result<()> { - try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &original_termios)); - Ok(()) +#[allow(unused_must_use)] +impl Drop for UnixTerminal { + fn drop(&mut self) { + self.disable_raw_mode(); + } } From c4317f5b5fbfcdfc5b7b33398b806fc2bc465e69 Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Sun, 12 Jun 2016 09:20:05 -0400 Subject: [PATCH 0022/1201] Move get_columns method into appropriate module for unix tty --- src/lib.rs | 41 ++--------------------------------------- src/tty/unix.rs | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ace4affc66..36f37d757b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,43 +171,6 @@ impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> { } } -#[cfg(any(target_os = "macos", target_os = "freebsd"))] -const TIOCGWINSZ: libc::c_ulong = 0x40087468; - -#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "android"))] -const TIOCGWINSZ: libc::c_ulong = 0x5413; - -#[cfg(all(target_os = "linux", target_env = "musl"))] -const TIOCGWINSZ: libc::c_int = 0x5413; - - -/// Try to get the number of columns in the current terminal, -/// or assume 80 if it fails. -#[cfg(any(target_os = "linux", - target_os = "android", - target_os = "macos", - target_os = "freebsd"))] -fn get_columns() -> usize { - use std::mem::zeroed; - use libc::c_ushort; - use libc; - - unsafe { - #[repr(C)] - struct winsize { - ws_row: c_ushort, - ws_col: c_ushort, - ws_xpixel: c_ushort, - ws_ypixel: c_ushort, - } - - let mut size: winsize = zeroed(); - match libc::ioctl(libc::STDOUT_FILENO, TIOCGWINSZ, &mut size) { - 0 => size.ws_col as usize, // TODO getCursorPosition - _ => 80, - } - } -} fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> { try!(w.write_all(buf)); @@ -689,13 +652,13 @@ fn readline_edit(prompt: &str, try!(write_and_flush(&mut stdout, prompt.as_bytes())); kill_ring.reset(); - let mut s = State::new(&mut stdout, prompt, MAX_LINE, get_columns(), history.len()); + let mut s = State::new(&mut stdout, prompt, MAX_LINE, tty::get_columns(), history.len()); let stdin = io::stdin(); let mut chars = char_iter::chars(stdin.lock()); loop { let c = chars.next().unwrap(); if c.is_err() && SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { - s.cols = get_columns(); + s.cols = tty::get_columns(); try!(s.refresh_line()); continue; } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index fec4110883..1adf68e343 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -12,11 +12,46 @@ use tty_common::Terminal; /// Unsupported Terminals that don't support RAW mode static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"]; +#[cfg(any(target_os = "macos", target_os = "freebsd"))] +const TIOCGWINSZ: libc::c_ulong = 0x40087468; + +#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "android"))] +const TIOCGWINSZ: libc::c_ulong = 0x5413; + +#[cfg(all(target_os = "linux", target_env = "musl"))] +const TIOCGWINSZ: libc::c_int = 0x5413; + +/// Try to get the number of columns in the current terminal, +/// or assume 80 if it fails. +pub fn get_columns() -> usize { + use std::mem::zeroed; + use libc::c_ushort; + use libc; + + unsafe { + #[repr(C)] + struct winsize { + ws_row: c_ushort, + ws_col: c_ushort, + ws_xpixel: c_ushort, + ws_ypixel: c_ushort, + } + + let mut size: winsize = zeroed(); + match libc::ioctl(libc::STDOUT_FILENO, TIOCGWINSZ, &mut size) { + 0 => size.ws_col as usize, // TODO getCursorPosition + _ => 80, + } + } +} + /// Get UnixTerminal struct pub fn get_terminal() -> UnixTerminal { UnixTerminal{ original_termios: None } } +/// Check TERM environment variable to see if current term is in our +/// unsupported list pub fn is_unsupported_term() -> bool { use std::ascii::AsciiExt; match std::env::var("TERM") { @@ -31,7 +66,7 @@ pub fn is_unsupported_term() -> bool { } } - +/// Structure that will contain the original termios before enabling RAW mode pub struct UnixTerminal { original_termios: Option } @@ -71,6 +106,7 @@ impl tty_common::Terminal for UnixTerminal { } } +/// Ensure that RAW mode is disabled even in the case of a panic! #[allow(unused_must_use)] impl Drop for UnixTerminal { fn drop(&mut self) { From cb085b6e405bfff63e8092e441ba21019a715a49 Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Sun, 12 Jun 2016 21:18:25 -0400 Subject: [PATCH 0023/1201] Remove custom paths to tty modules Instead of using custom path to module in lib.rs, load modules for different tty platforms in tty/mod.rs --- src/lib.rs | 18 +++++------------- src/tty/{common.rs => mod.rs} | 10 ++++++++++ src/tty/unix.rs | 11 +++++------ 3 files changed, 20 insertions(+), 19 deletions(-) rename src/tty/{common.rs => mod.rs} (63%) diff --git a/src/lib.rs b/src/lib.rs index 36f37d757b..d2e60e4ea3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,15 +29,7 @@ mod kill_ring; pub mod line_buffer; mod char_iter; -// Depending on the platform, load the correct -// tty modules -#[cfg(unix)] -#[path = "tty/unix.rs"] mod tty; - -#[cfg(windows)] -#[path = "tty/windows.rs"] mod tty; - -#[path = "tty/common.rs"] mod tty_common; +mod tty; use std::fmt; use std::io::{self, Write}; @@ -48,7 +40,7 @@ use std::sync; use std::sync::atomic; use nix::sys::signal; use encode_unicode::CharExt; -use tty_common::Terminal; +use tty::Terminal; use completion::Completer; use consts::{KeyPress, char_to_key_press}; use history::History; @@ -642,7 +634,7 @@ fn escape_sequence(chars: &mut char_iter::Chars) -> Result(prompt: &str, +fn readline_edit(prompt: &str, history: &mut History, completer: Option<&Completer>, kill_ring: &mut KillRing, @@ -908,8 +900,8 @@ impl<'completer> Editor<'completer> { // if the number of columns is stored here, we need a SIGWINCH handler... let editor = Editor { unsupported_term: tty::is_unsupported_term(), - stdin_isatty: tty_common::is_a_tty(libc::STDIN_FILENO), - stdout_isatty: tty_common::is_a_tty(libc::STDOUT_FILENO), + stdin_isatty: tty::is_a_tty(libc::STDIN_FILENO), + stdout_isatty: tty::is_a_tty(libc::STDOUT_FILENO), history: History::new(), completer: None, kill_ring: KillRing::new(60), diff --git a/src/tty/common.rs b/src/tty/mod.rs similarity index 63% rename from src/tty/common.rs rename to src/tty/mod.rs index 0a6e88c1d0..ef8a1a0b28 100644 --- a/src/tty/common.rs +++ b/src/tty/mod.rs @@ -2,6 +2,16 @@ extern crate libc; use super::Result; +// If on Windows platform import Windows TTY module +// and re-export into mod.rs scope +#[cfg(windows)] mod windows; +#[cfg(windows)] pub use self::windows::*; + +// If on Unix platform import Unix TTY module +// and re-export into mod.rs scope +#[cfg(unix)] mod unix; +#[cfg(unix)] pub use self::unix::*; + /// Trait that should be for each TTY/Terminal on various platforms /// (e.g. unix & windows) pub trait Terminal { diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 1adf68e343..fce1b6a050 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -4,10 +4,9 @@ extern crate libc; use std; use nix::sys::termios; use nix::errno::Errno; -use super::Result; -use super::tty_common; -use super::error; -use tty_common::Terminal; +use super::Terminal; +use ::Result; +use ::error; /// Unsupported Terminals that don't support RAW mode static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"]; @@ -71,12 +70,12 @@ pub struct UnixTerminal { original_termios: Option } -impl tty_common::Terminal for UnixTerminal { +impl Terminal for UnixTerminal { /// Enable raw mode for the TERM fn enable_raw_mode(&mut self) -> Result<()> { use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, OPOST, VMIN, VTIME}; - if !tty_common::is_a_tty(libc::STDIN_FILENO) { + if !super::is_a_tty(libc::STDIN_FILENO) { return Err(error::ReadlineError::from_errno(Errno::ENOTTY)); } let original_termios = try!(termios::tcgetattr(libc::STDIN_FILENO)); From bb16b12a6acb8600af88f367112dc8562552fea0 Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Sun, 12 Jun 2016 22:20:03 -0400 Subject: [PATCH 0024/1201] Adding Window-specific isatty function libc does not define the STDIN_FILENO & STDOUT_FILENO for the windows platform. We instead check whether or not stdin and stdout are TTY by checking the GetConsoleMode output. --- src/lib.rs | 4 ++-- src/tty/mod.rs | 12 ++++++++---- src/tty/unix.rs | 16 +++++++++++++++- src/tty/windows.rs | 18 ++++++++++++++++++ 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d2e60e4ea3..302ced7c58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -900,8 +900,8 @@ impl<'completer> Editor<'completer> { // if the number of columns is stored here, we need a SIGWINCH handler... let editor = Editor { unsupported_term: tty::is_unsupported_term(), - stdin_isatty: tty::is_a_tty(libc::STDIN_FILENO), - stdout_isatty: tty::is_a_tty(libc::STDOUT_FILENO), + stdin_isatty: tty::is_a_tty(tty::StandardStream::StdIn), + stdout_isatty: tty::is_a_tty(tty::StandardStream::StdOut), history: History::new(), completer: None, kill_ring: KillRing::new(60), diff --git a/src/tty/mod.rs b/src/tty/mod.rs index ef8a1a0b28..72cc3f368b 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -22,8 +22,12 @@ pub trait Terminal { fn disable_raw_mode(&self) -> Result<()>; } -/// Check to see if `fd` is a TTY -pub fn is_a_tty(fd: libc::c_int) -> bool { - unsafe { libc::isatty(fd) != 0 } +/// Enum for Standard Streams +/// +/// libc::STDIN_FILENO/STDOUT_FILENO/STDERR_FILENO is not defined for the +/// Windows platform. We will use this enum instead when calling isatty +/// function +pub enum StandardStream { + StdIn, + StdOut, } - diff --git a/src/tty/unix.rs b/src/tty/unix.rs index fce1b6a050..d3f192df14 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -5,6 +5,7 @@ use std; use nix::sys::termios; use nix::errno::Errno; use super::Terminal; +use super::StandardStream; use ::Result; use ::error; @@ -65,6 +66,19 @@ pub fn is_unsupported_term() -> bool { } } + +/// Return whether or not STDIN, STDOUT or STDERR is a TTY +pub fn is_a_tty(stream: StandardStream) -> bool { + extern crate libc; + + let fd = match stream { + StandardStream::StdIn => libc::STDIN_FILENO, + StandardStream::StdOut => libc::STDOUT_FILENO, + }; + + unsafe { libc::isatty(fd) != 0 } +} + /// Structure that will contain the original termios before enabling RAW mode pub struct UnixTerminal { original_termios: Option @@ -75,7 +89,7 @@ impl Terminal for UnixTerminal { fn enable_raw_mode(&mut self) -> Result<()> { use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, OPOST, VMIN, VTIME}; - if !super::is_a_tty(libc::STDIN_FILENO) { + if !is_a_tty(StandardStream::StdIn) { return Err(error::ReadlineError::from_errno(Errno::ENOTTY)); } let original_termios = try!(termios::tcgetattr(libc::STDIN_FILENO)); diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 3a9772b154..b661d2f1a3 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,3 +1,21 @@ +extern crate kernel32; +extern crate winapi; + +use super::StandardStream; + +/// Return whether or not STDIN, STDOUT or STDERR is a TTY +fn is_a_tty(stream: StandardStream) -> bool { + let handle = match stream { + StandardStream::StdIn => winapi::winbase::STD_INPUT_HANDLE, + StandardStream::Stdout => winapi::winbase::STD_OUTPUT_HANDLE, + }; + + unsafe { + let handle = kernel32::GetStdHandle(handle); + let mut out = 0; + kernel32::GetConsoleMode(handle, &mut out) != 0 + } +} /// Checking for an unsupported TERM in windows is a no-op pub fn is_unsupported_term() -> bool { From 14668902322bbf2c732e22e8c78685ab9b19c13f Mon Sep 17 00:00:00 2001 From: kkawakam Date: Mon, 13 Jun 2016 01:46:59 -0400 Subject: [PATCH 0025/1201] Adding winapi crate to support rustyline on windows --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 31f659aa16..e94718b478 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,9 @@ libc = "0.2.7" nix = "0.5.0" unicode-width = "0.1.3" encode_unicode = "0.1.3" +clippy = {version = "~0.0.58", optional = true} +winapi = "0.2" +kernel32-sys = "0.2.2" [dev-dependencies] tempdir = "0.3.4" From 328c0e2c9a66a882774f755d4754f7ef27c4c27b Mon Sep 17 00:00:00 2001 From: kkawakam Date: Mon, 13 Jun 2016 01:50:56 -0400 Subject: [PATCH 0026/1201] Add conditional compilation for unix errors --- src/error.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/error.rs b/src/error.rs index 755824cce1..9105ba62ac 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,24 +12,26 @@ use char_iter; pub enum ReadlineError { /// I/O Error Io(io::Error), - /// Error from syscall - Errno(nix::Error), /// Chars Error Char(char_iter::CharsError), /// EOF (Ctrl-d) Eof, /// Ctrl-C Interrupted, + /// Unix Error from syscall + #[cfg(unix)] + Errno(nix::Error), } impl fmt::Display for ReadlineError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ReadlineError::Io(ref err) => err.fmt(f), - ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()), ReadlineError::Char(ref err) => err.fmt(f), ReadlineError::Eof => write!(f, "EOF"), ReadlineError::Interrupted => write!(f, "Interrupted"), + #[cfg(unix)] + ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()), } } } @@ -38,10 +40,11 @@ impl error::Error for ReadlineError { fn description(&self) -> &str { match *self { ReadlineError::Io(ref err) => err.description(), - ReadlineError::Errno(ref err) => err.errno().desc(), ReadlineError::Char(ref err) => err.description(), ReadlineError::Eof => "EOF", ReadlineError::Interrupted => "Interrupted", + #[cfg(unix)] + ReadlineError::Errno(ref err) => err.errno().desc(), } } } @@ -52,6 +55,7 @@ impl From for ReadlineError { } } +#[cfg(unix)] impl From for ReadlineError { fn from(err: nix::Error) -> ReadlineError { ReadlineError::Errno(err) @@ -65,6 +69,7 @@ impl From for ReadlineError { } impl ReadlineError { + #[cfg(unix)] pub fn from_errno(errno: nix::errno::Errno) -> ReadlineError { ReadlineError::from(nix::Error::from_errno(errno)) } From bbffe699051ac4b11582cb4f5cbe0e7a3ffbd2f5 Mon Sep 17 00:00:00 2001 From: kkawakam Date: Mon, 13 Jun 2016 01:52:18 -0400 Subject: [PATCH 0027/1201] Additional conditional compilation for unix --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 302ced7c58..3f5ecd325b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,8 @@ use std::path::Path; use std::result; use std::sync; use std::sync::atomic; + +#[cfg(unix)] use nix::sys::signal; use encode_unicode::CharExt; use tty::Terminal; @@ -995,7 +997,7 @@ fn install_sigwinch_handler() { fn install_sigwinch_handler() { } - +#[cfg(unix)] extern "C" fn sigwinch_handler(_: signal::SigNum) { SIGWINCH.store(true, atomic::Ordering::SeqCst); } From 9e12f70057b21d1a62e49c4b0a6e321bf4b8482c Mon Sep 17 00:00:00 2001 From: kkawakam Date: Mon, 13 Jun 2016 01:53:22 -0400 Subject: [PATCH 0028/1201] Implement necessary functions for Windows Console support * get_columns * is_a_tty * enable_raw_mode * disable_raw_mode * Drop trait for WindowsTerminal struct --- src/tty/windows.rs | 90 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index b661d2f1a3..46d473c316 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -2,12 +2,54 @@ extern crate kernel32; extern crate winapi; use super::StandardStream; +use super::Terminal; +use ::Result; + +/// Try to get the number of columns in the current terminal, or assume 80 if it fails. +pub fn get_columns() -> usize { + // Get HANDLE to stdout + let handle = unsafe { kernel32::GetStdHandle(winapi::STD_OUTPUT_HANDLE) }; + + // Create CONSOLE_SCREEN_BUFFER_INFO with some default values + let mut csbi = winapi::wincon::CONSOLE_SCREEN_BUFFER_INFO { + dwSize: winapi::wincon::COORD { X: 0, Y: 0 }, + dwCursorPosition: winapi::wincon::COORD { X: 0, Y: 0 }, + wAttributes: 0, + srWindow: winapi::wincon::SMALL_RECT { + Left: 0, + Top: 0, + Right: 0, + Bottom: 0, + }, + dwMaximumWindowSize: winapi::wincon::COORD { X: 0, Y: 0 }, + }; + + let success: bool = unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut csbi) != 0 }; + + // If we were not able to retrieve console info successfully, + // we will default to a column size of 80 + if success && csbi.dwSize.X > 0 { + csbi.dwSize.X as usize + } else { + 80 + } +} + +/// Get WindowsTerminal struct +pub fn get_terminal() -> WindowsTerminal { + WindowsTerminal{ original_mode: None } +} + +/// Checking for an unsupported TERM in windows is a no-op +pub fn is_unsupported_term() -> bool { + false +} /// Return whether or not STDIN, STDOUT or STDERR is a TTY -fn is_a_tty(stream: StandardStream) -> bool { +pub fn is_a_tty(stream: StandardStream) -> bool { let handle = match stream { - StandardStream::StdIn => winapi::winbase::STD_INPUT_HANDLE, - StandardStream::Stdout => winapi::winbase::STD_OUTPUT_HANDLE, + StandardStream::StdIn => winapi::STD_INPUT_HANDLE, + StandardStream::StdOut => winapi::STD_OUTPUT_HANDLE, }; unsafe { @@ -17,7 +59,43 @@ fn is_a_tty(stream: StandardStream) -> bool { } } -/// Checking for an unsupported TERM in windows is a no-op -pub fn is_unsupported_term() -> bool { - false +pub struct WindowsTerminal { + original_mode: Option +} + +impl Terminal for WindowsTerminal { + /// Enable raw mode for the TERM + fn enable_raw_mode(&mut self) -> Result<()> { + let mut original_mode: winapi::minwindef::DWORD = 0; + unsafe { + let handle = kernel32::GetStdHandle(winapi::STD_INPUT_HANDLE); + kernel32::GetConsoleMode(handle, &mut original_mode); + kernel32::SetConsoleMode( + handle, + original_mode & !(winapi::wincon::ENABLE_LINE_INPUT | + winapi::wincon::ENABLE_ECHO_INPUT | + winapi::wincon::ENABLE_PROCESSED_INPUT) + ); + }; + self.original_mode = Some(original_mode); + Ok(()) + } + + /// Disable Raw mode for the term + fn disable_raw_mode(&self) -> Result<()> { + unsafe { + let handle = kernel32::GetStdHandle(winapi::STD_INPUT_HANDLE); + kernel32::SetConsoleMode(handle, + self.original_mode.expect("RAW MODE was not enabled previously")); + } + Ok(()) + } +} + +/// Ensure that RAW mode is disabled even in the case of a panic! +#[allow(unused_must_use)] +impl Drop for WindowsTerminal { + fn drop(&mut self) { + self.disable_raw_mode(); + } } From 34da2b95511a0c4e74d56f808d34b9bef36ac2f0 Mon Sep 17 00:00:00 2001 From: kkawakam Date: Mon, 13 Jun 2016 01:56:38 -0400 Subject: [PATCH 0029/1201] fixup! Created Terminal Trait for different platform TTYs --- src/tty/unix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index d3f192df14..1b46fea7af 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -114,7 +114,7 @@ impl Terminal for UnixTerminal { fn disable_raw_mode(&self) -> Result<()> { try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, - &self.original_termios.expect("RAW was not enabled previously"))); + &self.original_termios.expect("RAW MODE was not enabled previously"))); Ok(()) } } From 7ca9a95eb7a5a7b3c92591679b5b1664141c1112 Mon Sep 17 00:00:00 2001 From: kkawakam Date: Mon, 13 Jun 2016 21:58:24 -0400 Subject: [PATCH 0030/1201] Adding appveyor.yml --- appveyor.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..07642c7a0a --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,25 @@ +environment: + matrix: + - TARGET: 1.9.0-x86_64-pc-windows-msvc + - TARGET: 1.9.0-i686-pc-windows-msvc + - TARGET: 1.9.0-x86_64-pc-windows-gnu + - TARGET: 1.9.0-i686-pc-windows-gnu + - TARGET: beta-x86_64-pc-windows-msvc + - TARGET: beta-i686-pc-windows-msvc + - TARGET: beta-x86_64-pc-windows-gnu + - TARGET: beta-i686-pc-windows-gnu + - TARGET: nightly-x86_64-pc-windows-msvc + - TARGET: nightly-i686-pc-windows-msvc + - TARGET: nightly-x86_64-pc-windows-gnu + - TARGET: nightly-i686-pc-windows-gnu +install: + - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" -FileName "rust-install.exe" + - ps: .\rust-install.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" + - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin + - SET PATH=%PATH%;C:\MinGW\bin + - rustc -V + - cargo -V +build_script: + - cargo build +test_script: + - cargo test From 2b27c77c8ddbf6735854978104f68c91cd5b0f2c Mon Sep 17 00:00:00 2001 From: kkawakam Date: Mon, 13 Jun 2016 22:06:14 -0400 Subject: [PATCH 0031/1201] Fixing appveyor build --- appveyor.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 07642c7a0a..c9ec3aef9d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,13 +13,14 @@ environment: - TARGET: nightly-x86_64-pc-windows-gnu - TARGET: nightly-i686-pc-windows-gnu install: - - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" -FileName "rust-install.exe" - - ps: .\rust-install.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" + - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" + - rust-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin - SET PATH=%PATH%;C:\MinGW\bin - rustc -V - cargo -V -build_script: - - cargo build + +build: false + test_script: - - cargo test + - cargo test --verbose From ac7b94f19283fc5d46142f9a861b203530d2080f Mon Sep 17 00:00:00 2001 From: kkawakam Date: Mon, 13 Jun 2016 23:10:11 -0400 Subject: [PATCH 0032/1201] Reduce number of envionments to test in appveyor Building and testing all the different windows environments against stable/beta/nightly would take too long. Reducing the environments msvc & gnu 64 bit environments for beta and 1.9.0 rust --- README.md | 1 + appveyor.yml | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index 48772e8d9c..8573a6e410 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Build Status](https://travis-ci.org/kkawakam/rustyline.svg?branch=master)](https://travis-ci.org/kkawakam/rustyline) [![Clippy Linting Result](https://clippy.bashy.io/github/kkawakam/rustyline/master/badge.svg)](https://clippy.bashy.io/github/kkawakam/rustyline/master/log) [![](http://meritbadge.herokuapp.com/rustyline)](https://crates.io/crates/rustyline) +[![Build status](https://ci.appveyor.com/api/projects/status/ls7sty8nt25rdfkq/branch/master?svg=true)](https://ci.appveyor.com/project/kkawakam/rustyline/branch/master) Readline implementation in Rust that is based on [Antirez' Linenoise](https://github.com/antirez/linenoise) diff --git a/appveyor.yml b/appveyor.yml index c9ec3aef9d..eb55790154 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,17 +1,9 @@ environment: matrix: - TARGET: 1.9.0-x86_64-pc-windows-msvc - - TARGET: 1.9.0-i686-pc-windows-msvc - TARGET: 1.9.0-x86_64-pc-windows-gnu - - TARGET: 1.9.0-i686-pc-windows-gnu - TARGET: beta-x86_64-pc-windows-msvc - - TARGET: beta-i686-pc-windows-msvc - TARGET: beta-x86_64-pc-windows-gnu - - TARGET: beta-i686-pc-windows-gnu - - TARGET: nightly-x86_64-pc-windows-msvc - - TARGET: nightly-i686-pc-windows-msvc - - TARGET: nightly-x86_64-pc-windows-gnu - - TARGET: nightly-i686-pc-windows-gnu install: - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" - rust-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" From fbc837c0dbc9533366cd2920f84861c787f05969 Mon Sep 17 00:00:00 2001 From: kkawakam Date: Thu, 16 Jun 2016 00:06:05 -0400 Subject: [PATCH 0033/1201] Remove ANSI escape sequence from example if on windows --- examples/example.rs | 11 ++++++++++- history.txt | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 history.txt diff --git a/examples/example.rs b/examples/example.rs index 68e63c4725..d098f2620b 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -4,6 +4,15 @@ use rustyline::completion::FilenameCompleter; use rustyline::error::ReadlineError; use rustyline::Editor; +// On unix platforms you can use ANSI escape sequences +#[cfg(unix)] +static PROMPT: &'static str = "\x1b[1;32m>>\x1b[0m "; + +// Windows consoles typically don't support ANSI escape sequences out +// of the box +#[cfg(windows)] +static PROMPT: &'static str = ">> "; + fn main() { let c = FilenameCompleter::new(); let mut rl = Editor::new(); @@ -12,7 +21,7 @@ fn main() { println!("No previous history."); } loop { - let readline = rl.readline("\x1b[1;32m>>\x1b[0m "); + let readline = rl.readline(PROMPT); match readline { Ok(line) => { rl.add_history_entry(&line); diff --git a/history.txt b/history.txt new file mode 100644 index 0000000000..abb9d2261f --- /dev/null +++ b/history.txt @@ -0,0 +1,4 @@ +asdadsasddsdfasdfasdfsfds +adsdsfafasdfasdfasdf +af +ls From d8eec28cd882f4b584a1c2596f32dfe494472778 Mon Sep 17 00:00:00 2001 From: kkawakam Date: Thu, 16 Jun 2016 00:07:43 -0400 Subject: [PATCH 0034/1201] Add history.txt to gitignore Accidently added history.txt to repo, remove from repo and add it to the gitignore --- .gitignore | 3 +++ history.txt | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 history.txt diff --git a/.gitignore b/.gitignore index 4173d064f7..188ef41619 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ Cargo.lock # vim swap file *.swp + +# default history file +history.txt diff --git a/history.txt b/history.txt deleted file mode 100644 index abb9d2261f..0000000000 --- a/history.txt +++ /dev/null @@ -1,4 +0,0 @@ -asdadsasddsdfasdfasdfsfds -adsdsfafasdfasdfasdf -af -ls From 7223fbee168517912e33c9243d42a25ba490ee12 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 9 Jul 2016 15:58:40 +0200 Subject: [PATCH 0035/1201] Check winapi calls. And declare target specific dependencies. --- Cargo.toml | 6 +++++- appveyor.yml | 4 ++++ src/error.rs | 1 + src/lib.rs | 3 +++ src/tty/windows.rs | 25 +++++++++++++++++-------- 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e94718b478..69996f00a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,14 @@ license = "MIT" [dependencies] libc = "0.2.7" -nix = "0.5.0" unicode-width = "0.1.3" encode_unicode = "0.1.3" clippy = {version = "~0.0.58", optional = true} + +[target.'cfg(unix)'.dependencies] +nix = "0.5.0" + +[target.'cfg(windows)'.dependencies] winapi = "0.2" kernel32-sys = "0.2.2" diff --git a/appveyor.yml b/appveyor.yml index eb55790154..03a90c5212 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,3 +16,7 @@ build: false test_script: - cargo test --verbose + +cache: + - C:\Users\appveyor\.cargo + diff --git a/src/error.rs b/src/error.rs index 9105ba62ac..2f1b259a59 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,7 @@ use std::io; use std::error; use std::fmt; +#[cfg(unix)] use nix; use char_iter; diff --git a/src/lib.rs b/src/lib.rs index 3f5ecd325b..06da79d2e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ //! ``` extern crate libc; +#[cfg(unix)] extern crate nix; extern crate unicode_width; extern crate encode_unicode; @@ -36,6 +37,7 @@ use std::io::{self, Write}; use std::mem; use std::path::Path; use std::result; +#[cfg(unix)] use std::sync; use std::sync::atomic; @@ -980,6 +982,7 @@ impl<'completer> fmt::Debug for Editor<'completer> { } } +#[cfg(unix)] static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT; static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; #[cfg(unix)] diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 46d473c316..c7fc35b911 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,10 +1,19 @@ extern crate kernel32; extern crate winapi; +use std::io; use super::StandardStream; use super::Terminal; use ::Result; +macro_rules! check { + ($funcall:expr) => ( + if $funcall == 0 { + return Err(From::from(io::Error::last_os_error())); + } + ); +} + /// Try to get the number of columns in the current terminal, or assume 80 if it fails. pub fn get_columns() -> usize { // Get HANDLE to stdout @@ -67,15 +76,15 @@ impl Terminal for WindowsTerminal { /// Enable raw mode for the TERM fn enable_raw_mode(&mut self) -> Result<()> { let mut original_mode: winapi::minwindef::DWORD = 0; - unsafe { + unsafe { let handle = kernel32::GetStdHandle(winapi::STD_INPUT_HANDLE); - kernel32::GetConsoleMode(handle, &mut original_mode); - kernel32::SetConsoleMode( + check!(kernel32::GetConsoleMode(handle, &mut original_mode)); + check!(kernel32::SetConsoleMode( handle, - original_mode & !(winapi::wincon::ENABLE_LINE_INPUT | - winapi::wincon::ENABLE_ECHO_INPUT | + original_mode & !(winapi::wincon::ENABLE_LINE_INPUT | + winapi::wincon::ENABLE_ECHO_INPUT | winapi::wincon::ENABLE_PROCESSED_INPUT) - ); + )); }; self.original_mode = Some(original_mode); Ok(()) @@ -85,8 +94,8 @@ impl Terminal for WindowsTerminal { fn disable_raw_mode(&self) -> Result<()> { unsafe { let handle = kernel32::GetStdHandle(winapi::STD_INPUT_HANDLE); - kernel32::SetConsoleMode(handle, - self.original_mode.expect("RAW MODE was not enabled previously")); + check!(kernel32::SetConsoleMode(handle, + self.original_mode.expect("RAW MODE was not enabled previously"))); } Ok(()) } From f0eb3899887ccb48e43863b653803fe8ff98c8b6 Mon Sep 17 00:00:00 2001 From: kkawakam Date: Sat, 9 Jul 2016 10:27:03 -0400 Subject: [PATCH 0036/1201] Porting 3e3db21 to windows_support branch --- src/tty/unix.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 1b46fea7af..2f3d994098 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -88,7 +88,7 @@ impl Terminal for UnixTerminal { /// Enable raw mode for the TERM fn enable_raw_mode(&mut self) -> Result<()> { use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, - OPOST, VMIN, VTIME}; + /*OPOST, */VMIN, VTIME}; if !is_a_tty(StandardStream::StdIn) { return Err(error::ReadlineError::from_errno(Errno::ENOTTY)); } @@ -97,7 +97,8 @@ impl Terminal for UnixTerminal { // disable BREAK interrupt, CR to NL conversion on input, // input parity check, strip high bit (bit 8), output flow control raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing + // we don't want raw output, it turns newlines into straight linefeeds + //raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits) // disable echoing, canonical mode, extended input processing and signals raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); From 4c67604cbd88e0231f00c65d431935a50f706a35 Mon Sep 17 00:00:00 2001 From: kkawakam Date: Sat, 9 Jul 2016 10:36:15 -0400 Subject: [PATCH 0037/1201] Update README reflecting current state of supported platforms --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 8573a6e410..9709bf4c34 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,11 @@ Readline implementation in Rust that is based on [Antirez' Linenoise](https://gi [Documentation](https://kkawakam.github.io/rustyline) + +**Supported Platforms** +* Linux +* Windows - Work in Progress (Issue #37), modifier keys do not work + ## Build This project uses Cargo and Rust Nightly ```bash From bc42b4fced9109deb5301d67750f8670594bbb56 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 07:39:46 +0200 Subject: [PATCH 0038/1201] Mark all unix specific stuff --- Cargo.toml | 4 +++- src/error.rs | 5 +++++ src/lib.rs | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2fde663742..c45da2a5f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,10 @@ license = "MIT" [dependencies] libc = "0.2.7" -nix = "0.5.0" unicode-width = "0.1.3" +[target.'cfg(unix)'.dependencies] +nix = "0.5.0" + [dev-dependencies] tempdir = "0.3.4" diff --git a/src/error.rs b/src/error.rs index d4654a09a2..3c3b2e9590 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,7 @@ use std::io; use std::error; use std::fmt; +#[cfg(unix)] use nix; /// The error type for Rustyline errors that can arise from @@ -11,6 +12,7 @@ pub enum ReadlineError { /// I/O Error Io(io::Error), /// Error from syscall + #[cfg(unix)] Errno(nix::Error), /// Chars Error Char(io::CharsError), @@ -24,6 +26,7 @@ impl fmt::Display for ReadlineError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ReadlineError::Io(ref err) => err.fmt(f), + #[cfg(unix)] ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()), ReadlineError::Char(ref err) => err.fmt(f), ReadlineError::Eof => write!(f, "EOF"), @@ -36,6 +39,7 @@ impl error::Error for ReadlineError { fn description(&self) -> &str { match *self { ReadlineError::Io(ref err) => err.description(), + #[cfg(unix)] ReadlineError::Errno(ref err) => err.errno().desc(), ReadlineError::Char(ref err) => err.description(), ReadlineError::Eof => "EOF", @@ -50,6 +54,7 @@ impl From for ReadlineError { } } +#[cfg(unix)] impl From for ReadlineError { fn from(err: nix::Error) -> ReadlineError { ReadlineError::Errno(err) diff --git a/src/lib.rs b/src/lib.rs index 55be83bd9f..a50a4899fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ #![feature(unicode)] extern crate libc; +#[cfg(unix)] extern crate nix; extern crate unicode_width; @@ -36,8 +37,11 @@ use std::path::Path; use std::result; use std::sync; use std::sync::atomic; +#[cfg(unix)] use nix::errno::Errno; +#[cfg(unix)] use nix::sys::signal; +#[cfg(unix)] use nix::sys::termios; use completion::Completer; @@ -107,6 +111,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { self.refresh(prompt, prompt_size) } + #[cfg(unix)] fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> { use std::fmt::Write; @@ -185,11 +190,13 @@ fn is_unsupported_term() -> bool { } } +#[cfg(unix)] fn from_errno(errno: Errno) -> error::ReadlineError { error::ReadlineError::from(nix::Error::from_errno(errno)) } /// Enable raw mode for the TERM +#[cfg(unix)] fn enable_raw_mode() -> Result { use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, /*OPOST, */VMIN, VTIME}; @@ -210,6 +217,7 @@ fn enable_raw_mode() -> Result { } /// Disable Raw mode for the term +#[cfg(unix)] fn disable_raw_mode(original_termios: termios::Termios) -> Result<()> { try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &original_termios)); Ok(()) @@ -256,6 +264,7 @@ fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> { } /// Clear the screen. Used to handle ctrl+l +#[cfg(unix)] fn clear_screen(out: &mut Write) -> Result<()> { write_and_flush(out, b"\x1b[H\x1b[2J") } @@ -655,6 +664,7 @@ fn reverse_incremental_search(chars: &mut io::Chars, Ok(Some(key)) } +#[cfg(unix)] fn escape_sequence(chars: &mut io::Chars) -> Result { // Read the next two bytes representing the escape sequence. let seq1 = try!(chars.next().unwrap()); @@ -869,6 +879,7 @@ fn readline_edit(prompt: &str, try!(edit_yank(&mut s, text)) } } + #[cfg(unix)] KeyPress::CTRL_Z => { try!(disable_raw_mode(original_termios)); try!(signal::raise(signal::SIGSTOP)); @@ -1075,8 +1086,10 @@ impl<'completer> fmt::Debug for Editor<'completer> { } } +#[cfg(unix)] static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT; static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; +#[cfg(unix)] fn install_sigwinch_handler() { SIGWINCH_ONCE.call_once(|| unsafe { let sigwinch = signal::SigAction::new(signal::SigHandler::Handler(sigwinch_handler), @@ -1085,6 +1098,7 @@ fn install_sigwinch_handler() { let _ = signal::sigaction(signal::SIGWINCH, &sigwinch); }); } +#[cfg(unix)] extern "C" fn sigwinch_handler(_: signal::SigNum) { SIGWINCH.store(true, atomic::Ordering::SeqCst); } From 04ceba60e4094af6cd5ff8c0a72701e958632757 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 07:50:09 +0200 Subject: [PATCH 0039/1201] Add FIXME --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a50a4899fd..1539c26b0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -742,7 +742,7 @@ fn readline_edit(prompt: &str, kill_ring.reset(); let mut s = State::new(&mut stdout, prompt, MAX_LINE, get_columns(), history.len()); - let stdin = io::stdin(); + let stdin = io::stdin(); // FIXME: ReadConsoleInputW on windows platform let mut chars = stdin.lock().chars(); loop { let c = chars.next().unwrap(); From b975ea65e6882ecf9a386b963398d04930f782b5 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 08:07:52 +0200 Subject: [PATCH 0040/1201] Introduce type alias for termios::Termios --- src/lib.rs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a50a4899fd..dc6ecd80e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -195,9 +195,12 @@ fn from_errno(errno: Errno) -> error::ReadlineError { error::ReadlineError::from(nix::Error::from_errno(errno)) } +#[cfg(unix)] +type Mode = termios::Termios; + /// Enable raw mode for the TERM #[cfg(unix)] -fn enable_raw_mode() -> Result { +fn enable_raw_mode() -> Result { use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, /*OPOST, */VMIN, VTIME}; if !is_a_tty(libc::STDIN_FILENO) { @@ -218,8 +221,8 @@ fn enable_raw_mode() -> Result { /// Disable Raw mode for the term #[cfg(unix)] -fn disable_raw_mode(original_termios: termios::Termios) -> Result<()> { - try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &original_termios)); +fn disable_raw_mode(original_mode: Mode) -> Result<()> { + try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &original_mode)); Ok(()) } @@ -735,7 +738,7 @@ fn readline_edit(prompt: &str, history: &mut History, completer: Option<&Completer>, kill_ring: &mut KillRing, - original_termios: termios::Termios) + original_mode: Mode) -> Result { let mut stdout = io::stdout(); try!(write_and_flush(&mut stdout, prompt.as_bytes())); @@ -881,9 +884,9 @@ fn readline_edit(prompt: &str, } #[cfg(unix)] KeyPress::CTRL_Z => { - try!(disable_raw_mode(original_termios)); + try!(disable_raw_mode(original_mode)); try!(signal::raise(signal::SIGSTOP)); - try!(enable_raw_mode()); // TODO original_termios may have changed + try!(enable_raw_mode()); // TODO original_mode may have changed try!(s.refresh_line()) } // TODO CTRL-_ // undo @@ -957,13 +960,13 @@ fn readline_edit(prompt: &str, Ok(s.line.into_string()) } -struct Guard(termios::Termios); +struct Guard(Mode); #[allow(unused_must_use)] impl Drop for Guard { fn drop(&mut self) { - let Guard(termios) = *self; - disable_raw_mode(termios); + let Guard(mode) = *self; + disable_raw_mode(mode); } } @@ -974,10 +977,10 @@ fn readline_raw(prompt: &str, completer: Option<&Completer>, kill_ring: &mut KillRing) -> Result { - let original_termios = try!(enable_raw_mode()); - let guard = Guard(original_termios); - let user_input = readline_edit(prompt, history, completer, kill_ring, original_termios); - drop(guard); // try!(disable_raw_mode(original_termios)); + let original_mode = try!(enable_raw_mode()); + let guard = Guard(original_mode); + let user_input = readline_edit(prompt, history, completer, kill_ring, original_mode); + drop(guard); // try!(disable_raw_mode(original_mode)); println!(""); user_input } From 7107347b986e63427a68ddc80cf8a78bffacba19 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 08:54:55 +0200 Subject: [PATCH 0041/1201] Use Stdin directly only on unix --- src/lib.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 14c35d792f..e2eb47a2da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -202,7 +202,7 @@ type Mode = termios::Termios; #[cfg(unix)] fn enable_raw_mode() -> Result { use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, - /*OPOST, */VMIN, VTIME}; + /* OPOST, */ VMIN, VTIME}; if !is_a_tty(libc::STDIN_FILENO) { return Err(from_errno(Errno::ENOTTY)); } @@ -745,8 +745,20 @@ fn readline_edit(prompt: &str, kill_ring.reset(); let mut s = State::new(&mut stdout, prompt, MAX_LINE, get_columns(), history.len()); - let stdin = io::stdin(); // FIXME: ReadConsoleInputW on windows platform - let mut chars = stdin.lock().chars(); + + let stdin = if cfg!(target_os = "unix") { + io::stdin() + } else { + // FIXME: ReadConsoleInputW on windows platform + unimplemented!() + }; + let stdin_lock = if cfg!(target_os = "unix") { + stdin.lock() + } else { + unimplemented!() + }; + let mut chars = stdin_lock.chars(); + loop { let c = chars.next().unwrap(); if c.is_err() && SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { From 3d4c8334a9dd35f935b7dfd62bdd79c5f7ebbe52 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 09:29:56 +0200 Subject: [PATCH 0042/1201] Remove from_errno function --- src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e2eb47a2da..43fc664625 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,11 +190,6 @@ fn is_unsupported_term() -> bool { } } -#[cfg(unix)] -fn from_errno(errno: Errno) -> error::ReadlineError { - error::ReadlineError::from(nix::Error::from_errno(errno)) -} - #[cfg(unix)] type Mode = termios::Termios; @@ -204,7 +199,7 @@ fn enable_raw_mode() -> Result { use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, /* OPOST, */ VMIN, VTIME}; if !is_a_tty(libc::STDIN_FILENO) { - return Err(from_errno(Errno::ENOTTY)); + try!(Err(nix::Error::from_errno(Errno::ENOTTY))); } let original_term = try!(termios::tcgetattr(libc::STDIN_FILENO)); let mut raw = original_term; From ecf3c16486a6599227931debcc654acbf59bff7d Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 09:56:22 +0200 Subject: [PATCH 0043/1201] Introduce method stubs for windows plarform --- Cargo.toml | 3 +++ src/lib.rs | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c45da2a5f1..1ce6b51758 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,5 +16,8 @@ unicode-width = "0.1.3" [target.'cfg(unix)'.dependencies] nix = "0.5.0" +[target.'cfg(windows)'.dependencies] +winapi = "0.2" + [dev-dependencies] tempdir = "0.3.4" diff --git a/src/lib.rs b/src/lib.rs index 43fc664625..a921397b0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,8 @@ extern crate libc; #[cfg(unix)] extern crate nix; extern crate unicode_width; +#[cfg(windows)] +extern crate winapi; pub mod completion; #[allow(non_camel_case_types)] @@ -38,11 +40,11 @@ use std::result; use std::sync; use std::sync::atomic; #[cfg(unix)] -use nix::errno::Errno; -#[cfg(unix)] use nix::sys::signal; #[cfg(unix)] use nix::sys::termios; +#[cfg(windows)] +use winapi; use completion::Completer; use consts::{KeyPress, char_to_key_press}; @@ -151,6 +153,11 @@ impl<'out, 'prompt> State<'out, 'prompt> { write_and_flush(self.out, ab.as_bytes()) } + + #[cfg(windows)] + fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> { + unimplemented!() + } } impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> { @@ -192,14 +199,17 @@ fn is_unsupported_term() -> bool { #[cfg(unix)] type Mode = termios::Termios; +#[cfg(windows)] +type Mode = winapi::minwindef::DWORD; /// Enable raw mode for the TERM #[cfg(unix)] fn enable_raw_mode() -> Result { + use nix::errno::Errno::ENOTTY; use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, /* OPOST, */ VMIN, VTIME}; if !is_a_tty(libc::STDIN_FILENO) { - try!(Err(nix::Error::from_errno(Errno::ENOTTY))); + try!(Err(nix::Error::from_errno(ENOTTY))); } let original_term = try!(termios::tcgetattr(libc::STDIN_FILENO)); let mut raw = original_term; @@ -213,6 +223,10 @@ fn enable_raw_mode() -> Result { try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &raw)); Ok(original_term) } +#[cfg(windows)] +fn enable_raw_mode() -> Result { + unimplemented!() +} /// Disable Raw mode for the term #[cfg(unix)] @@ -220,6 +234,10 @@ fn disable_raw_mode(original_mode: Mode) -> Result<()> { try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &original_mode)); Ok(()) } +#[cfg(windows)] +fn disable_raw_mode(original_mode: Mode) -> Result<()> { + unimplemented!() +} #[cfg(any(target_os = "macos", target_os = "freebsd"))] const TIOCGWINSZ: libc::c_ulong = 0x40087468; @@ -254,6 +272,10 @@ fn get_columns() -> usize { } } } +#[cfg(windows)] +fn get_columns() -> usize { + unimplemented!() +} fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> { try!(w.write_all(buf)); @@ -266,6 +288,10 @@ fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> { fn clear_screen(out: &mut Write) -> Result<()> { write_and_flush(out, b"\x1b[H\x1b[2J") } +#[cfg(windows)] +fn clear_screen(out: &mut Write) -> Result<()> { + unimplemented!() +} /// Beep, used for completion when there is nothing to complete or when all /// the choices were already shown. @@ -724,6 +750,10 @@ fn escape_sequence(chars: &mut io::Chars) -> Result { } } } +#[cfg(windows)] +fn escape_sequence(chars: &mut io::Chars) -> Result { + unimplemented!() +} /// Handles reading and editting the readline buffer. /// It will also handle special inputs in an appropriate fashion @@ -1112,6 +1142,10 @@ fn install_sigwinch_handler() { extern "C" fn sigwinch_handler(_: signal::SigNum) { SIGWINCH.store(true, atomic::Ordering::SeqCst); } +#[cfg(windows)] +fn install_sigwinch_handler() { + // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT +} #[cfg(test)] mod test { From 358ade0382ad094d6e91ba4df36758378b806550 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 10:14:29 +0200 Subject: [PATCH 0044/1201] Introduce missing constants on windows --- src/lib.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a921397b0d..9c8c55088b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,8 +43,6 @@ use std::sync::atomic; use nix::sys::signal; #[cfg(unix)] use nix::sys::termios; -#[cfg(windows)] -use winapi; use completion::Completer; use consts::{KeyPress, char_to_key_press}; @@ -199,8 +197,16 @@ fn is_unsupported_term() -> bool { #[cfg(unix)] type Mode = termios::Termios; +#[cfg(unix)] +const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; +#[cfg(unix)] +const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; #[cfg(windows)] type Mode = winapi::minwindef::DWORD; +#[cfg(windows)] +const STDIN_FILENO: libc::c_int = 0; +#[cfg(windows)] +const STDOUT_FILENO: libc::c_int = 1; /// Enable raw mode for the TERM #[cfg(unix)] @@ -208,10 +214,10 @@ fn enable_raw_mode() -> Result { use nix::errno::Errno::ENOTTY; use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, /* OPOST, */ VMIN, VTIME}; - if !is_a_tty(libc::STDIN_FILENO) { + if !is_a_tty(STDIN_FILENO) { try!(Err(nix::Error::from_errno(ENOTTY))); } - let original_term = try!(termios::tcgetattr(libc::STDIN_FILENO)); + let original_term = try!(termios::tcgetattr(STDIN_FILENO)); let mut raw = original_term; raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); // disable BREAK interrupt, CR to NL conversion on input, input parity check, strip high bit (bit 8), output flow control // we don't want raw output, it turns newlines into straight linefeeds @@ -220,7 +226,7 @@ fn enable_raw_mode() -> Result { raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); // disable echoing, canonical mode, extended input processing and signals raw.c_cc[VMIN] = 1; // One character-at-a-time input raw.c_cc[VTIME] = 0; // with blocking read - try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &raw)); + try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &raw)); Ok(original_term) } #[cfg(windows)] @@ -231,7 +237,7 @@ fn enable_raw_mode() -> Result { /// Disable Raw mode for the term #[cfg(unix)] fn disable_raw_mode(original_mode: Mode) -> Result<()> { - try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &original_mode)); + try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &original_mode)); Ok(()) } #[cfg(windows)] @@ -266,7 +272,7 @@ fn get_columns() -> usize { } let mut size: winsize = zeroed(); - match libc::ioctl(libc::STDOUT_FILENO, TIOCGWINSZ, &mut size) { + match libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size) { 0 => size.ws_col as usize, // TODO getCursorPosition _ => 80, } @@ -1048,8 +1054,8 @@ impl<'completer> Editor<'completer> { // if the number of columns is stored here, we need a SIGWINCH handler... let editor = Editor { unsupported_term: is_unsupported_term(), - stdin_isatty: is_a_tty(libc::STDIN_FILENO), - stdout_isatty: is_a_tty(libc::STDOUT_FILENO), + stdin_isatty: is_a_tty(STDIN_FILENO), + stdout_isatty: is_a_tty(STDOUT_FILENO), history: History::new(), completer: None, kill_ring: KillRing::new(60), From 91fa95098579cb499da01eecf2410c44bd3e798a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 10:43:13 +0200 Subject: [PATCH 0045/1201] Fix is_a_tty a escape_sequence on windows --- Cargo.toml | 1 + src/lib.rs | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1ce6b51758..9bd42837ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ nix = "0.5.0" [target.'cfg(windows)'.dependencies] winapi = "0.2" +kernel32-sys = "0.2" [dev-dependencies] tempdir = "0.3.4" diff --git a/src/lib.rs b/src/lib.rs index 9c8c55088b..2da86865eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,8 @@ extern crate nix; extern crate unicode_width; #[cfg(windows)] extern crate winapi; +#[cfg(windows)] +extern crate kernel32; pub mod completion; #[allow(non_camel_case_types)] @@ -37,6 +39,7 @@ use std::io::{self, Read, Write}; use std::mem; use std::path::Path; use std::result; +#[cfg(unix)] use std::sync; use std::sync::atomic; #[cfg(unix)] @@ -176,9 +179,22 @@ impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> { static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"]; /// Check to see if `fd` is a TTY +#[cfg(unix)] fn is_a_tty(fd: libc::c_int) -> bool { unsafe { libc::isatty(fd) != 0 } } +#[cfg(windows)] +fn is_a_tty(fd: libc::c_int) -> bool { + use libc::get_osfhandle; + use kernel32::GetConsoleMode; + use winapi::winnt::HANDLE; + let mut out = 0; + // If this function doesn't fail then fd is a TTY + match unsafe { GetConsoleMode(get_osfhandle(fd) as HANDLE, &mut out) } { + 0 => false, + _ => true, + } +} /// Check to see if the current `TERM` is unsupported fn is_unsupported_term() -> bool { @@ -758,7 +774,7 @@ fn escape_sequence(chars: &mut io::Chars) -> Result { } #[cfg(windows)] fn escape_sequence(chars: &mut io::Chars) -> Result { - unimplemented!() + Ok(KeyPress::UNKNOWN_ESC_SEQ) } /// Handles reading and editting the readline buffer. From 264b8f58fafc529191253a777e3c75ae00c3d3e6 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 11:22:37 +0200 Subject: [PATCH 0046/1201] Fix enable_raw_mode on windows --- appveyor.yml | 16 ++++++++++++++++ src/lib.rs | 34 +++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..53bfcf076b --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,16 @@ +environment: + TARGET: nightly-x86_64-pc-windows-gnu +install: + - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" -FileName "rust-install.exe" + - ps: .\rust-install.exe /VERYSILENT /NORESTART /DIR="C:\rust" | Out-Null + - ps: $env:PATH="$env:PATH;C:\rust\bin" + - rustc -V + - cargo -V + +build: false + +test_script: + - cargo test --lib --verbose + +cache: + - C:\Users\appveyor\.cargo diff --git a/src/lib.rs b/src/lib.rs index 2da86865eb..5b0dede6bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,15 +185,9 @@ fn is_a_tty(fd: libc::c_int) -> bool { } #[cfg(windows)] fn is_a_tty(fd: libc::c_int) -> bool { - use libc::get_osfhandle; - use kernel32::GetConsoleMode; - use winapi::winnt::HANDLE; - let mut out = 0; + let handle = unsafe { libc::get_osfhandle(fd) }; // If this function doesn't fail then fd is a TTY - match unsafe { GetConsoleMode(get_osfhandle(fd) as HANDLE, &mut out) } { - 0 => false, - _ => true, - } + get_console_mode(handle).is_ok() } /// Check to see if the current `TERM` is unsupported @@ -223,6 +217,14 @@ type Mode = winapi::minwindef::DWORD; const STDIN_FILENO: libc::c_int = 0; #[cfg(windows)] const STDOUT_FILENO: libc::c_int = 1; +#[cfg(windows)] +macro_rules! check { + ($funcall:expr) => ( + if unsafe { $funcall } == 0 { + try!(Err(io::Error::last_os_error())); + } + ); +} /// Enable raw mode for the TERM #[cfg(unix)] @@ -247,7 +249,21 @@ fn enable_raw_mode() -> Result { } #[cfg(windows)] fn enable_raw_mode() -> Result { - unimplemented!() + let handle = unsafe { libc::get_osfhandle(fd) }; + let original_mode = try!(get_console_mode(handle)); + let raw = original_mode & + !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT | + winapi::wincon::ENABLE_PROCESSED_INPUT); + check!(kernel32::SetConsoleMode(handle as HANDLE, raw)); + Ok(original_mode) +} +#[cfg(windows)] +fn get_console_mode(handle: libc::intptr_t) -> Result { + use winapi::winnt::HANDLE; + + let mut original_mode = 0; + check!(kernel32::GetConsoleMode(handle as HANDLE, &mut original_mode)); + Ok(original_mode) } /// Disable Raw mode for the term From ace5799eeb855ca2008a936763349c3682d685dd Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 11:26:17 +0200 Subject: [PATCH 0047/1201] Fix enable_raw_mode on windows --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 5b0dede6bd..2637e4e1b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -249,7 +249,8 @@ fn enable_raw_mode() -> Result { } #[cfg(windows)] fn enable_raw_mode() -> Result { - let handle = unsafe { libc::get_osfhandle(fd) }; + use winapi::winnt::HANDLE; + let handle = unsafe { libc::get_osfhandle(STDOUT_FILENO) }; let original_mode = try!(get_console_mode(handle)); let raw = original_mode & !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT | From fca66be99d1a49e3d4d37af77bb4a8e5d700e1e0 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 11:45:08 +0200 Subject: [PATCH 0048/1201] Fix disable_raw_mode on windows --- src/lib.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2637e4e1b1..6d4ce0853a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,8 +184,8 @@ fn is_a_tty(fd: libc::c_int) -> bool { unsafe { libc::isatty(fd) != 0 } } #[cfg(windows)] -fn is_a_tty(fd: libc::c_int) -> bool { - let handle = unsafe { libc::get_osfhandle(fd) }; +fn is_a_tty(fd: winapi::DWORD) -> bool { + let handle = unsafe { kernel32::GetStdHandle(fd) }; // If this function doesn't fail then fd is a TTY get_console_mode(handle).is_ok() } @@ -212,11 +212,11 @@ const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; #[cfg(unix)] const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; #[cfg(windows)] -type Mode = winapi::minwindef::DWORD; +type Mode = winapi::DWORD; #[cfg(windows)] -const STDIN_FILENO: libc::c_int = 0; +const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; #[cfg(windows)] -const STDOUT_FILENO: libc::c_int = 1; +const STDOUT_FILENO: winapi::DWORD = winapi::STD_OUTPUT_HANDLE; #[cfg(windows)] macro_rules! check { ($funcall:expr) => ( @@ -249,21 +249,18 @@ fn enable_raw_mode() -> Result { } #[cfg(windows)] fn enable_raw_mode() -> Result { - use winapi::winnt::HANDLE; - let handle = unsafe { libc::get_osfhandle(STDOUT_FILENO) }; + let handle = unsafe { kernel32::GetStdHandle(STDIN_FILENO) }; let original_mode = try!(get_console_mode(handle)); let raw = original_mode & !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT | winapi::wincon::ENABLE_PROCESSED_INPUT); - check!(kernel32::SetConsoleMode(handle as HANDLE, raw)); + check!(kernel32::SetConsoleMode(handle, raw)); Ok(original_mode) } #[cfg(windows)] -fn get_console_mode(handle: libc::intptr_t) -> Result { - use winapi::winnt::HANDLE; - +fn get_console_mode(handle: winapi::HANDLE) -> Result { let mut original_mode = 0; - check!(kernel32::GetConsoleMode(handle as HANDLE, &mut original_mode)); + check!(kernel32::GetConsoleMode(handle, &mut original_mode)); Ok(original_mode) } @@ -275,7 +272,9 @@ fn disable_raw_mode(original_mode: Mode) -> Result<()> { } #[cfg(windows)] fn disable_raw_mode(original_mode: Mode) -> Result<()> { - unimplemented!() + let handle = unsafe { kernel32::GetStdHandle(STDIN_FILENO) }; + check!(kernel32::SetConsoleMode(handle, original_mode)); + Ok(()) } #[cfg(any(target_os = "macos", target_os = "freebsd"))] From f81dac1dcfa490cfe87719fc85b2219501c01cb7 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 12:05:04 +0200 Subject: [PATCH 0049/1201] Fix get_columns on windows --- src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 6d4ce0853a..48ecf6c167 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -312,7 +312,12 @@ fn get_columns() -> usize { } #[cfg(windows)] fn get_columns() -> usize { - unimplemented!() + let handle = unsafe { kernel32::GetStdHandle(STDOUT_FILENO) }; + let mut info = unsafe { mem::zeroed() }; + match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { + 0 => 80, + _ => info.dwSize.X, + } } fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> { From 230339886209fe4ce838850748b8a955df0d05a8 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 12:06:55 +0200 Subject: [PATCH 0050/1201] Fix get_columns on windows --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 48ecf6c167..a93edaa3bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -316,7 +316,7 @@ fn get_columns() -> usize { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { 0 => 80, - _ => info.dwSize.X, + _ => info.dwSize.X as usize, } } From 2a11d1421c9ef9f175b646cd1c5d0ce30e93044d Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 12:25:59 +0200 Subject: [PATCH 0051/1201] Fix clear_screen on windows --- src/lib.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a93edaa3bf..ae572d1368 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -332,8 +332,19 @@ fn clear_screen(out: &mut Write) -> Result<()> { write_and_flush(out, b"\x1b[H\x1b[2J") } #[cfg(windows)] -fn clear_screen(out: &mut Write) -> Result<()> { - unimplemented!() +fn clear_screen(_: &mut Write) -> Result<()> { + let handle = unsafe { kernel32::GetStdHandle(STDOUT_FILENO) }; + let mut info = unsafe { mem::zeroed() }; + check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); + let coord = winapi::COORD { X: 0, Y: 0 }; + check!(kernel32::SetConsoleCursorPosition(handle, coord)); + let mut _count = 0; + check!(kernel32::FillConsoleOutputCharacterA(handle, + ' ', + info.dwSize.X * info.dwSize.Y, + coord, + &mut _count)); + Ok(()) } /// Beep, used for completion when there is nothing to complete or when all From 15d85d1928903ba9a0fc229577264005b16e2bac Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 12:32:58 +0200 Subject: [PATCH 0052/1201] Fix clear_screen on windows --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ae572d1368..8dcdadde33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -340,8 +340,8 @@ fn clear_screen(_: &mut Write) -> Result<()> { check!(kernel32::SetConsoleCursorPosition(handle, coord)); let mut _count = 0; check!(kernel32::FillConsoleOutputCharacterA(handle, - ' ', - info.dwSize.X * info.dwSize.Y, + ' ' as winapi::CHAR, + (info.dwSize.X * info.dwSize.Y) as winapi::DWORD, coord, &mut _count)); Ok(()) From c80d69d8b0b9b60af3a45c5c4d3398ec04b9b03f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 13:38:51 +0200 Subject: [PATCH 0053/1201] Introduce get_std_handle on windows --- src/lib.rs | 51 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8dcdadde33..09faee10bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,9 +185,14 @@ fn is_a_tty(fd: libc::c_int) -> bool { } #[cfg(windows)] fn is_a_tty(fd: winapi::DWORD) -> bool { - let handle = unsafe { kernel32::GetStdHandle(fd) }; - // If this function doesn't fail then fd is a TTY - get_console_mode(handle).is_ok() + let handle = get_std_handle(fd); + match handle { + Ok(handle) => { + // If this function doesn't fail then fd is a TTY + get_console_mode(handle).is_ok() + } + Err(_) => false, + } } /// Check to see if the current `TERM` is unsupported @@ -225,6 +230,17 @@ macro_rules! check { } ); } +#[cfg(windows)] +fn get_std_handle(fd: winapi::DWORD) -> Result { + let handle = unsafe { kernel32::GetStdHandle(fd) }; + if handle == winapi::INVALID_HANDLE_VALUE { + try!(Err(io::Error::last_os_error())); + } else if handle.is_null() { + try!(Err(io::Error::new(io::ErrorKind::Other, + "no stdio handle available for this process"))); + } + Ok(handle) +} /// Enable raw mode for the TERM #[cfg(unix)] @@ -249,7 +265,7 @@ fn enable_raw_mode() -> Result { } #[cfg(windows)] fn enable_raw_mode() -> Result { - let handle = unsafe { kernel32::GetStdHandle(STDIN_FILENO) }; + let handle = try!(get_std_handle(STDIN_FILENO)); let original_mode = try!(get_console_mode(handle)); let raw = original_mode & !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT | @@ -272,7 +288,7 @@ fn disable_raw_mode(original_mode: Mode) -> Result<()> { } #[cfg(windows)] fn disable_raw_mode(original_mode: Mode) -> Result<()> { - let handle = unsafe { kernel32::GetStdHandle(STDIN_FILENO) }; + let handle = try!(get_std_handle(STDIN_FILENO)); check!(kernel32::SetConsoleMode(handle, original_mode)); Ok(()) } @@ -312,7 +328,11 @@ fn get_columns() -> usize { } #[cfg(windows)] fn get_columns() -> usize { - let handle = unsafe { kernel32::GetStdHandle(STDOUT_FILENO) }; + let handle = get_std_handle(STDOUT_FILENO); + if handle.is_err() { + return 80; + } + let handle = handle.unwrap(); let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { 0 => 80, @@ -333,7 +353,7 @@ fn clear_screen(out: &mut Write) -> Result<()> { } #[cfg(windows)] fn clear_screen(_: &mut Write) -> Result<()> { - let handle = unsafe { kernel32::GetStdHandle(STDOUT_FILENO) }; + let handle = try!(get_std_handle(STDOUT_FILENO)); let mut info = unsafe { mem::zeroed() }; check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); let coord = winapi::COORD { X: 0, Y: 0 }; @@ -809,6 +829,16 @@ fn escape_sequence(chars: &mut io::Chars) -> Result { Ok(KeyPress::UNKNOWN_ESC_SEQ) } +#[cfg(windows)] +struct InputBuffer(winapi::HANDLE); + +#[cfg(windows)] +impl Read for InputBuffer { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + unimplemented!() + } +} + /// Handles reading and editting the readline buffer. /// It will also handle special inputs in an appropriate fashion /// (e.g., C-c will exit readline) @@ -831,12 +861,7 @@ fn readline_edit(prompt: &str, // FIXME: ReadConsoleInputW on windows platform unimplemented!() }; - let stdin_lock = if cfg!(target_os = "unix") { - stdin.lock() - } else { - unimplemented!() - }; - let mut chars = stdin_lock.chars(); + let mut chars = stdin.chars(); // TODO stdin.lock() ??? loop { let c = chars.next().unwrap(); From 200afea84b0399bfda3a15a6030b143f3939a72c Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 13:50:40 +0200 Subject: [PATCH 0054/1201] Introduce InputBuffer on windows --- src/lib.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 09faee10bf..c29a61a3a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -829,16 +829,27 @@ fn escape_sequence(chars: &mut io::Chars) -> Result { Ok(KeyPress::UNKNOWN_ESC_SEQ) } +#[cfg(unix)] +fn stdin() -> Result { + Ok(io::stdin()) +} #[cfg(windows)] -struct InputBuffer(winapi::HANDLE); +fn stdin() -> Result { + let handle = try!(get_std_handle(STDIN_FILENO)); + Ok(InputBuffer(handle)) +} +#[cfg(windows)] +struct InputBuffer(winapi::HANDLE); #[cfg(windows)] impl Read for InputBuffer { fn read(&mut self, buf: &mut [u8]) -> io::Result { + // FIXME: ReadConsoleInputW on windows platform unimplemented!() } } + /// Handles reading and editting the readline buffer. /// It will also handle special inputs in an appropriate fashion /// (e.g., C-c will exit readline) @@ -855,12 +866,7 @@ fn readline_edit(prompt: &str, kill_ring.reset(); let mut s = State::new(&mut stdout, prompt, MAX_LINE, get_columns(), history.len()); - let stdin = if cfg!(target_os = "unix") { - io::stdin() - } else { - // FIXME: ReadConsoleInputW on windows platform - unimplemented!() - }; + let stdin = try!(stdin()); let mut chars = stdin.chars(); // TODO stdin.lock() ??? loop { From 213eff2c33f765eff4e972efbf8b058fbec32c96 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 14:54:27 +0200 Subject: [PATCH 0055/1201] Introduce State.output_handle field on windows. It seems that we need both (out and output_handle) --- src/lib.rs | 90 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c29a61a3a5..95ed4ac593 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,6 +66,8 @@ struct State<'out, 'prompt> { cols: usize, // Number of columns in terminal history_index: usize, // The history index we are currently editing. snapshot: LineBuffer, // Current edited line before history browsing/completion + #[cfg(windows)] + output_handle: winapi::HANDLE, } #[derive(Copy, Clone, Debug, Default)] @@ -75,14 +77,15 @@ struct Position { } impl<'out, 'prompt> State<'out, 'prompt> { + #[cfg(unix)] fn new(out: &'out mut Write, prompt: &'prompt str, - capacity: usize, - cols: usize, history_index: usize) - -> State<'out, 'prompt> { + -> Result> { + let capacity = MAX_LINE; + let cols = get_columns(); let prompt_size = calculate_position(prompt, Default::default(), cols); - State { + let state = State { out: out, prompt: prompt, prompt_size: prompt_size, @@ -91,7 +94,30 @@ impl<'out, 'prompt> State<'out, 'prompt> { cols: cols, history_index: history_index, snapshot: LineBuffer::with_capacity(capacity), - } + }; + Ok(state) + } + #[cfg(windows)] + fn new(out: &'out mut Write, + prompt: &'prompt str, + history_index: usize) + -> Result> { + let handle = try!(get_std_handle(STDOUT_FILENO)); + let capacity = MAX_LINE; + let cols = get_columns(handle); + let prompt_size = calculate_position(prompt, Default::default(), cols); + let state = State { + out: out, + prompt: prompt, + prompt_size: prompt_size, + line: LineBuffer::with_capacity(capacity), + cursor: prompt_size, + cols: cols, + history_index: history_index, + snapshot: LineBuffer::with_capacity(capacity), + output_handle: handle, + }; + Ok(state) } fn snapshot(&mut self) { @@ -159,6 +185,16 @@ impl<'out, 'prompt> State<'out, 'prompt> { fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> { unimplemented!() } + + #[cfg(unix)] + fn update_columns(&mut self) { + self.cols = get_columns(); + } + + #[cfg(windows)] + fn update_columns(&mut self) { + self.cols = get_columns(self.output_handle); + } } impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> { @@ -327,12 +363,7 @@ fn get_columns() -> usize { } } #[cfg(windows)] -fn get_columns() -> usize { - let handle = get_std_handle(STDOUT_FILENO); - if handle.is_err() { - return 80; - } - let handle = handle.unwrap(); +fn get_columns(handle: winapi::HANDLE) -> usize { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { 0 => 80, @@ -348,12 +379,12 @@ fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> { /// Clear the screen. Used to handle ctrl+l #[cfg(unix)] -fn clear_screen(out: &mut Write) -> Result<()> { - write_and_flush(out, b"\x1b[H\x1b[2J") +fn clear_screen(s: &mut State) -> Result<()> { + write_and_flush(s.out, b"\x1b[H\x1b[2J") } #[cfg(windows)] -fn clear_screen(_: &mut Write) -> Result<()> { - let handle = try!(get_std_handle(STDOUT_FILENO)); +fn clear_screen(s: &mut State) -> Result<()> { + let handle = s.output_handle; let mut info = unsafe { mem::zeroed() }; check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); let coord = winapi::COORD { X: 0, Y: 0 }; @@ -849,7 +880,6 @@ impl Read for InputBuffer { } } - /// Handles reading and editting the readline buffer. /// It will also handle special inputs in an appropriate fashion /// (e.g., C-c will exit readline) @@ -861,10 +891,10 @@ fn readline_edit(prompt: &str, original_mode: Mode) -> Result { let mut stdout = io::stdout(); - try!(write_and_flush(&mut stdout, prompt.as_bytes())); kill_ring.reset(); - let mut s = State::new(&mut stdout, prompt, MAX_LINE, get_columns(), history.len()); + let mut s = try!(State::new(&mut stdout, prompt, history.len())); + try!(s.refresh_line()); let stdin = try!(stdin()); let mut chars = stdin.chars(); // TODO stdin.lock() ??? @@ -872,7 +902,7 @@ fn readline_edit(prompt: &str, loop { let c = chars.next().unwrap(); if c.is_err() && SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { - s.cols = get_columns(); + s.update_columns(); try!(s.refresh_line()); continue; } @@ -961,7 +991,7 @@ fn readline_edit(prompt: &str, } KeyPress::CTRL_L => { // Clear the screen leaving the current line at the top of the screen. - try!(clear_screen(s.out)); + try!(clear_screen(&mut s)); try!(s.refresh_line()) } KeyPress::CTRL_N => { @@ -1241,11 +1271,30 @@ mod test { use State; use super::Result; + #[cfg(unix)] + fn init_state<'out>(out: &'out mut Write, + line: &str, + pos: usize, + cols: usize) + -> State<'out, 'static> { + State { + out: out, + prompt: "", + prompt_size: Default::default(), + line: LineBuffer::init(line, pos), + cursor: Default::default(), + cols: cols, + history_index: 0, + snapshot: LineBuffer::with_capacity(100), + } + } + #[cfg(windows)] fn init_state<'out>(out: &'out mut Write, line: &str, pos: usize, cols: usize) -> State<'out, 'static> { + use std::ptr; State { out: out, prompt: "", @@ -1255,6 +1304,7 @@ mod test { cols: cols, history_index: 0, snapshot: LineBuffer::with_capacity(100), + output_handle: ptr::null_mut(), } } From 27295c462028f9babc9699f3b4084928e0e536e9 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 16:25:07 +0200 Subject: [PATCH 0056/1201] Start implementing refresh on windows. --- src/lib.rs | 118 +++++++++++++++++++++++------------------------------ 1 file changed, 51 insertions(+), 67 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 95ed4ac593..3ca1aec61b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,19 @@ use kill_ring::KillRing; /// The error type for I/O and Linux Syscalls (Errno) pub type Result = result::Result; +#[cfg(unix)] +type Handle = (); +#[cfg(windows)] +type Handle = winapi::HANDLE; +#[cfg(windows)] +macro_rules! check { + ($funcall:expr) => ( + if unsafe { $funcall } == 0 { + try!(Err(io::Error::last_os_error())); + } + ); +} + // Represent the state during line editing. struct State<'out, 'prompt> { out: &'out mut Write, @@ -66,8 +79,7 @@ struct State<'out, 'prompt> { cols: usize, // Number of columns in terminal history_index: usize, // The history index we are currently editing. snapshot: LineBuffer, // Current edited line before history browsing/completion - #[cfg(windows)] - output_handle: winapi::HANDLE, + output_handle: Handle, // output handle (for windows) } #[derive(Copy, Clone, Debug, Default)] @@ -77,36 +89,15 @@ struct Position { } impl<'out, 'prompt> State<'out, 'prompt> { - #[cfg(unix)] - fn new(out: &'out mut Write, - prompt: &'prompt str, - history_index: usize) - -> Result> { - let capacity = MAX_LINE; - let cols = get_columns(); - let prompt_size = calculate_position(prompt, Default::default(), cols); - let state = State { - out: out, - prompt: prompt, - prompt_size: prompt_size, - line: LineBuffer::with_capacity(capacity), - cursor: prompt_size, - cols: cols, - history_index: history_index, - snapshot: LineBuffer::with_capacity(capacity), - }; - Ok(state) - } - #[cfg(windows)] fn new(out: &'out mut Write, + output_handle: Handle, prompt: &'prompt str, history_index: usize) - -> Result> { - let handle = try!(get_std_handle(STDOUT_FILENO)); + -> State<'out, 'prompt> { let capacity = MAX_LINE; - let cols = get_columns(handle); + let cols = get_columns(output_handle); let prompt_size = calculate_position(prompt, Default::default(), cols); - let state = State { + State { out: out, prompt: prompt, prompt_size: prompt_size, @@ -115,9 +106,8 @@ impl<'out, 'prompt> State<'out, 'prompt> { cols: cols, history_index: history_index, snapshot: LineBuffer::with_capacity(capacity), - output_handle: handle, - }; - Ok(state) + output_handle: output_handle, + } } fn snapshot(&mut self) { @@ -144,7 +134,9 @@ impl<'out, 'prompt> State<'out, 'prompt> { fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> { use std::fmt::Write; + // calculate the position of the end of the input line let end_pos = calculate_position(&self.line, prompt_size, self.cols); + // calculate the desired position of the cursor let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols); let mut ab = String::new(); @@ -183,15 +175,23 @@ impl<'out, 'prompt> State<'out, 'prompt> { #[cfg(windows)] fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> { - unimplemented!() - } + let handle = s.output_handle; + // calculate the position of the end of the input line + let end_pos = calculate_position(&self.line, prompt_size, self.cols); + // calculate the desired position of the cursor + let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols); - #[cfg(unix)] - fn update_columns(&mut self) { - self.cols = get_columns(); + // position at the end of the prompt, clear to end of previous input + let mut info = unsafe { mem::zeroed() }; + check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); + info.dwCursorPosition.X = self.prompt_size.col; + inf.dwCursorPosition.Y -= self.cursor.row - self.prompt_size.row; + check!(kernel32::SetConsoleCursorPosition(handle, coord)); + + self.cursor = cursor; + unimplemented!() } - #[cfg(windows)] fn update_columns(&mut self) { self.cols = get_columns(self.output_handle); } @@ -259,14 +259,6 @@ const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; #[cfg(windows)] const STDOUT_FILENO: winapi::DWORD = winapi::STD_OUTPUT_HANDLE; #[cfg(windows)] -macro_rules! check { - ($funcall:expr) => ( - if unsafe { $funcall } == 0 { - try!(Err(io::Error::last_os_error())); - } - ); -} -#[cfg(windows)] fn get_std_handle(fd: winapi::DWORD) -> Result { let handle = unsafe { kernel32::GetStdHandle(fd) }; if handle == winapi::INVALID_HANDLE_VALUE { @@ -341,7 +333,7 @@ const TIOCGWINSZ: libc::c_ulong = 0x5413; target_os = "android", target_os = "macos", target_os = "freebsd"))] -fn get_columns() -> usize { +fn get_columns(_: Handle) -> usize { use std::mem::zeroed; use libc::c_ushort; use libc; @@ -363,7 +355,7 @@ fn get_columns() -> usize { } } #[cfg(windows)] -fn get_columns(handle: winapi::HANDLE) -> usize { +fn get_columns(handle: Handle) -> usize { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { 0 => 80, @@ -870,6 +862,16 @@ fn stdin() -> Result { Ok(InputBuffer(handle)) } +#[cfg(unix)] +fn stdout_handle() -> Result { + Ok(()) +} +#[cfg(windows)] +fn stdout_handle() -> Result { + let handle = try!(get_std_handle(STDOUT_FILENO)); + Ok(handle) +} + #[cfg(windows)] struct InputBuffer(winapi::HANDLE); #[cfg(windows)] @@ -891,9 +893,10 @@ fn readline_edit(prompt: &str, original_mode: Mode) -> Result { let mut stdout = io::stdout(); + let stdout_handle = try!(stdout_handle()); kill_ring.reset(); - let mut s = try!(State::new(&mut stdout, prompt, history.len())); + let mut s = State::new(&mut stdout, stdout_handle, prompt, history.len()); try!(s.refresh_line()); let stdin = try!(stdin()); @@ -1271,30 +1274,11 @@ mod test { use State; use super::Result; - #[cfg(unix)] - fn init_state<'out>(out: &'out mut Write, - line: &str, - pos: usize, - cols: usize) - -> State<'out, 'static> { - State { - out: out, - prompt: "", - prompt_size: Default::default(), - line: LineBuffer::init(line, pos), - cursor: Default::default(), - cols: cols, - history_index: 0, - snapshot: LineBuffer::with_capacity(100), - } - } - #[cfg(windows)] fn init_state<'out>(out: &'out mut Write, line: &str, pos: usize, cols: usize) -> State<'out, 'static> { - use std::ptr; State { out: out, prompt: "", @@ -1304,7 +1288,7 @@ mod test { cols: cols, history_index: 0, snapshot: LineBuffer::with_capacity(100), - output_handle: ptr::null_mut(), + output_handle: Default::default(), } } From 7d3a26d4d8fd94ec346c04c76fc29fcc2f7e2b0e Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 16:42:22 +0200 Subject: [PATCH 0057/1201] Partially fix refresh on windows. --- src/lib.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3ca1aec61b..2a5fb6449d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -175,7 +175,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { #[cfg(windows)] fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> { - let handle = s.output_handle; + let handle = self.output_handle; // calculate the position of the end of the input line let end_pos = calculate_position(&self.line, prompt_size, self.cols); // calculate the desired position of the cursor @@ -184,9 +184,9 @@ impl<'out, 'prompt> State<'out, 'prompt> { // position at the end of the prompt, clear to end of previous input let mut info = unsafe { mem::zeroed() }; check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); - info.dwCursorPosition.X = self.prompt_size.col; - inf.dwCursorPosition.Y -= self.cursor.row - self.prompt_size.row; - check!(kernel32::SetConsoleCursorPosition(handle, coord)); + info.dwCursorPosition.X = self.prompt_size.col as i16; + info.dwCursorPosition.Y -= (self.cursor.row - self.prompt_size.row) as i16; + check!(kernel32::SetConsoleCursorPosition(handle, info.dwCursorPosition)); self.cursor = cursor; unimplemented!() @@ -1272,7 +1272,16 @@ mod test { use history::History; use completion::Completer; use State; - use super::Result; + use super::{Handle, Result}; + + #[cfg(unix)] + fn default_handle() -> Handle { + () + } + #[cfg(windows)] + fn default_handle() -> Handle { + ::std::ptr::null_mut() + } fn init_state<'out>(out: &'out mut Write, line: &str, @@ -1288,7 +1297,7 @@ mod test { cols: cols, history_index: 0, snapshot: LineBuffer::with_capacity(100), - output_handle: Default::default(), + output_handle: default_handle(), } } From d24c6c11b8e15355d59a3619af603f720eb58cd8 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 17:39:14 +0200 Subject: [PATCH 0058/1201] Fix refresh on windows --- src/lib.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2a5fb6449d..19fc18ae71 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,15 +181,33 @@ impl<'out, 'prompt> State<'out, 'prompt> { // calculate the desired position of the cursor let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols); - // position at the end of the prompt, clear to end of previous input + // position at the start of the prompt, clear to end of previous input let mut info = unsafe { mem::zeroed() }; check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); - info.dwCursorPosition.X = self.prompt_size.col as i16; - info.dwCursorPosition.Y -= (self.cursor.row - self.prompt_size.row) as i16; + info.dwCursorPosition.X = 0; + info.dwCursorPosition.Y -= self.cursor.row as i16; + check!(kernel32::SetConsoleCursorPosition(handle, info.dwCursorPosition)); + let mut _count = 0; + check!(kernel32::FillConsoleOutputCharacterA(handle, + ' ' as winapi::CHAR, + (info.dwSize.X * info.dwSize.Y) as winapi::DWORD, // FIXME + info.dwCursorPosition, + &mut _count)); + let mut ab = String::new(); + // display the prompt + ab.push_str(prompt); + // display the input line + ab.push_str(&self.line); + try!(write_and_flush(self.out, ab.as_bytes())); + + // position the cursor + check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); + info.dwCursorPosition.X = cursor.col as i16; + info.dwCursorPosition.Y -= (end_pos.row - cursor.row) as i16; check!(kernel32::SetConsoleCursorPosition(handle, info.dwCursorPosition)); self.cursor = cursor; - unimplemented!() + Ok(()) } fn update_columns(&mut self) { From ee3856d431e1147fc16929b0e5ac0123763b877c Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 10 Jul 2016 17:49:33 +0200 Subject: [PATCH 0059/1201] Fix test on windows --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 19fc18ae71..ac2fc2bf97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -176,6 +176,9 @@ impl<'out, 'prompt> State<'out, 'prompt> { #[cfg(windows)] fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> { let handle = self.output_handle; + if cfg!(test) && handle.is_null() { + return Ok(()); + } // calculate the position of the end of the input line let end_pos = calculate_position(&self.line, prompt_size, self.cols); // calculate the desired position of the cursor @@ -1299,6 +1302,7 @@ mod test { #[cfg(windows)] fn default_handle() -> Handle { ::std::ptr::null_mut() + //super::get_std_handle(super::STDOUT_FILENO).expect("Valid stdout") } fn init_state<'out>(out: &'out mut Write, From 91ac0a8494dba95a3df5de0164a567bb86b8e9a7 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 14 Jul 2016 07:08:42 +0200 Subject: [PATCH 0060/1201] Make History ignore space/dups configurable (#50) --- examples/example.rs | 2 +- src/history.rs | 25 +++++++++++++++++++------ src/lib.rs | 12 +++++++++++- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 68e63c4725..3d824b66df 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -6,7 +6,7 @@ use rustyline::Editor; fn main() { let c = FilenameCompleter::new(); - let mut rl = Editor::new(); + let mut rl = Editor::new().history_ignore_space(true); rl.set_completer(Some(&c)); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); diff --git a/src/history.rs b/src/history.rs index cdf5522383..39667917f4 100644 --- a/src/history.rs +++ b/src/history.rs @@ -9,6 +9,8 @@ use super::Result; pub struct History { entries: VecDeque, max_len: usize, + ignore_space: bool, + ignore_dups: bool, } const DEFAULT_HISTORY_MAX_LEN: usize = 100; @@ -18,9 +20,19 @@ impl History { History { entries: VecDeque::new(), max_len: DEFAULT_HISTORY_MAX_LEN, + ignore_space: true, + ignore_dups: true, } } + pub fn ignore_space(&mut self, yes: bool) { + self.ignore_space = yes; + } + + pub fn ignore_dups(&mut self, yes: bool) { + self.ignore_dups = yes; + } + /// Return the history entry at position `index`, starting from 0. pub fn get(&self, index: usize) -> Option<&String> { self.entries.get(index) @@ -31,14 +43,15 @@ impl History { if self.max_len == 0 { return false; } - if line.is_empty() || line.chars().next().map_or(true, |c| c.is_whitespace()) { - // ignorespace + if line.is_empty() || + (self.ignore_space && line.chars().next().map_or(true, |c| c.is_whitespace())) { return false; } - if let Some(s) = self.entries.back() { - if s == line { - // ignoredups - return false; + if self.ignore_dups { + if let Some(s) = self.entries.back() { + if s == line { + return false; + } } } if self.entries.len() == self.max_len { diff --git a/src/lib.rs b/src/lib.rs index ac2fc2bf97..ac5d9ff65b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1219,6 +1219,16 @@ impl<'completer> Editor<'completer> { } } + pub fn history_ignore_space(mut self, yes: bool) -> Editor<'completer> { + self.history.ignore_space(yes); + self + } + + pub fn history_ignore_dups(mut self, yes: bool) -> Editor<'completer> { + self.history.ignore_dups(yes); + self + } + /// Load the history from the specified file. pub fn load_history + ?Sized>(&mut self, path: &P) -> Result<()> { self.history.load(path) @@ -1302,7 +1312,7 @@ mod test { #[cfg(windows)] fn default_handle() -> Handle { ::std::ptr::null_mut() - //super::get_std_handle(super::STDOUT_FILENO).expect("Valid stdout") + // super::get_std_handle(super::STDOUT_FILENO).expect("Valid stdout") } fn init_state<'out>(out: &'out mut Write, From 8b14661e0b831fe461b270499df45e2d1e51ec65 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 14 Jul 2016 07:08:42 +0200 Subject: [PATCH 0061/1201] Make History ignore space/dups configurable (#50) --- examples/example.rs | 2 +- src/history.rs | 25 +++++++++++++++++++------ src/lib.rs | 10 ++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index d098f2620b..4e553cd75f 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -15,7 +15,7 @@ static PROMPT: &'static str = ">> "; fn main() { let c = FilenameCompleter::new(); - let mut rl = Editor::new(); + let mut rl = Editor::new().history_ignore_space(true); rl.set_completer(Some(&c)); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); diff --git a/src/history.rs b/src/history.rs index cdf5522383..39667917f4 100644 --- a/src/history.rs +++ b/src/history.rs @@ -9,6 +9,8 @@ use super::Result; pub struct History { entries: VecDeque, max_len: usize, + ignore_space: bool, + ignore_dups: bool, } const DEFAULT_HISTORY_MAX_LEN: usize = 100; @@ -18,9 +20,19 @@ impl History { History { entries: VecDeque::new(), max_len: DEFAULT_HISTORY_MAX_LEN, + ignore_space: true, + ignore_dups: true, } } + pub fn ignore_space(&mut self, yes: bool) { + self.ignore_space = yes; + } + + pub fn ignore_dups(&mut self, yes: bool) { + self.ignore_dups = yes; + } + /// Return the history entry at position `index`, starting from 0. pub fn get(&self, index: usize) -> Option<&String> { self.entries.get(index) @@ -31,14 +43,15 @@ impl History { if self.max_len == 0 { return false; } - if line.is_empty() || line.chars().next().map_or(true, |c| c.is_whitespace()) { - // ignorespace + if line.is_empty() || + (self.ignore_space && line.chars().next().map_or(true, |c| c.is_whitespace())) { return false; } - if let Some(s) = self.entries.back() { - if s == line { - // ignoredups - return false; + if self.ignore_dups { + if let Some(s) = self.entries.back() { + if s == line { + return false; + } } } if self.entries.len() == self.max_len { diff --git a/src/lib.rs b/src/lib.rs index 06da79d2e2..f52e2771dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -936,6 +936,16 @@ impl<'completer> Editor<'completer> { } } + pub fn history_ignore_space(mut self, yes: bool) -> Editor<'completer> { + self.history.ignore_space(yes); + self + } + + pub fn history_ignore_dups(mut self, yes: bool) -> Editor<'completer> { + self.history.ignore_dups(yes); + self + } + /// Load the history from the specified file. pub fn load_history + ?Sized>(&mut self, path: &P) -> Result<()> { self.history.load(path) From 0c64a1ffd45b2424d21e4d1b19b00330702229a7 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 14 Jul 2016 13:50:57 +0200 Subject: [PATCH 0062/1201] Partial implementation of InputBuffer.read --- src/lib.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ac5d9ff65b..385c86ff3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -355,7 +355,6 @@ const TIOCGWINSZ: libc::c_ulong = 0x5413; target_os = "macos", target_os = "freebsd"))] fn get_columns(_: Handle) -> usize { - use std::mem::zeroed; use libc::c_ushort; use libc; @@ -368,7 +367,7 @@ fn get_columns(_: Handle) -> usize { ws_ypixel: c_ushort, } - let mut size: winsize = zeroed(); + let mut size: winsize = mem::zeroed(); match libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size) { 0 => size.ws_col as usize, // TODO getCursorPosition _ => 80, @@ -898,8 +897,29 @@ struct InputBuffer(winapi::HANDLE); #[cfg(windows)] impl Read for InputBuffer { fn read(&mut self, buf: &mut [u8]) -> io::Result { - // FIXME: ReadConsoleInputW on windows platform - unimplemented!() + let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; + let mut count = 0; + loop { + check!(kernel32::ReadConsoleInputW(self.0, &mut rec, 1 as winapi::DWORD, &mut count)); + + // TODO ENABLE_WINDOW_INPUT ??? + if rec.EventType == winapi::WINDOW_BUFFER_SIZE_EVENT { + SIGWINCH.store(true, atomic::Ordering::SeqCst); + return Err(io::Error::new(io::ErrorKind::Other, "WINDOW_BUFFER_SIZE_EVENT")); + } else if rec.EventType != winapi::KEY_EVENT { + continue; + } + let key_event = unsafe { rec.KeyEvent() }; + if key_event.bKeyDown == 0 && key_event.wVirtualKeyCode != winapi::VK_MENU as winapi::WORD { + continue; + } + + if key_event.UnicodeChar != 0 { + } + // TODO dwControlKeyState + // TODO wVirtualKeyCode WORD & winapi::VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_DELETE, VK_HOME, VK_END, VK_PRIOR, VK_NEXT c_int + unimplemented!() + } } } From cfacc186d58dfc20548357a96f66207e5083e95b Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 14 Jul 2016 19:54:30 +0200 Subject: [PATCH 0063/1201] InputBuffer.read on windows (in progress) --- src/lib.rs | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 385c86ff3a..4facbcc2b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -895,10 +895,18 @@ fn stdout_handle() -> Result { #[cfg(windows)] struct InputBuffer(winapi::HANDLE); #[cfg(windows)] +impl InputBuffer { + fn single_byte(c: u8, buf: &mut [u8]) -> io::Result { + buf[0] = c; + Ok(1) + } +} +#[cfg(windows)] impl Read for InputBuffer { fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; let mut count = 0; + let mut esc_seen = false; loop { check!(kernel32::ReadConsoleInputW(self.0, &mut rec, 1 as winapi::DWORD, &mut count)); @@ -910,14 +918,43 @@ impl Read for InputBuffer { continue; } let key_event = unsafe { rec.KeyEvent() }; - if key_event.bKeyDown == 0 && key_event.wVirtualKeyCode != winapi::VK_MENU as winapi::WORD { + if key_event.bKeyDown == 0 && + key_event.wVirtualKeyCode != winapi::VK_MENU as winapi::WORD { continue; } - if key_event.UnicodeChar != 0 { + let ctrl = key_event.dwControlKeyState & + (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) == + (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED); + let meta = (key_event.dwControlKeyState & + (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED) == + (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED)) || + esc_seen; + + let utf16 = key_event.UnicodeChar; + if utf16 == 0 { + match key_event.wVirtualKeyCode as i32 { + winapi::VK_LEFT => return InputBuffer::single_byte(b'\x02', buf), + winapi::VK_RIGHT => return InputBuffer::single_byte(b'\x06', buf), + winapi::VK_UP => return InputBuffer::single_byte(b'\x10', buf), + winapi::VK_DOWN => return InputBuffer::single_byte(b'\x0e', buf), + // winapi::VK_DELETE => b"\x1b[3~", + winapi::VK_HOME => return InputBuffer::single_byte(b'\x01', buf), + winapi::VK_END => return InputBuffer::single_byte(b'\x05', buf), + _ => continue, + }; + } else if utf16 == 27 { + esc_seen = true; + continue; + } else { + if ctrl { + + } else if meta { + + } else { + // return utf8.read(buf); + } } - // TODO dwControlKeyState - // TODO wVirtualKeyCode WORD & winapi::VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_DELETE, VK_HOME, VK_END, VK_PRIOR, VK_NEXT c_int unimplemented!() } } From 893ca7aceb45fc1d2f85a498c952ba6ff94aba30 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 16 Jul 2016 07:59:29 +0200 Subject: [PATCH 0064/1201] Clear only the lines/rows drawn/used by the editor (#48) And do not ignorespace by default. And do not panic when the history is empty --- src/history.rs | 2 +- src/lib.rs | 30 +++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/history.rs b/src/history.rs index 39667917f4..d7cf02ef9c 100644 --- a/src/history.rs +++ b/src/history.rs @@ -20,7 +20,7 @@ impl History { History { entries: VecDeque::new(), max_len: DEFAULT_HISTORY_MAX_LEN, - ignore_space: true, + ignore_space: false, ignore_dups: true, } } diff --git a/src/lib.rs b/src/lib.rs index 4facbcc2b3..234ebc6686 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,7 +77,8 @@ struct State<'out, 'prompt> { line: LineBuffer, // Edited line buffer cursor: Position, // Cursor position (relative to the start of the prompt for `row`) cols: usize, // Number of columns in terminal - history_index: usize, // The history index we are currently editing. + old_rows: usize, // Number of rows used so far (from start of prompt to end of input) + history_index: usize, // The history index we are currently editing snapshot: LineBuffer, // Current edited line before history browsing/completion output_handle: Handle, // output handle (for windows) } @@ -104,6 +105,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { line: LineBuffer::with_capacity(capacity), cursor: prompt_size, cols: cols, + old_rows: prompt_size.row, history_index: history_index, snapshot: LineBuffer::with_capacity(capacity), output_handle: output_handle, @@ -140,13 +142,19 @@ impl<'out, 'prompt> State<'out, 'prompt> { let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols); let mut ab = String::new(); - let cursor_row_movement = self.cursor.row - self.prompt_size.row; - // move the cursor up as required + + let cursor_row_movement = self.old_rows - self.cursor.row; + // move the cursor down as required if cursor_row_movement > 0 { - write!(ab, "\x1b[{}A", cursor_row_movement).unwrap(); + write!(ab, "\x1b[{}B", cursor_row_movement).unwrap(); + } + // clear old rows + for _ in 0..self.old_rows { + ab.push_str("\r\x1b[0K\x1b[1A"); } - // position at the start of the prompt, clear to end of screen - ab.push_str("\r\x1b[J"); + // clear the line + ab.push_str("\r\x1b[0K"); + // display the prompt ab.push_str(prompt); // display the input line @@ -169,6 +177,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { } self.cursor = cursor; + self.old_rows = end_pos.row; write_and_flush(self.out, ab.as_bytes()) } @@ -226,6 +235,7 @@ impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> { .field("buf", &self.line) .field("cursor", &self.cursor) .field("cols", &self.cols) + .field("old_rows", &self.old_rows) .field("history_index", &self.history_index) .field("snapshot", &self.snapshot) .finish() @@ -475,6 +485,8 @@ fn edit_insert(s: &mut State, ch: char) -> Result<()> { if push { if s.cursor.col + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) < s.cols { // Avoid a full update of the line in the trivial case. + let cursor = calculate_position(&s.line[..s.line.pos()], s.prompt_size, s.cols); + s.cursor = cursor; let bits = ch.encode_utf8(); let bits = bits.as_slice(); write_and_flush(s.out, bits) @@ -732,6 +744,9 @@ fn reverse_incremental_search(chars: &mut io::Chars, s: &mut State, history: &History) -> Result> { + if history.is_empty() { + return Ok(None); + } // Save the current edited line (and cursor position) before to overwrite it s.snapshot(); @@ -861,7 +876,7 @@ fn escape_sequence(chars: &mut io::Chars) -> Result { 'y' | 'Y' => Ok(KeyPress::ESC_Y), '\x08' | '\x7f' => Ok(KeyPress::ESC_BACKSPACE), _ => { - writeln!(io::stderr(), "key: {:?}, seq1, {:?}", KeyPress::ESC, seq1).unwrap(); + // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::ESC, seq1).unwrap(); Ok(KeyPress::UNKNOWN_ESC_SEQ) } } @@ -931,6 +946,7 @@ impl Read for InputBuffer { (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED)) || esc_seen; + // TODO How to support surrogate pair ? let utf16 = key_event.UnicodeChar; if utf16 == 0 { match key_event.wVirtualKeyCode as i32 { From abb9b6b714cabce53ac95d2aef46251c58c81620 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 16 Jul 2016 08:22:23 +0200 Subject: [PATCH 0065/1201] Fix tests --- src/history.rs | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/history.rs b/src/history.rs index d7cf02ef9c..8b28f24987 100644 --- a/src/history.rs +++ b/src/history.rs @@ -169,6 +169,7 @@ mod tests { #[test] fn add() { let mut history = super::History::new(); + history.ignore_space(true); assert!(history.add("line1")); assert!(history.add("line2")); assert!(!history.add("line2")); diff --git a/src/lib.rs b/src/lib.rs index 234ebc6686..4aa89e34d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1400,6 +1400,7 @@ mod test { line: LineBuffer::init(line, pos), cursor: Default::default(), cols: cols, + old_rows: 0, history_index: 0, snapshot: LineBuffer::with_capacity(100), output_handle: default_handle(), From 8c9859732521b6413ddf8481a40aa701a9c367ff Mon Sep 17 00:00:00 2001 From: Markus Date: Fri, 15 Jul 2016 18:38:28 +0200 Subject: [PATCH 0066/1201] Parametrize Editor by the Completer type This allows the `Editor` to implement `Sync` as long as it contains a completer which implement `Sync`. It lets the `Editor` take ownership of the completer. --- README.md | 3 ++- examples/example.rs | 2 +- src/completion.rs | 36 +++++++++++++++++++++++++ src/lib.rs | 64 +++++++++++++++++++++++---------------------- 4 files changed, 72 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 9709bf4c34..3737f41a04 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ use rustyline::error::ReadlineError; use rustyline::Editor; fn main() { - let mut rl = Editor::new(); + // `()` can be used when no completer is required + let mut rl = Editor::<()>::new(); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); } diff --git a/examples/example.rs b/examples/example.rs index 4e553cd75f..134e1e25c8 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -16,7 +16,7 @@ static PROMPT: &'static str = ">> "; fn main() { let c = FilenameCompleter::new(); let mut rl = Editor::new().history_ignore_space(true); - rl.set_completer(Some(&c)); + rl.set_completer(Some(c)); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); } diff --git a/src/completion.rs b/src/completion.rs index 9a5aa39672..2359316744 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -23,6 +23,42 @@ pub trait Completer { } } +impl Completer for () { + fn complete(&self, _line: &str, _pos: usize) -> Result<(usize, Vec)> { + Ok((0, Vec::new())) + } + fn update(&self, _line: &mut LineBuffer, _start: usize, _elected: &str) { + unreachable!() + } +} + +impl<'c, C: ?Sized + Completer> Completer for &'c C { + fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec)> { + (**self).complete(line, pos) + } + fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) { + (**self).update(line, start, elected) + } +} +macro_rules! box_completer { + ($($id: ident)*) => { + $( + impl Completer for $id { + fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec)> { + (**self).complete(line, pos) + } + fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) { + (**self).update(line, start, elected) + } + } + )* + } +} + +use std::sync::Arc; +use std::rc::Rc; +box_completer! { Box Rc Arc } + pub struct FilenameCompleter { break_chars: BTreeSet, } diff --git a/src/lib.rs b/src/lib.rs index f52e2771dc..03a0bd9b8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ //! Usage //! //! ``` -//! let mut rl = rustyline::Editor::new(); +//! let mut rl = rustyline::Editor::<()>::new(); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -888,18 +888,18 @@ fn readline_direct() -> Result { } /// Line editor -pub struct Editor<'completer> { +pub struct Editor { unsupported_term: bool, stdin_isatty: bool, stdout_isatty: bool, // cols: usize, // Number of columns in terminal history: History, - completer: Option<&'completer Completer>, + completer: Option, kill_ring: KillRing, } -impl<'completer> Editor<'completer> { - pub fn new() -> Editor<'completer> { +impl Editor { + pub fn new() -> Editor { // TODO check what is done in rl_initialize() // if the number of columns is stored here, we need a SIGWINCH handler... let editor = Editor { @@ -916,32 +916,12 @@ impl<'completer> Editor<'completer> { editor } - /// This method will read a line from STDIN and will display a `prompt` - #[cfg_attr(feature="clippy", allow(if_not_else))] - pub fn readline(&mut self, prompt: &str) -> Result { - if self.unsupported_term { - // Write prompt and flush it to stdout - let mut stdout = io::stdout(); - try!(write_and_flush(&mut stdout, prompt.as_bytes())); - - readline_direct() - } else if !self.stdin_isatty { - // Not a tty: read from file / pipe. - readline_direct() - } else { - readline_raw(prompt, - &mut self.history, - self.completer, - &mut self.kill_ring) - } - } - - pub fn history_ignore_space(mut self, yes: bool) -> Editor<'completer> { + pub fn history_ignore_space(mut self, yes: bool) -> Editor { self.history.ignore_space(yes); self } - pub fn history_ignore_dups(mut self, yes: bool) -> Editor<'completer> { + pub fn history_ignore_dups(mut self, yes: bool) -> Editor { self.history.ignore_dups(yes); self } @@ -970,20 +950,42 @@ impl<'completer> Editor<'completer> { pub fn get_history(&mut self) -> &mut History { &mut self.history } +} + +impl Editor { + /// This method will read a line from STDIN and will display a `prompt` + #[cfg_attr(feature="clippy", allow(if_not_else))] + pub fn readline(&mut self, prompt: &str) -> Result { + if self.unsupported_term { + // Write prompt and flush it to stdout + let mut stdout = io::stdout(); + try!(write_and_flush(&mut stdout, prompt.as_bytes())); + + readline_direct() + } else if !self.stdin_isatty { + // Not a tty: read from file / pipe. + readline_direct() + } else { + readline_raw(prompt, + &mut self.history, + self.completer.as_ref().map(|c| c as &Completer), + &mut self.kill_ring) + } + } /// Register a callback function to be called for tab-completion. - pub fn set_completer(&mut self, completer: Option<&'completer Completer>) { + pub fn set_completer(&mut self, completer: Option) { self.completer = completer; } } -impl<'completer> Default for Editor<'completer> { - fn default() -> Editor<'completer> { +impl Default for Editor { + fn default() -> Editor { Editor::new() } } -impl<'completer> fmt::Debug for Editor<'completer> { +impl fmt::Debug for Editor { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("State") .field("unsupported_term", &self.unsupported_term) From 2868cb8f444042bd7d80475076d2e8ff62ffefb5 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 16 Jul 2016 12:20:48 +0200 Subject: [PATCH 0067/1201] Fix refresh on windows --- README.md | 10 ++++++++++ src/lib.rs | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ccb49c2d5..7ade1f1859 100644 --- a/README.md +++ b/README.md @@ -107,3 +107,13 @@ Alt-BackSpace | Kill from the start of the current word, or, if between words, t - Show completion list - expose an API callable from C + +## Wine + +```sh +$ cargo run --example example --target 'x86_64-pc-windows-gnu' +... +Error: Io(Error { repr: Os { code: 6, message: "Invalid handle." } }) +$ wineconsole --backend=curses target/x86_64-pc-windows-gnu/debug/examples/example.exe +... +``` \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4aa89e34d8..800585f2ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -202,7 +202,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { let mut _count = 0; check!(kernel32::FillConsoleOutputCharacterA(handle, ' ' as winapi::CHAR, - (info.dwSize.X * info.dwSize.Y) as winapi::DWORD, // FIXME + (info.dwSize.X * (self.old_rows as i16 +1)) as winapi::DWORD, info.dwCursorPosition, &mut _count)); let mut ab = String::new(); @@ -219,6 +219,8 @@ impl<'out, 'prompt> State<'out, 'prompt> { check!(kernel32::SetConsoleCursorPosition(handle, info.dwCursorPosition)); self.cursor = cursor; + self.old_rows = end_pos.row; + Ok(()) } From 076eecf5cf3900ce18a635ffa792e5e8854741b0 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 17 Jul 2016 07:55:31 +0200 Subject: [PATCH 0068/1201] Partially fix Read impl for windows --- src/lib.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 800585f2ab..37bfa509f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,11 +62,15 @@ type Handle = (); type Handle = winapi::HANDLE; #[cfg(windows)] macro_rules! check { - ($funcall:expr) => ( - if unsafe { $funcall } == 0 { + ($funcall:expr) => { + { + let rc = unsafe { $funcall }; + if rc == 0 { try!(Err(io::Error::last_os_error())); } - ); + rc + } + }; } // Represent the state during line editing. @@ -207,7 +211,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { &mut _count)); let mut ab = String::new(); // display the prompt - ab.push_str(prompt); + ab.push_str(prompt); // TODO handle ansi escape code (SetConsoleTextAttribute) // display the input line ab.push_str(&self.line); try!(write_and_flush(self.out, ab.as_bytes())); @@ -917,6 +921,20 @@ impl InputBuffer { buf[0] = c; Ok(1) } + fn wide_char_to_multi_byte(c: u16) -> io::Result<([u8; 4], usize)> { + use std::ptr; + const WC_COMPOSITECHECK: winapi::DWORD = 0x00000200; // use composite chars + let mut bytes = [0u8; 4]; + let len = check!(kernel32::WideCharToMultiByte(winapi::CP_ACP, + WC_COMPOSITECHECK, + &c, + 1, + (&mut bytes).as_mut_ptr() as *mut i8, + 4, + ptr::null(), + ptr::null_mut())); + Ok((bytes, len as usize)) + } } #[cfg(windows)] impl Read for InputBuffer { @@ -965,12 +983,13 @@ impl Read for InputBuffer { esc_seen = true; continue; } else { + let (bytes, len) = try!(InputBuffer::wide_char_to_multi_byte(utf16)); if ctrl { } else if meta { } else { - // return utf8.read(buf); + return (&bytes[..len]).read(buf); } } unimplemented!() From d092ff73db635c80f5f0994b48282044d1eef027 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 21 Jul 2016 22:00:09 +0200 Subject: [PATCH 0069/1201] Introduce RawReader to abstract reads from console Only the unix implementation works. The windows impl does not even compile... --- src/consts.rs | 1 + src/lib.rs | 306 +++++++++++++++++++++++++++++--------------------- 2 files changed, 181 insertions(+), 126 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index e0a6d3ce7a..b762ede5eb 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -42,6 +42,7 @@ pub enum KeyPress { } #[cfg_attr(feature="clippy", allow(match_same_arms))] +#[cfg(unix)] pub fn char_to_key_press(c: char) -> KeyPress { match c { '\x00' => KeyPress::NULL, diff --git a/src/lib.rs b/src/lib.rs index 37bfa509f5..774ffddc37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,10 +34,14 @@ pub mod history; mod kill_ring; pub mod line_buffer; +#[cfg(windows)] +use std::cell::RefCell; use std::fmt; use std::io::{self, Read, Write}; use std::mem; use std::path::Path; +#[cfg(windows)] +use std::rc::Rc; use std::result; #[cfg(unix)] use std::sync; @@ -48,7 +52,7 @@ use nix::sys::signal; use nix::sys::termios; use completion::Completer; -use consts::{KeyPress, char_to_key_press}; +use consts::KeyPress; use history::History; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; use kill_ring::KillRing; @@ -693,10 +697,10 @@ fn edit_history_next(s: &mut State, history: &History, prev: bool) -> Result<()> } /// Completes the line/word -fn complete_line(chars: &mut io::Chars, - s: &mut State, - completer: &Completer) - -> Result> { +fn complete_line(rdr: &mut RawReader, + s: &mut State, + completer: &Completer) + -> Result> { let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); if candidates.is_empty() { try!(beep()); @@ -718,8 +722,8 @@ fn complete_line(chars: &mut io::Chars, s.snapshot(); } - ch = try!(chars.next().unwrap()); - let key = char_to_key_press(ch); + ch = try!(rdr.next()); + let key = rdr.char_to_key_press(ch); match key { KeyPress::TAB => { i = (i + 1) % (candidates.len() + 1); // Circular @@ -746,10 +750,10 @@ fn complete_line(chars: &mut io::Chars, /// Incremental search #[cfg_attr(feature="clippy", allow(if_not_else))] -fn reverse_incremental_search(chars: &mut io::Chars, - s: &mut State, - history: &History) - -> Result> { +fn reverse_incremental_search(rdr: &mut RawReader, + s: &mut State, + history: &History) + -> Result> { if history.is_empty() { return Ok(None); } @@ -772,13 +776,13 @@ fn reverse_incremental_search(chars: &mut io::Chars, }; try!(s.refresh_prompt_and_line(&prompt)); - ch = try!(chars.next().unwrap()); - if !ch.is_control() { + ch = try!(rdr.next()); + if !rdr.is_control(ch) { search_buf.push(ch); } else { - key = char_to_key_press(ch); + key = rdr.char_to_key_press(ch); if key == KeyPress::ESC { - key = try!(escape_sequence(chars)); + key = try!(rdr.escape_sequence()); } match key { KeyPress::CTRL_H | KeyPress::BACKSPACE => { @@ -826,97 +830,145 @@ fn reverse_incremental_search(chars: &mut io::Chars, Ok(Some(key)) } +/// Console input reader +#[cfg(unix)] +struct RawReader { + chars: io::Chars, +} + #[cfg(unix)] -fn escape_sequence(chars: &mut io::Chars) -> Result { - // Read the next two bytes representing the escape sequence. - let seq1 = try!(chars.next().unwrap()); - if seq1 == '[' { - // ESC [ sequences. - let seq2 = try!(chars.next().unwrap()); - if seq2.is_digit(10) { - // Extended escape, read additional byte. - let seq3 = try!(chars.next().unwrap()); - if seq3 == '~' { +impl RawReader { + fn new(stdin: R) -> Result> { + Ok(RawReader { chars: stdin.chars() }) + } + + fn next(&mut self) -> Result { + match self.chars.next() { + Some(c) => { + Ok(try!(c)) // TODO SIGWINCH + } + None => Err(error::ReadlineError::Eof), + } + } + + fn is_control(&self, c: char) -> bool { + c.is_control() + } + + fn char_to_key_press(&self, c: char) -> KeyPress { + use consts::char_to_key_press; + char_to_key_press(c) // TODO directly handle ESC + escape_sequence + } + + fn escape_sequence(&mut self) -> Result { + // Read the next two bytes representing the escape sequence. + let seq1 = try!(self.next()); + if seq1 == '[' { + // ESC [ sequences. + let seq2 = try!(self.next()); + if seq2.is_digit(10) { + // Extended escape, read additional byte. + let seq3 = try!(self.next()); + if seq3 == '~' { + match seq2 { + '3' => Ok(KeyPress::ESC_SEQ_DELETE), + // TODO '1' // Home + // TODO '4' // End + _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), + } + } else { + Ok(KeyPress::UNKNOWN_ESC_SEQ) + } + } else { match seq2 { - '3' => Ok(KeyPress::ESC_SEQ_DELETE), - // TODO '1' // Home - // TODO '4' // End + 'A' => Ok(KeyPress::CTRL_P), // Up + 'B' => Ok(KeyPress::CTRL_N), // Down + 'C' => Ok(KeyPress::CTRL_F), // Right + 'D' => Ok(KeyPress::CTRL_B), // Left + 'F' => Ok(KeyPress::CTRL_E), // End + 'H' => Ok(KeyPress::CTRL_A), // Home _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), } - } else { - Ok(KeyPress::UNKNOWN_ESC_SEQ) } - } else { + } else if seq1 == 'O' { + // ESC O sequences. + let seq2 = try!(self.next()); match seq2 { - 'A' => Ok(KeyPress::CTRL_P), // Up - 'B' => Ok(KeyPress::CTRL_N), // Down - 'C' => Ok(KeyPress::CTRL_F), // Right - 'D' => Ok(KeyPress::CTRL_B), // Left - 'F' => Ok(KeyPress::CTRL_E), // End - 'H' => Ok(KeyPress::CTRL_A), // Home + 'F' => Ok(KeyPress::CTRL_E), + 'H' => Ok(KeyPress::CTRL_A), _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), } - } - } else if seq1 == 'O' { - // ESC O sequences. - let seq2 = try!(chars.next().unwrap()); - match seq2 { - 'F' => Ok(KeyPress::CTRL_E), - 'H' => Ok(KeyPress::CTRL_A), - _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), - } - } else { - // TODO ESC-N (n): search history forward not interactively - // TODO ESC-P (p): search history backward not interactively - // TODO ESC-R (r): Undo all changes made to this line. - // TODO ESC-<: move to first entry in history - // TODO ESC->: move to last entry in history - match seq1 { - 'b' | 'B' => Ok(KeyPress::ESC_B), - 'c' | 'C' => Ok(KeyPress::ESC_C), - 'd' | 'D' => Ok(KeyPress::ESC_D), - 'f' | 'F' => Ok(KeyPress::ESC_F), - 'l' | 'L' => Ok(KeyPress::ESC_L), - 't' | 'T' => Ok(KeyPress::ESC_T), - 'u' | 'U' => Ok(KeyPress::ESC_U), - 'y' | 'Y' => Ok(KeyPress::ESC_Y), - '\x08' | '\x7f' => Ok(KeyPress::ESC_BACKSPACE), - _ => { - // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::ESC, seq1).unwrap(); - Ok(KeyPress::UNKNOWN_ESC_SEQ) + } else { + // TODO ESC-N (n): search history forward not interactively + // TODO ESC-P (p): search history backward not interactively + // TODO ESC-R (r): Undo all changes made to this line. + // TODO ESC-<: move to first entry in history + // TODO ESC->: move to last entry in history + match seq1 { + 'b' | 'B' => Ok(KeyPress::ESC_B), + 'c' | 'C' => Ok(KeyPress::ESC_C), + 'd' | 'D' => Ok(KeyPress::ESC_D), + 'f' | 'F' => Ok(KeyPress::ESC_F), + 'l' | 'L' => Ok(KeyPress::ESC_L), + 't' | 'T' => Ok(KeyPress::ESC_T), + 'u' | 'U' => Ok(KeyPress::ESC_U), + 'y' | 'Y' => Ok(KeyPress::ESC_Y), + '\x08' | '\x7f' => Ok(KeyPress::ESC_BACKSPACE), + _ => { + // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::ESC, seq1).unwrap(); + Ok(KeyPress::UNKNOWN_ESC_SEQ) + } } } } } -#[cfg(windows)] -fn escape_sequence(chars: &mut io::Chars) -> Result { - Ok(KeyPress::UNKNOWN_ESC_SEQ) -} -#[cfg(unix)] -fn stdin() -> Result { - Ok(io::stdin()) -} #[cfg(windows)] -fn stdin() -> Result { - let handle = try!(get_std_handle(STDIN_FILENO)); - Ok(InputBuffer(handle)) -} - -#[cfg(unix)] -fn stdout_handle() -> Result { - Ok(()) +struct RawReader { + handle: winapi::HANDLE, + key_state: Rc>, + chars: io::Chars, } #[cfg(windows)] -fn stdout_handle() -> Result { - let handle = try!(get_std_handle(STDOUT_FILENO)); - Ok(handle) +struct KeyState { + ctrl: bool, + meta: bool, } #[cfg(windows)] -struct InputBuffer(winapi::HANDLE); -#[cfg(windows)] -impl InputBuffer { +impl RawReader { + fn new() -> Result> { + let handle = try!(get_std_handle(STDIN_FILENO)); + Ok(RawReader { + handle: handle, + key_state: Rc::new(RefCell::new(KeyState { + ctrl: false, + meta: false, + })), + }) + } + + fn next(&mut self) -> Result { + match self.chars.next() { + Some(c) => { + Ok(try!(c)) // TODO SIGWINCH + } + None => Err(error::ReadlineError::Eof), + } + } + + fn is_control(&self, c: char) -> bool { + c.is_control() || self.key_state.borrow().ctrl + } + + fn char_to_key_press(&self, c: char) -> KeyPress { + unimplemented!() + } + + fn escape_sequence(&mut self) -> Result { + Ok(KeyPress::UNKNOWN_ESC_SEQ) + } + fn single_byte(c: u8, buf: &mut [u8]) -> io::Result { buf[0] = c; Ok(1) @@ -936,8 +988,9 @@ impl InputBuffer { Ok((bytes, len as usize)) } } + #[cfg(windows)] -impl Read for InputBuffer { +impl Read for RawReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; let mut count = 0; @@ -958,45 +1011,49 @@ impl Read for InputBuffer { continue; } - let ctrl = key_event.dwControlKeyState & - (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) == - (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED); - let meta = (key_event.dwControlKeyState & - (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED) == - (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED)) || - esc_seen; + let key_state = self.key_state.borrow_mut(); + key_state.ctrl = key_event.dwControlKeyState & + (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) == + (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED); + key_state.meta = (key_event.dwControlKeyState & + (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED) == + (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED)) || + esc_seen; // TODO How to support surrogate pair ? let utf16 = key_event.UnicodeChar; if utf16 == 0 { match key_event.wVirtualKeyCode as i32 { - winapi::VK_LEFT => return InputBuffer::single_byte(b'\x02', buf), - winapi::VK_RIGHT => return InputBuffer::single_byte(b'\x06', buf), - winapi::VK_UP => return InputBuffer::single_byte(b'\x10', buf), - winapi::VK_DOWN => return InputBuffer::single_byte(b'\x0e', buf), + winapi::VK_LEFT => return RawReader::single_byte(b'\x02', buf), + winapi::VK_RIGHT => return RawReader::single_byte(b'\x06', buf), + winapi::VK_UP => return RawReader::single_byte(b'\x10', buf), + winapi::VK_DOWN => return RawReader::single_byte(b'\x0e', buf), // winapi::VK_DELETE => b"\x1b[3~", - winapi::VK_HOME => return InputBuffer::single_byte(b'\x01', buf), - winapi::VK_END => return InputBuffer::single_byte(b'\x05', buf), + winapi::VK_HOME => return RawReader::single_byte(b'\x01', buf), + winapi::VK_END => return RawReader::single_byte(b'\x05', buf), _ => continue, }; } else if utf16 == 27 { esc_seen = true; continue; } else { - let (bytes, len) = try!(InputBuffer::wide_char_to_multi_byte(utf16)); - if ctrl { - - } else if meta { - - } else { - return (&bytes[..len]).read(buf); - } + let (bytes, len) = try!(RawReader::wide_char_to_multi_byte(utf16)); + return (&bytes[..len]).read(buf); } - unimplemented!() } } } +#[cfg(unix)] +fn stdout_handle() -> Result { + Ok(()) +} +#[cfg(windows)] +fn stdout_handle() -> Result { + let handle = try!(get_std_handle(STDOUT_FILENO)); + Ok(handle) +} + /// Handles reading and editting the readline buffer. /// It will also handle special inputs in an appropriate fashion /// (e.g., C-c will exit readline) @@ -1014,41 +1071,40 @@ fn readline_edit(prompt: &str, let mut s = State::new(&mut stdout, stdout_handle, prompt, history.len()); try!(s.refresh_line()); - let stdin = try!(stdin()); - let mut chars = stdin.chars(); // TODO stdin.lock() ??? + let mut rdr = try!(RawReader::new(io::stdin())); loop { - let c = chars.next().unwrap(); + let c = rdr.next(); if c.is_err() && SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { s.update_columns(); try!(s.refresh_line()); continue; } let mut ch = try!(c); - if !ch.is_control() { + if !rdr.is_control(ch) { kill_ring.reset(); try!(edit_insert(&mut s, ch)); continue; } - let mut key = char_to_key_press(ch); + let mut key = rdr.char_to_key_press(ch); // autocomplete if key == KeyPress::TAB && completer.is_some() { - let next = try!(complete_line(&mut chars, &mut s, completer.unwrap())); + let next = try!(complete_line(&mut rdr, &mut s, completer.unwrap())); if next.is_some() { kill_ring.reset(); ch = next.unwrap(); - if !ch.is_control() { + if !rdr.is_control(ch) { try!(edit_insert(&mut s, ch)); continue; } - key = char_to_key_press(ch); + key = rdr.char_to_key_press(ch); } else { continue; } } else if key == KeyPress::CTRL_R { // Search history backward - let next = try!(reverse_incremental_search(&mut chars, &mut s, history)); + let next = try!(reverse_incremental_search(&mut rdr, &mut s, history)); if next.is_some() { key = next.unwrap(); } else { @@ -1056,7 +1112,7 @@ fn readline_edit(prompt: &str, } } else if key == KeyPress::ESC { // escape sequence - key = try!(escape_sequence(&mut chars)); + key = try!(rdr.escape_sequence()); if key == KeyPress::UNKNOWN_ESC_SEQ { continue; } @@ -1136,7 +1192,7 @@ fn readline_edit(prompt: &str, KeyPress::CTRL_V => { // Quoted insert kill_ring.reset(); - let c = chars.next().unwrap(); + let c = rdr.next(); let ch = try!(c); try!(edit_insert(&mut s, ch)) } @@ -1397,7 +1453,7 @@ mod test { use history::History; use completion::Completer; use State; - use super::{Handle, Result}; + use super::{Handle, RawReader, Result}; #[cfg(unix)] fn default_handle() -> Handle { @@ -1475,14 +1531,12 @@ mod test { #[test] fn complete_line() { - use std::io::Read; - let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); let input = b"\n"; - let mut chars = input.chars(); + let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; - let ch = super::complete_line(&mut chars, &mut s, &completer).unwrap(); + let ch = super::complete_line(&mut rdr, &mut s, &completer).unwrap(); assert_eq!(Some('\n'), ch); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); From 91ed65f8d2809d631ab5e19c7e5b6c1664681e8d Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 22 Jul 2016 21:57:37 +0200 Subject: [PATCH 0070/1201] Replace RawReader.next calls by RawReader.next_key Iterate on keys pressed instead of chars --- src/consts.rs | 4 +++ src/lib.rs | 79 ++++++++++++++++++++++++--------------------------- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index b762ede5eb..e98703accc 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -39,11 +39,15 @@ pub enum KeyPress { ESC_T, ESC_U, ESC_Y, + Char(char), } #[cfg_attr(feature="clippy", allow(match_same_arms))] #[cfg(unix)] pub fn char_to_key_press(c: char) -> KeyPress { + if !c.is_control() { + return KeyPress::Char(c); + } match c { '\x00' => KeyPress::NULL, '\x01' => KeyPress::CTRL_A, diff --git a/src/lib.rs b/src/lib.rs index 774ffddc37..d74c67cb65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -700,7 +700,7 @@ fn edit_history_next(s: &mut State, history: &History, prev: bool) -> Result<()> fn complete_line(rdr: &mut RawReader, s: &mut State, completer: &Completer) - -> Result> { + -> Result> { let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); if candidates.is_empty() { try!(beep()); @@ -708,7 +708,7 @@ fn complete_line(rdr: &mut RawReader, } else { // Save the current edited line before to overwrite it s.backup(); - let mut ch; + let mut key; let mut i = 0; loop { // Show completion or original buffer @@ -722,8 +722,7 @@ fn complete_line(rdr: &mut RawReader, s.snapshot(); } - ch = try!(rdr.next()); - let key = rdr.char_to_key_press(ch); + key = try!(rdr.next_key()); match key { KeyPress::TAB => { i = (i + 1) % (candidates.len() + 1); // Circular @@ -744,7 +743,7 @@ fn complete_line(rdr: &mut RawReader, } } } - Ok(Some(ch)) + Ok(Some(key)) } } @@ -765,7 +764,6 @@ fn reverse_incremental_search(rdr: &mut RawReader, let mut reverse = true; let mut success = true; - let mut ch; let mut key; // Display the reverse-i-search prompt and process chars loop { @@ -776,14 +774,10 @@ fn reverse_incremental_search(rdr: &mut RawReader, }; try!(s.refresh_prompt_and_line(&prompt)); - ch = try!(rdr.next()); - if !rdr.is_control(ch) { - search_buf.push(ch); + key = try!(rdr.next_key()); + if let KeyPress::Char(c) = key { + search_buf.push(c); } else { - key = rdr.char_to_key_press(ch); - if key == KeyPress::ESC { - key = try!(rdr.escape_sequence()); - } match key { KeyPress::CTRL_H | KeyPress::BACKSPACE => { search_buf.pop(); @@ -842,6 +836,22 @@ impl RawReader { Ok(RawReader { chars: stdin.chars() }) } + fn next_key(&mut self) -> Result { + use consts::char_to_key_press; + + let c = try!(self.next()); + if !c.is_control() { + return Ok(KeyPress::Char(c)); + } + + let mut key = char_to_key_press(c); + if key == KeyPress::ESC { + // escape sequence + key = try!(self.escape_sequence()); + } + Ok(key) + } + fn next(&mut self) -> Result { match self.chars.next() { Some(c) => { @@ -851,15 +861,6 @@ impl RawReader { } } - fn is_control(&self, c: char) -> bool { - c.is_control() - } - - fn char_to_key_press(&self, c: char) -> KeyPress { - use consts::char_to_key_press; - char_to_key_press(c) // TODO directly handle ESC + escape_sequence - } - fn escape_sequence(&mut self) -> Result { // Read the next two bytes representing the escape sequence. let seq1 = try!(self.next()); @@ -1074,31 +1075,29 @@ fn readline_edit(prompt: &str, let mut rdr = try!(RawReader::new(io::stdin())); loop { - let c = rdr.next(); - if c.is_err() && SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { + let rk = rdr.next_key(); + if rk.is_err() && SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { s.update_columns(); try!(s.refresh_line()); continue; } - let mut ch = try!(c); - if !rdr.is_control(ch) { + let mut key = try!(rk); + if let KeyPress::Char(c) = key { kill_ring.reset(); - try!(edit_insert(&mut s, ch)); + try!(edit_insert(&mut s, c)); continue; } - let mut key = rdr.char_to_key_press(ch); // autocomplete if key == KeyPress::TAB && completer.is_some() { let next = try!(complete_line(&mut rdr, &mut s, completer.unwrap())); if next.is_some() { kill_ring.reset(); - ch = next.unwrap(); - if !rdr.is_control(ch) { - try!(edit_insert(&mut s, ch)); + key = next.unwrap(); + if let KeyPress::Char(c) = key { + try!(edit_insert(&mut s, c)); continue; } - key = rdr.char_to_key_press(ch); } else { continue; } @@ -1110,12 +1109,8 @@ fn readline_edit(prompt: &str, } else { continue; } - } else if key == KeyPress::ESC { - // escape sequence - key = try!(rdr.escape_sequence()); - if key == KeyPress::UNKNOWN_ESC_SEQ { - continue; - } + } else if key == KeyPress::UNKNOWN_ESC_SEQ { + continue; } match key { @@ -1278,8 +1273,7 @@ fn readline_edit(prompt: &str, } _ => { kill_ring.reset(); - // Insert the character typed. - try!(edit_insert(&mut s, ch)) + // Ignore the character typed. } } } @@ -1452,6 +1446,7 @@ mod test { use line_buffer::LineBuffer; use history::History; use completion::Completer; + use consts::KeyPress; use State; use super::{Handle, RawReader, Result}; @@ -1536,8 +1531,8 @@ mod test { let input = b"\n"; let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; - let ch = super::complete_line(&mut rdr, &mut s, &completer).unwrap(); - assert_eq!(Some('\n'), ch); + let key = super::complete_line(&mut rdr, &mut s, &completer).unwrap(); + assert_eq!(Some(KeyPress::CTRL_J), key); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); } From 0570614c518838e1082185fb40070bb944e37327 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 22 Jul 2016 22:04:02 +0200 Subject: [PATCH 0071/1201] Rename RawReader.next to next_char --- src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d74c67cb65..7718e7606a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -839,7 +839,7 @@ impl RawReader { fn next_key(&mut self) -> Result { use consts::char_to_key_press; - let c = try!(self.next()); + let c = try!(self.next_char()); if !c.is_control() { return Ok(KeyPress::Char(c)); } @@ -852,7 +852,7 @@ impl RawReader { Ok(key) } - fn next(&mut self) -> Result { + fn next_char(&mut self) -> Result { match self.chars.next() { Some(c) => { Ok(try!(c)) // TODO SIGWINCH @@ -863,13 +863,13 @@ impl RawReader { fn escape_sequence(&mut self) -> Result { // Read the next two bytes representing the escape sequence. - let seq1 = try!(self.next()); + let seq1 = try!(self.next_char()); if seq1 == '[' { // ESC [ sequences. - let seq2 = try!(self.next()); + let seq2 = try!(self.next_char()); if seq2.is_digit(10) { // Extended escape, read additional byte. - let seq3 = try!(self.next()); + let seq3 = try!(self.next_char()); if seq3 == '~' { match seq2 { '3' => Ok(KeyPress::ESC_SEQ_DELETE), @@ -893,7 +893,7 @@ impl RawReader { } } else if seq1 == 'O' { // ESC O sequences. - let seq2 = try!(self.next()); + let seq2 = try!(self.next_char()); match seq2 { 'F' => Ok(KeyPress::CTRL_E), 'H' => Ok(KeyPress::CTRL_A), From 8c738aba0401595d0d0ab3fdc20ff17a6cfc29a3 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 22 Jul 2016 22:15:18 +0200 Subject: [PATCH 0072/1201] Update README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7ade1f1859..0a463cb7ed 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # RustyLine [![Build Status](https://travis-ci.org/kkawakam/rustyline.svg?branch=master)](https://travis-ci.org/kkawakam/rustyline) +[![Build Status](https://ci.appveyor.com/api/projects/status/github/gwenn/rustyline?branch=master&svg=true)](https://ci.appveyor.com/project/gwenn/rustyline) [![Clippy Linting Result](https://clippy.bashy.io/github/kkawakam/rustyline/master/badge.svg)](https://clippy.bashy.io/github/kkawakam/rustyline/master/log) [![](http://meritbadge.herokuapp.com/rustyline)](https://crates.io/crates/rustyline) @@ -106,6 +107,8 @@ Alt-BackSpace | Kill from the start of the current word, or, if between words, t ## ToDo - Show completion list + - Undos + - Read input with timeout to properly handle single ESC key - expose an API callable from C ## Wine From 4a8d950a9e6554e6bfc60ddb27f16b3062821441 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 23 Jul 2016 07:07:58 +0200 Subject: [PATCH 0073/1201] First RawReader.next_key impl for windows --- src/error.rs | 6 +++ src/lib.rs | 126 ++++++++++++++++++++------------------------------- 2 files changed, 55 insertions(+), 77 deletions(-) diff --git a/src/error.rs b/src/error.rs index 3c3b2e9590..8e4aaccafc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,6 +20,8 @@ pub enum ReadlineError { Eof, /// Ctrl-C Interrupted, + #[cfg(windows)] + BufferSizeEvent, } impl fmt::Display for ReadlineError { @@ -31,6 +33,8 @@ impl fmt::Display for ReadlineError { ReadlineError::Char(ref err) => err.fmt(f), ReadlineError::Eof => write!(f, "EOF"), ReadlineError::Interrupted => write!(f, "Interrupted"), + #[cfg(windows)] + ReadlineError::BufferSizeEvent => write!(f, "BufferSizeEvent"), } } } @@ -44,6 +48,8 @@ impl error::Error for ReadlineError { ReadlineError::Char(ref err) => err.description(), ReadlineError::Eof => "EOF", ReadlineError::Interrupted => "Interrupted", + #[cfg(windows)] + ReadlineError::BufferSizeEvent => "BufferSizeEvent", } } } diff --git a/src/lib.rs b/src/lib.rs index 7718e7606a..97a0c871d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,14 +34,12 @@ pub mod history; mod kill_ring; pub mod line_buffer; -#[cfg(windows)] -use std::cell::RefCell; use std::fmt; use std::io::{self, Read, Write}; +#[cfg(windows)] +use std::marker::PhantomData; use std::mem; use std::path::Path; -#[cfg(windows)] -use std::rc::Rc; use std::result; #[cfg(unix)] use std::sync; @@ -927,72 +925,23 @@ impl RawReader { #[cfg(windows)] struct RawReader { handle: winapi::HANDLE, - key_state: Rc>, - chars: io::Chars, -} -#[cfg(windows)] -struct KeyState { - ctrl: bool, - meta: bool, + buf: Option, + phantom: PhantomData, } #[cfg(windows)] impl RawReader { - fn new() -> Result> { + fn new(stdin: R) -> Result> { let handle = try!(get_std_handle(STDIN_FILENO)); Ok(RawReader { handle: handle, - key_state: Rc::new(RefCell::new(KeyState { - ctrl: false, - meta: false, - })), + buf: None, }) } - fn next(&mut self) -> Result { - match self.chars.next() { - Some(c) => { - Ok(try!(c)) // TODO SIGWINCH - } - None => Err(error::ReadlineError::Eof), - } - } - - fn is_control(&self, c: char) -> bool { - c.is_control() || self.key_state.borrow().ctrl - } - - fn char_to_key_press(&self, c: char) -> KeyPress { - unimplemented!() - } - - fn escape_sequence(&mut self) -> Result { - Ok(KeyPress::UNKNOWN_ESC_SEQ) - } - - fn single_byte(c: u8, buf: &mut [u8]) -> io::Result { - buf[0] = c; - Ok(1) - } - fn wide_char_to_multi_byte(c: u16) -> io::Result<([u8; 4], usize)> { - use std::ptr; - const WC_COMPOSITECHECK: winapi::DWORD = 0x00000200; // use composite chars - let mut bytes = [0u8; 4]; - let len = check!(kernel32::WideCharToMultiByte(winapi::CP_ACP, - WC_COMPOSITECHECK, - &c, - 1, - (&mut bytes).as_mut_ptr() as *mut i8, - 4, - ptr::null(), - ptr::null_mut())); - Ok((bytes, len as usize)) - } -} + fn next_key(&mut self) -> Result { + use std::char::decode_utf16; -#[cfg(windows)] -impl Read for RawReader { - fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; let mut count = 0; let mut esc_seen = false; @@ -1002,7 +951,7 @@ impl Read for RawReader { // TODO ENABLE_WINDOW_INPUT ??? if rec.EventType == winapi::WINDOW_BUFFER_SIZE_EVENT { SIGWINCH.store(true, atomic::Ordering::SeqCst); - return Err(io::Error::new(io::ErrorKind::Other, "WINDOW_BUFFER_SIZE_EVENT")); + return Err(error::ReadlineError::BufferSizeEvent); } else if rec.EventType != winapi::KEY_EVENT { continue; } @@ -1013,37 +962,58 @@ impl Read for RawReader { } let key_state = self.key_state.borrow_mut(); - key_state.ctrl = key_event.dwControlKeyState & - (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) == - (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED); - key_state.meta = (key_event.dwControlKeyState & - (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED) == - (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED)) || - esc_seen; + let ctrl = key_event.dwControlKeyState & + (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) == + (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED); + let meta = (key_event.dwControlKeyState & + (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED) == + (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED)) || + esc_seen; // TODO How to support surrogate pair ? let utf16 = key_event.UnicodeChar; if utf16 == 0 { match key_event.wVirtualKeyCode as i32 { - winapi::VK_LEFT => return RawReader::single_byte(b'\x02', buf), - winapi::VK_RIGHT => return RawReader::single_byte(b'\x06', buf), - winapi::VK_UP => return RawReader::single_byte(b'\x10', buf), - winapi::VK_DOWN => return RawReader::single_byte(b'\x0e', buf), - // winapi::VK_DELETE => b"\x1b[3~", - winapi::VK_HOME => return RawReader::single_byte(b'\x01', buf), - winapi::VK_END => return RawReader::single_byte(b'\x05', buf), + winapi::VK_LEFT => return Ok(KeyPress::CTRL_B), + winapi::VK_RIGHT => return Ok(KeyPress::CTRL_F), + winapi::VK_UP => return Ok(KeyPress::CTRL_P), + winapi::VK_DOWN => return Ok(KeyPress::CTRL_N), + winapi::VK_DELETE => return Ok(KeyPress::ESC_SEQ_DELETE), + winapi::VK_HOME => return Ok(KeyPress::CTRL_A), + winapi::VK_END => return Ok(KeyPress::CTRL_E), _ => continue, }; } else if utf16 == 27 { esc_seen = true; continue; } else { + if ctrl { + unimplemented!() + } else if meta { + unimplemented!() + } else { + self.buf = Some(utf16); + match decode_utf16(self).next() { + Some(item) => Ok(KeyPress::Char(try!(item))), + None => return Err(error::ReadlineError::Eof), + } + } let (bytes, len) = try!(RawReader::wide_char_to_multi_byte(utf16)); return (&bytes[..len]).read(buf); } } } } +#[cfg(windows)] +impl Iterator for RawReader { + type Item = u16; + + fn next(&mut self) -> Option { + let buf = self.buf; + self.buf = None; + buf + } +} #[cfg(unix)] fn stdout_handle() -> Result { @@ -1187,9 +1157,11 @@ fn readline_edit(prompt: &str, KeyPress::CTRL_V => { // Quoted insert kill_ring.reset(); - let c = rdr.next(); - let ch = try!(c); - try!(edit_insert(&mut s, ch)) + let rk = rdr.next_key(); + let key = try!(rk); + if let KeyPress::Char(c) = key { + try!(edit_insert(&mut s, c)) + } } KeyPress::CTRL_W => { // Kill the word behind point, using white space as a word boundary From e87f0878f7904b4972d2e6db05b64887f729b99a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 23 Jul 2016 07:23:20 +0200 Subject: [PATCH 0074/1201] Update readme --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0a463cb7ed..27bc63c144 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # RustyLine -[![Build Status](https://travis-ci.org/kkawakam/rustyline.svg?branch=master)](https://travis-ci.org/kkawakam/rustyline) +[![Build Status](https://travis-ci.org/gwenn/rustyline.svg?branch=master)](https://travis-ci.org/gwenn/rustyline) [![Build Status](https://ci.appveyor.com/api/projects/status/github/gwenn/rustyline?branch=master&svg=true)](https://ci.appveyor.com/project/gwenn/rustyline) -[![Clippy Linting Result](https://clippy.bashy.io/github/kkawakam/rustyline/master/badge.svg)](https://clippy.bashy.io/github/kkawakam/rustyline/master/log) +[![Clippy Linting Result](https://clippy.bashy.io/github/gwenn/rustyline/master/badge.svg)](https://clippy.bashy.io/github/gwenn/rustyline/master/log) [![](http://meritbadge.herokuapp.com/rustyline)](https://crates.io/crates/rustyline) Readline implementation in Rust that is based on [Antirez' Linenoise](https://github.com/antirez/linenoise) @@ -119,4 +119,11 @@ $ cargo run --example example --target 'x86_64-pc-windows-gnu' Error: Io(Error { repr: Os { code: 6, message: "Invalid handle." } }) $ wineconsole --backend=curses target/x86_64-pc-windows-gnu/debug/examples/example.exe ... -``` \ No newline at end of file +``` + +## Similar projects + + - [copperline](https://github.com/srijs/rust-copperline) (Rust) + - [linenoise-ng](https://github.com/arangodb/linenoise-ng) (C++) + - [liner](https://github.com/peterh/liner) (Go) + - [readline](https://github.com/chzyer/readline) (Go) From a84ee62e9c2d77b83622f81e3f70b012d28ea969 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 23 Jul 2016 07:43:57 +0200 Subject: [PATCH 0075/1201] Fix RawReader impl for windows --- src/error.rs | 16 ++++++++++++++++ src/lib.rs | 8 +++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/error.rs b/src/error.rs index 8e4aaccafc..11335a7da5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,6 @@ //! Contains error type for handling I/O and Errno errors +#[cfg(windows)] +use std::char; use std::io; use std::error; use std::fmt; @@ -22,6 +24,8 @@ pub enum ReadlineError { Interrupted, #[cfg(windows)] BufferSizeEvent, + #[cfg(windows)] + Decode(char::DecodeUtf16Error), } impl fmt::Display for ReadlineError { @@ -35,6 +39,8 @@ impl fmt::Display for ReadlineError { ReadlineError::Interrupted => write!(f, "Interrupted"), #[cfg(windows)] ReadlineError::BufferSizeEvent => write!(f, "BufferSizeEvent"), + #[cfg(windows)] + ReadlineError::Decode(ref err) => err.fmt(f), } } } @@ -50,6 +56,8 @@ impl error::Error for ReadlineError { ReadlineError::Interrupted => "Interrupted", #[cfg(windows)] ReadlineError::BufferSizeEvent => "BufferSizeEvent", + #[cfg(windows)] + ReadlineError::Decode(ref err) => err.description(), } } } @@ -67,8 +75,16 @@ impl From for ReadlineError { } } +#[cfg(unix)] impl From for ReadlineError { fn from(err: io::CharsError) -> ReadlineError { ReadlineError::Char(err) } } + +#[cfg(windows)] +impl From for ReadlineError { + fn from(err: char::DecodeUtf16Error) -> ReadlineError { + ReadlineError::Decode(err) + } +} diff --git a/src/lib.rs b/src/lib.rs index 97a0c871d7..a6cb515d47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -936,6 +936,7 @@ impl RawReader { Ok(RawReader { handle: handle, buf: None, + phantom: PhantomData, }) } @@ -946,7 +947,7 @@ impl RawReader { let mut count = 0; let mut esc_seen = false; loop { - check!(kernel32::ReadConsoleInputW(self.0, &mut rec, 1 as winapi::DWORD, &mut count)); + check!(kernel32::ReadConsoleInputW(self.handle, &mut rec, 1 as winapi::DWORD, &mut count)); // TODO ENABLE_WINDOW_INPUT ??? if rec.EventType == winapi::WINDOW_BUFFER_SIZE_EVENT { @@ -961,7 +962,6 @@ impl RawReader { continue; } - let key_state = self.key_state.borrow_mut(); let ctrl = key_event.dwControlKeyState & (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) == (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED); @@ -970,7 +970,6 @@ impl RawReader { (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED)) || esc_seen; - // TODO How to support surrogate pair ? let utf16 = key_event.UnicodeChar; if utf16 == 0 { match key_event.wVirtualKeyCode as i32 { @@ -992,14 +991,13 @@ impl RawReader { } else if meta { unimplemented!() } else { + // TODO How to support surrogate pair ? self.buf = Some(utf16); match decode_utf16(self).next() { Some(item) => Ok(KeyPress::Char(try!(item))), None => return Err(error::ReadlineError::Eof), } } - let (bytes, len) = try!(RawReader::wide_char_to_multi_byte(utf16)); - return (&bytes[..len]).read(buf); } } } From 1a3a71fe33a6271d5e996123d18b87c1a9e3502f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 23 Jul 2016 07:53:07 +0200 Subject: [PATCH 0076/1201] Fix RawReader impl for windows --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a6cb515d47..97d1beffb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -994,9 +994,9 @@ impl RawReader { // TODO How to support surrogate pair ? self.buf = Some(utf16); match decode_utf16(self).next() { - Some(item) => Ok(KeyPress::Char(try!(item))), + Some(item) => return Ok(KeyPress::Char(try!(item))), None => return Err(error::ReadlineError::Eof), - } + }; } } } From 4254f5b934f8f93448065ff65f9a71689ab09f5a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 23 Jul 2016 07:59:44 +0200 Subject: [PATCH 0077/1201] Try to fix test freeze on windows --- src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 97d1beffb1..ec96fc68b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -947,7 +947,10 @@ impl RawReader { let mut count = 0; let mut esc_seen = false; loop { - check!(kernel32::ReadConsoleInputW(self.handle, &mut rec, 1 as winapi::DWORD, &mut count)); + check!(kernel32::ReadConsoleInputW(self.handle, + &mut rec, + 1 as winapi::DWORD, + &mut count)); // TODO ENABLE_WINDOW_INPUT ??? if rec.EventType == winapi::WINDOW_BUFFER_SIZE_EVENT { @@ -1495,6 +1498,7 @@ mod test { } #[test] + #[cfg(unix)] fn complete_line() { let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); From 83af4e6d4f1383eee2b22beb74d0fe45f00d5006 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 23 Jul 2016 08:08:16 +0200 Subject: [PATCH 0078/1201] Fix some warnings --- src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index ec96fc68b6..74182ebba4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -931,7 +931,7 @@ struct RawReader { #[cfg(windows)] impl RawReader { - fn new(stdin: R) -> Result> { + fn new(_: R) -> Result> { let handle = try!(get_std_handle(STDIN_FILENO)); Ok(RawReader { handle: handle, @@ -1418,7 +1418,9 @@ mod test { use std::io::Write; use line_buffer::LineBuffer; use history::History; + #[cfg(unix)] use completion::Completer; + #[cfg(unix)] use consts::KeyPress; use State; use super::{Handle, RawReader, Result}; @@ -1490,7 +1492,9 @@ mod test { assert_eq!(line, s.line.as_str()); } + #[cfg(unix)] struct SimpleCompleter; + #[cfg(unix)] impl Completer for SimpleCompleter { fn complete(&self, line: &str, _pos: usize) -> Result<(usize, Vec)> { Ok((0, vec![line.to_string() + "t"])) From 5e34a111bfa3d27c3433b3b4263abfa90b4dc7ed Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 23 Jul 2016 10:06:44 +0200 Subject: [PATCH 0079/1201] Move char_to_key_press in RawReader impl for unix --- README.md | 1 + src/consts.rs | 38 ------------------------------ src/error.rs | 6 ++--- src/lib.rs | 65 +++++++++++++++++++++++++++++++++++++++------------ 4 files changed, 54 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 27bc63c144..190f2e288e 100644 --- a/README.md +++ b/README.md @@ -127,3 +127,4 @@ $ wineconsole --backend=curses target/x86_64-pc-windows-gnu/debug/examples/examp - [linenoise-ng](https://github.com/arangodb/linenoise-ng) (C++) - [liner](https://github.com/peterh/liner) (Go) - [readline](https://github.com/chzyer/readline) (Go) + - [haskeline](https://github.com/judah/haskeline) (Haskell) diff --git a/src/consts.rs b/src/consts.rs index e98703accc..081a075460 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,5 +1,4 @@ - #[derive(Debug, Clone, PartialEq, Copy)] pub enum KeyPress { NULL, @@ -41,40 +40,3 @@ pub enum KeyPress { ESC_Y, Char(char), } - -#[cfg_attr(feature="clippy", allow(match_same_arms))] -#[cfg(unix)] -pub fn char_to_key_press(c: char) -> KeyPress { - if !c.is_control() { - return KeyPress::Char(c); - } - match c { - '\x00' => KeyPress::NULL, - '\x01' => KeyPress::CTRL_A, - '\x02' => KeyPress::CTRL_B, - '\x03' => KeyPress::CTRL_C, - '\x04' => KeyPress::CTRL_D, - '\x05' => KeyPress::CTRL_E, - '\x06' => KeyPress::CTRL_F, - '\x07' => KeyPress::CTRL_G, - '\x08' => KeyPress::CTRL_H, - '\x09' => KeyPress::TAB, - '\x0a' => KeyPress::CTRL_J, - '\x0b' => KeyPress::CTRL_K, - '\x0c' => KeyPress::CTRL_L, - '\x0d' => KeyPress::ENTER, - '\x0e' => KeyPress::CTRL_N, - '\x10' => KeyPress::CTRL_P, - '\x12' => KeyPress::CTRL_R, - '\x13' => KeyPress::CTRL_S, - '\x14' => KeyPress::CTRL_T, - '\x15' => KeyPress::CTRL_U, - '\x16' => KeyPress::CTRL_V, - '\x17' => KeyPress::CTRL_W, - '\x19' => KeyPress::CTRL_Y, - '\x1a' => KeyPress::CTRL_Z, - '\x1b' => KeyPress::ESC, - '\x7f' => KeyPress::BACKSPACE, - _ => KeyPress::NULL, - } -} diff --git a/src/error.rs b/src/error.rs index 11335a7da5..98c148fdd6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,7 +23,7 @@ pub enum ReadlineError { /// Ctrl-C Interrupted, #[cfg(windows)] - BufferSizeEvent, + WindowResize, #[cfg(windows)] Decode(char::DecodeUtf16Error), } @@ -38,7 +38,7 @@ impl fmt::Display for ReadlineError { ReadlineError::Eof => write!(f, "EOF"), ReadlineError::Interrupted => write!(f, "Interrupted"), #[cfg(windows)] - ReadlineError::BufferSizeEvent => write!(f, "BufferSizeEvent"), + ReadlineError::WindowResize => write!(f, "WindowResize"), #[cfg(windows)] ReadlineError::Decode(ref err) => err.fmt(f), } @@ -55,7 +55,7 @@ impl error::Error for ReadlineError { ReadlineError::Eof => "EOF", ReadlineError::Interrupted => "Interrupted", #[cfg(windows)] - ReadlineError::BufferSizeEvent => "BufferSizeEvent", + ReadlineError::WindowResize => "WindowResize", #[cfg(windows)] ReadlineError::Decode(ref err) => err.description(), } diff --git a/src/lib.rs b/src/lib.rs index 74182ebba4..bb03c9d635 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -835,14 +835,12 @@ impl RawReader { } fn next_key(&mut self) -> Result { - use consts::char_to_key_press; - let c = try!(self.next_char()); if !c.is_control() { return Ok(KeyPress::Char(c)); } - let mut key = char_to_key_press(c); + let mut key = Self::char_to_key_press(c); if key == KeyPress::ESC { // escape sequence key = try!(self.escape_sequence()); @@ -859,6 +857,42 @@ impl RawReader { } } + #[cfg_attr(feature="clippy", allow(match_same_arms))] + fn char_to_key_press(c: char) -> KeyPress { + if !c.is_control() { + return KeyPress::Char(c); + } + match c { + '\x00' => KeyPress::NULL, + '\x01' => KeyPress::CTRL_A, + '\x02' => KeyPress::CTRL_B, + '\x03' => KeyPress::CTRL_C, + '\x04' => KeyPress::CTRL_D, + '\x05' => KeyPress::CTRL_E, + '\x06' => KeyPress::CTRL_F, + '\x07' => KeyPress::CTRL_G, + '\x08' => KeyPress::CTRL_H, + '\x09' => KeyPress::TAB, + '\x0a' => KeyPress::CTRL_J, + '\x0b' => KeyPress::CTRL_K, + '\x0c' => KeyPress::CTRL_L, + '\x0d' => KeyPress::ENTER, + '\x0e' => KeyPress::CTRL_N, + '\x10' => KeyPress::CTRL_P, + '\x12' => KeyPress::CTRL_R, + '\x13' => KeyPress::CTRL_S, + '\x14' => KeyPress::CTRL_T, + '\x15' => KeyPress::CTRL_U, + '\x16' => KeyPress::CTRL_V, + '\x17' => KeyPress::CTRL_W, + '\x19' => KeyPress::CTRL_Y, + '\x1a' => KeyPress::CTRL_Z, + '\x1b' => KeyPress::ESC, + '\x7f' => KeyPress::BACKSPACE, + _ => KeyPress::NULL, + } + } + fn escape_sequence(&mut self) -> Result { // Read the next two bytes representing the escape sequence. let seq1 = try!(self.next_char()); @@ -955,7 +989,7 @@ impl RawReader { // TODO ENABLE_WINDOW_INPUT ??? if rec.EventType == winapi::WINDOW_BUFFER_SIZE_EVENT { SIGWINCH.store(true, atomic::Ordering::SeqCst); - return Err(error::ReadlineError::BufferSizeEvent); + return Err(error::ReadlineError::WindowResize); } else if rec.EventType != winapi::KEY_EVENT { continue; } @@ -966,12 +1000,11 @@ impl RawReader { } let ctrl = key_event.dwControlKeyState & - (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) == - (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED); + (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) != + 0; let meta = (key_event.dwControlKeyState & - (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED) == - (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED)) || - esc_seen; + (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED) != + 0) || esc_seen; let utf16 = key_event.UnicodeChar; if utf16 == 0 { @@ -989,17 +1022,19 @@ impl RawReader { esc_seen = true; continue; } else { + // TODO How to support surrogate pair ? + self.buf = Some(utf16); + let orc = decode_utf16(self).next(); + if orc.is_none() { + return Err(error::ReadlineError::Eof); + } + let c = try!(orc.unwrap()); if ctrl { unimplemented!() } else if meta { unimplemented!() } else { - // TODO How to support surrogate pair ? - self.buf = Some(utf16); - match decode_utf16(self).next() { - Some(item) => return Ok(KeyPress::Char(try!(item))), - None => return Err(error::ReadlineError::Eof), - }; + return Ok(KeyPress::Char(c)); } } } From 6f3f73f5fed0bbf8b1378a0978c82a379e077974 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 23 Jul 2016 13:06:43 +0200 Subject: [PATCH 0080/1201] Fix RawReader.next_key for windows --- src/lib.rs | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bb03c9d635..59b54c003a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -994,6 +994,7 @@ impl RawReader { continue; } let key_event = unsafe { rec.KeyEvent() }; + // writeln!(io::stderr(), "key_event: {:?}", key_event).unwrap(); if key_event.bKeyDown == 0 && key_event.wVirtualKeyCode != winapi::VK_MENU as winapi::WORD { continue; @@ -1030,9 +1031,45 @@ impl RawReader { } let c = try!(orc.unwrap()); if ctrl { - unimplemented!() + match c { + 'a' | 'A' => return Ok(KeyPress::CTRL_A), + 'b' | 'B' => return Ok(KeyPress::CTRL_B), + 'c' | 'C' => return Ok(KeyPress::CTRL_C), + 'd' | 'D' => return Ok(KeyPress::CTRL_D), + 'e' | 'E' => return Ok(KeyPress::CTRL_E), + 'f' | 'F' => return Ok(KeyPress::CTRL_F), + 'g' | 'G' => return Ok(KeyPress::CTRL_G), + // TODO ctrl + meta + H + 'h' | 'H' => return Ok(KeyPress::CTRL_H), + 'i' | 'I' => return Ok(KeyPress::TAB), + 'j' | 'J' => return Ok(KeyPress::CTRL_J), + 'k' | 'K' => return Ok(KeyPress::CTRL_K), + 'l' | 'L' => return Ok(KeyPress::CTRL_L), + 'm' | 'M' => return Ok(KeyPress::ENTER), + 'n' | 'N' => return Ok(KeyPress::CTRL_N), + 'p' | 'P' => return Ok(KeyPress::CTRL_P), + 'r' | 'R' => return Ok(KeyPress::CTRL_R), + 's' | 'S' => return Ok(KeyPress::CTRL_S), + 't' | 'T' => return Ok(KeyPress::CTRL_T), + 'u' | 'U' => return Ok(KeyPress::CTRL_U), + 'v' | 'V' => return Ok(KeyPress::CTRL_V), + 'w' | 'W' => return Ok(KeyPress::CTRL_W), + 'y' | 'Y' => return Ok(KeyPress::CTRL_Y), + 'z' | 'Z' => return Ok(KeyPress::CTRL_Z), + _ => return Ok(KeyPress::UNKNOWN_ESC_SEQ), + } } else if meta { - unimplemented!() + match c { + 'b' | 'B' => return Ok(KeyPress::ESC_B), + 'c' | 'C' => return Ok(KeyPress::ESC_C), + 'd' | 'D' => return Ok(KeyPress::ESC_D), + 'f' | 'F' => return Ok(KeyPress::ESC_F), + 'l' | 'L' => return Ok(KeyPress::ESC_L), + 't' | 'T' => return Ok(KeyPress::ESC_T), + 'u' | 'U' => return Ok(KeyPress::ESC_U), + 'y' | 'Y' => return Ok(KeyPress::ESC_Y), + _ => return Ok(KeyPress::UNKNOWN_ESC_SEQ), + } } else { return Ok(KeyPress::Char(c)); } From 7b8911072a7e21ee60d02b302fcfefbf6608919a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 23 Jul 2016 13:43:35 +0200 Subject: [PATCH 0081/1201] Use char_to_key_press on windows too... --- src/consts.rs | 36 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 43 ++----------------------------------------- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 081a075460..cbdf91a7b4 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -40,3 +40,39 @@ pub enum KeyPress { ESC_Y, Char(char), } + +#[cfg_attr(feature="clippy", allow(match_same_arms))] +pub fn char_to_key_press(c: char) -> KeyPress { + if !c.is_control() { + return KeyPress::Char(c); + } + match c { + '\x00' => KeyPress::NULL, + '\x01' => KeyPress::CTRL_A, + '\x02' => KeyPress::CTRL_B, + '\x03' => KeyPress::CTRL_C, + '\x04' => KeyPress::CTRL_D, + '\x05' => KeyPress::CTRL_E, + '\x06' => KeyPress::CTRL_F, + '\x07' => KeyPress::CTRL_G, + '\x08' => KeyPress::CTRL_H, + '\x09' => KeyPress::TAB, + '\x0a' => KeyPress::CTRL_J, + '\x0b' => KeyPress::CTRL_K, + '\x0c' => KeyPress::CTRL_L, + '\x0d' => KeyPress::ENTER, + '\x0e' => KeyPress::CTRL_N, + '\x10' => KeyPress::CTRL_P, + '\x12' => KeyPress::CTRL_R, + '\x13' => KeyPress::CTRL_S, + '\x14' => KeyPress::CTRL_T, + '\x15' => KeyPress::CTRL_U, + '\x16' => KeyPress::CTRL_V, + '\x17' => KeyPress::CTRL_W, + '\x19' => KeyPress::CTRL_Y, + '\x1a' => KeyPress::CTRL_Z, + '\x1b' => KeyPress::ESC, + '\x7f' => KeyPress::BACKSPACE, + _ => KeyPress::NULL, + } +} diff --git a/src/lib.rs b/src/lib.rs index 59b54c003a..7962dd4b04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -836,11 +836,8 @@ impl RawReader { fn next_key(&mut self) -> Result { let c = try!(self.next_char()); - if !c.is_control() { - return Ok(KeyPress::Char(c)); - } - let mut key = Self::char_to_key_press(c); + let mut key = consts::char_to_key_press(c); if key == KeyPress::ESC { // escape sequence key = try!(self.escape_sequence()); @@ -857,42 +854,6 @@ impl RawReader { } } - #[cfg_attr(feature="clippy", allow(match_same_arms))] - fn char_to_key_press(c: char) -> KeyPress { - if !c.is_control() { - return KeyPress::Char(c); - } - match c { - '\x00' => KeyPress::NULL, - '\x01' => KeyPress::CTRL_A, - '\x02' => KeyPress::CTRL_B, - '\x03' => KeyPress::CTRL_C, - '\x04' => KeyPress::CTRL_D, - '\x05' => KeyPress::CTRL_E, - '\x06' => KeyPress::CTRL_F, - '\x07' => KeyPress::CTRL_G, - '\x08' => KeyPress::CTRL_H, - '\x09' => KeyPress::TAB, - '\x0a' => KeyPress::CTRL_J, - '\x0b' => KeyPress::CTRL_K, - '\x0c' => KeyPress::CTRL_L, - '\x0d' => KeyPress::ENTER, - '\x0e' => KeyPress::CTRL_N, - '\x10' => KeyPress::CTRL_P, - '\x12' => KeyPress::CTRL_R, - '\x13' => KeyPress::CTRL_S, - '\x14' => KeyPress::CTRL_T, - '\x15' => KeyPress::CTRL_U, - '\x16' => KeyPress::CTRL_V, - '\x17' => KeyPress::CTRL_W, - '\x19' => KeyPress::CTRL_Y, - '\x1a' => KeyPress::CTRL_Z, - '\x1b' => KeyPress::ESC, - '\x7f' => KeyPress::BACKSPACE, - _ => KeyPress::NULL, - } - } - fn escape_sequence(&mut self) -> Result { // Read the next two bytes representing the escape sequence. let seq1 = try!(self.next_char()); @@ -1071,7 +1032,7 @@ impl RawReader { _ => return Ok(KeyPress::UNKNOWN_ESC_SEQ), } } else { - return Ok(KeyPress::Char(c)); + return Ok(consts::char_to_key_press(c)); } } } From 5b0e42ee1a445b2f3d22c37d3a5054306752f0bf Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 23 Jul 2016 13:59:09 +0200 Subject: [PATCH 0082/1201] Fix ctrl keys on windows. --- src/lib.rs | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7962dd4b04..d6b2f7ce6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -991,35 +991,7 @@ impl RawReader { return Err(error::ReadlineError::Eof); } let c = try!(orc.unwrap()); - if ctrl { - match c { - 'a' | 'A' => return Ok(KeyPress::CTRL_A), - 'b' | 'B' => return Ok(KeyPress::CTRL_B), - 'c' | 'C' => return Ok(KeyPress::CTRL_C), - 'd' | 'D' => return Ok(KeyPress::CTRL_D), - 'e' | 'E' => return Ok(KeyPress::CTRL_E), - 'f' | 'F' => return Ok(KeyPress::CTRL_F), - 'g' | 'G' => return Ok(KeyPress::CTRL_G), - // TODO ctrl + meta + H - 'h' | 'H' => return Ok(KeyPress::CTRL_H), - 'i' | 'I' => return Ok(KeyPress::TAB), - 'j' | 'J' => return Ok(KeyPress::CTRL_J), - 'k' | 'K' => return Ok(KeyPress::CTRL_K), - 'l' | 'L' => return Ok(KeyPress::CTRL_L), - 'm' | 'M' => return Ok(KeyPress::ENTER), - 'n' | 'N' => return Ok(KeyPress::CTRL_N), - 'p' | 'P' => return Ok(KeyPress::CTRL_P), - 'r' | 'R' => return Ok(KeyPress::CTRL_R), - 's' | 'S' => return Ok(KeyPress::CTRL_S), - 't' | 'T' => return Ok(KeyPress::CTRL_T), - 'u' | 'U' => return Ok(KeyPress::CTRL_U), - 'v' | 'V' => return Ok(KeyPress::CTRL_V), - 'w' | 'W' => return Ok(KeyPress::CTRL_W), - 'y' | 'Y' => return Ok(KeyPress::CTRL_Y), - 'z' | 'Z' => return Ok(KeyPress::CTRL_Z), - _ => return Ok(KeyPress::UNKNOWN_ESC_SEQ), - } - } else if meta { + if meta { match c { 'b' | 'B' => return Ok(KeyPress::ESC_B), 'c' | 'C' => return Ok(KeyPress::ESC_C), From 6737fd2abeeb598698fce298bcc67d326968165e Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 24 Jul 2016 12:13:45 +0200 Subject: [PATCH 0083/1201] Refactor KeyPress enum. --- README.md | 25 ++++---- src/consts.rs | 104 +++++++++++++-------------------- src/lib.rs | 155 +++++++++++++++++++++++++++----------------------- 3 files changed, 137 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index 190f2e288e..aaaafd9e89 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,8 @@ Ctrl-D | (if line *is* empty) End of File Ctrl-E, End | Move cursor to end of line Ctrl-F, Right| Move cursor one character right Ctrl-H, BackSpace | Delete character before cursor -Ctrl-J, Return | Finish the line entry +Ctrl-I, Tab | Next completion +Ctrl-J, Ctrl-M, Enter | Finish the line entry Ctrl-K | Delete from cursor to end of line Ctrl-L | Clear screen Ctrl-N, Down | Next match from history @@ -92,17 +93,16 @@ Ctrl-T | Transpose previous character with current character Ctrl-U | Delete from start of line to cursor Ctrl-V | Insert any special character without perfoming its associated action Ctrl-W | Delete word leading up to cursor (using white space as a word boundary) -Ctrl-Y | Paste from Yank buffer (Alt-Y to paste next yank instead) -Tab | Next completion -Alt-B, Alt-Left | Move cursor to previous word -Alt-C | Capitalize the current word -Alt-D | Delete forwards one word -Alt-F, Alt-Right | Move cursor to next word -Alt-L | Lower-case the next word -Alt-T | Transpose words -Alt-U | Upper-case the next word -Alt-Y | See Ctrl-Y -Alt-BackSpace | Kill from the start of the current word, or, if between words, to the start of the previous word +Ctrl-Y | Paste from Yank buffer (Meta-Y to paste next yank instead) +Meta-B, Alt-Left | Move cursor to previous word +Meta-C | Capitalize the current word +Meta-D | Delete forwards one word +Meta-F, Alt-Right | Move cursor to next word +Meta-L | Lower-case the next word +Meta-T | Transpose words +Meta-U | Upper-case the next word +Meta-Y | See Ctrl-Y +Meta-BackSpace | Kill from the start of the current word, or, if between words, to the start of the previous word ## ToDo @@ -124,6 +124,7 @@ $ wineconsole --backend=curses target/x86_64-pc-windows-gnu/debug/examples/examp ## Similar projects - [copperline](https://github.com/srijs/rust-copperline) (Rust) + - [liner](https://github.com/MovingtoMars/liner) (Rust) - [linenoise-ng](https://github.com/arangodb/linenoise-ng) (C++) - [liner](https://github.com/peterh/liner) (Go) - [readline](https://github.com/chzyer/readline) (Go) diff --git a/src/consts.rs b/src/consts.rs index cbdf91a7b4..bd7ffcd62e 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,44 +1,22 @@ #[derive(Debug, Clone, PartialEq, Copy)] pub enum KeyPress { - NULL, - CTRL_A, - CTRL_B, - CTRL_C, - CTRL_D, - CTRL_E, - CTRL_F, - CTRL_G, - CTRL_H, - TAB, - CTRL_J, - CTRL_K, - CTRL_L, - ENTER, - CTRL_N, - CTRL_P, - CTRL_R, - CTRL_S, - CTRL_T, - CTRL_U, - CTRL_V, - CTRL_W, - CTRL_Y, - CTRL_Z, - ESC, - BACKSPACE, UNKNOWN_ESC_SEQ, - ESC_SEQ_DELETE, - ESC_BACKSPACE, - ESC_B, - ESC_C, - ESC_D, - ESC_F, - ESC_L, - ESC_T, - ESC_U, - ESC_Y, + Backspace, Char(char), + Ctrl(char), + Delete, + Down, + End, + Enter, // Ctrl('M') + Esc, + Home, + Left, + Meta(char), + Null, + Right, + Tab, // Ctrl('I') + Up, } #[cfg_attr(feature="clippy", allow(match_same_arms))] @@ -47,32 +25,32 @@ pub fn char_to_key_press(c: char) -> KeyPress { return KeyPress::Char(c); } match c { - '\x00' => KeyPress::NULL, - '\x01' => KeyPress::CTRL_A, - '\x02' => KeyPress::CTRL_B, - '\x03' => KeyPress::CTRL_C, - '\x04' => KeyPress::CTRL_D, - '\x05' => KeyPress::CTRL_E, - '\x06' => KeyPress::CTRL_F, - '\x07' => KeyPress::CTRL_G, - '\x08' => KeyPress::CTRL_H, - '\x09' => KeyPress::TAB, - '\x0a' => KeyPress::CTRL_J, - '\x0b' => KeyPress::CTRL_K, - '\x0c' => KeyPress::CTRL_L, - '\x0d' => KeyPress::ENTER, - '\x0e' => KeyPress::CTRL_N, - '\x10' => KeyPress::CTRL_P, - '\x12' => KeyPress::CTRL_R, - '\x13' => KeyPress::CTRL_S, - '\x14' => KeyPress::CTRL_T, - '\x15' => KeyPress::CTRL_U, - '\x16' => KeyPress::CTRL_V, - '\x17' => KeyPress::CTRL_W, - '\x19' => KeyPress::CTRL_Y, - '\x1a' => KeyPress::CTRL_Z, - '\x1b' => KeyPress::ESC, - '\x7f' => KeyPress::BACKSPACE, - _ => KeyPress::NULL, + '\x00' => KeyPress::Null, + '\x01' => KeyPress::Ctrl('A'), + '\x02' => KeyPress::Ctrl('B'), + '\x03' => KeyPress::Ctrl('C'), + '\x04' => KeyPress::Ctrl('D'), + '\x05' => KeyPress::Ctrl('E'), + '\x06' => KeyPress::Ctrl('F'), + '\x07' => KeyPress::Ctrl('G'), + '\x08' => KeyPress::Backspace, + '\x09' => KeyPress::Tab, + '\x0a' => KeyPress::Ctrl('J'), + '\x0b' => KeyPress::Ctrl('K'), + '\x0c' => KeyPress::Ctrl('L'), + '\x0d' => KeyPress::Enter, + '\x0e' => KeyPress::Ctrl('N'), + '\x10' => KeyPress::Ctrl('P'), + '\x12' => KeyPress::Ctrl('R'), + '\x13' => KeyPress::Ctrl('S'), + '\x14' => KeyPress::Ctrl('T'), + '\x15' => KeyPress::Ctrl('U'), + '\x16' => KeyPress::Ctrl('V'), + '\x17' => KeyPress::Ctrl('W'), + '\x19' => KeyPress::Ctrl('Y'), + '\x1a' => KeyPress::Ctrl('Z'), + '\x1b' => KeyPress::Esc, + '\x7f' => KeyPress::Delete, + _ => KeyPress::Null, } } diff --git a/src/lib.rs b/src/lib.rs index d6b2f7ce6a..bd065a116b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -722,13 +722,13 @@ fn complete_line(rdr: &mut RawReader, key = try!(rdr.next_key()); match key { - KeyPress::TAB => { + KeyPress::Tab => { i = (i + 1) % (candidates.len() + 1); // Circular if i == candidates.len() { try!(beep()); } } - KeyPress::ESC => { + KeyPress::Esc => { // Re-show original buffer s.snapshot(); if i < candidates.len() { @@ -777,11 +777,12 @@ fn reverse_incremental_search(rdr: &mut RawReader, search_buf.push(c); } else { match key { - KeyPress::CTRL_H | KeyPress::BACKSPACE => { + KeyPress::Ctrl('H') | + KeyPress::Backspace => { search_buf.pop(); continue; } - KeyPress::CTRL_R => { + KeyPress::Ctrl('R') => { reverse = true; if history_idx > 0 { history_idx -= 1; @@ -790,7 +791,7 @@ fn reverse_incremental_search(rdr: &mut RawReader, continue; } } - KeyPress::CTRL_S => { + KeyPress::Ctrl('S') => { reverse = false; if history_idx < history.len() - 1 { history_idx += 1; @@ -799,7 +800,7 @@ fn reverse_incremental_search(rdr: &mut RawReader, continue; } } - KeyPress::CTRL_G => { + KeyPress::Ctrl('G') => { // Restore current edited line (before search) s.snapshot(); try!(s.refresh_line()); @@ -838,7 +839,7 @@ impl RawReader { let c = try!(self.next_char()); let mut key = consts::char_to_key_press(c); - if key == KeyPress::ESC { + if key == KeyPress::Esc { // escape sequence key = try!(self.escape_sequence()); } @@ -865,7 +866,7 @@ impl RawReader { let seq3 = try!(self.next_char()); if seq3 == '~' { match seq2 { - '3' => Ok(KeyPress::ESC_SEQ_DELETE), + '3' => Ok(KeyPress::Delete), // TODO '1' // Home // TODO '4' // End _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), @@ -875,12 +876,12 @@ impl RawReader { } } else { match seq2 { - 'A' => Ok(KeyPress::CTRL_P), // Up - 'B' => Ok(KeyPress::CTRL_N), // Down - 'C' => Ok(KeyPress::CTRL_F), // Right - 'D' => Ok(KeyPress::CTRL_B), // Left - 'F' => Ok(KeyPress::CTRL_E), // End - 'H' => Ok(KeyPress::CTRL_A), // Home + 'A' => Ok(KeyPress::Up), + 'B' => Ok(KeyPress::Down), + 'C' => Ok(KeyPress::Right), + 'D' => Ok(KeyPress::Left), + 'F' => Ok(KeyPress::End), + 'H' => Ok(KeyPress::Home), _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), } } @@ -888,8 +889,8 @@ impl RawReader { // ESC O sequences. let seq2 = try!(self.next_char()); match seq2 { - 'F' => Ok(KeyPress::CTRL_E), - 'H' => Ok(KeyPress::CTRL_A), + 'F' => Ok(KeyPress::End), + 'H' => Ok(KeyPress::Home), _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), } } else { @@ -899,17 +900,18 @@ impl RawReader { // TODO ESC-<: move to first entry in history // TODO ESC->: move to last entry in history match seq1 { - 'b' | 'B' => Ok(KeyPress::ESC_B), - 'c' | 'C' => Ok(KeyPress::ESC_C), - 'd' | 'D' => Ok(KeyPress::ESC_D), - 'f' | 'F' => Ok(KeyPress::ESC_F), - 'l' | 'L' => Ok(KeyPress::ESC_L), - 't' | 'T' => Ok(KeyPress::ESC_T), - 'u' | 'U' => Ok(KeyPress::ESC_U), - 'y' | 'Y' => Ok(KeyPress::ESC_Y), - '\x08' | '\x7f' => Ok(KeyPress::ESC_BACKSPACE), + 'b' | 'B' => Ok(KeyPress::Meta('B')), + 'c' | 'C' => Ok(KeyPress::Meta('C')), + 'd' | 'D' => Ok(KeyPress::Meta('D')), + 'f' | 'F' => Ok(KeyPress::Meta('F')), + 'l' | 'L' => Ok(KeyPress::Meta('L')), + 't' | 'T' => Ok(KeyPress::Meta('T')), + 'u' | 'U' => Ok(KeyPress::Meta('U')), + 'y' | 'Y' => Ok(KeyPress::Meta('Y')), + '\x08' => Ok(KeyPress::Meta('\x08')), // Backspace + '\x7f' => Ok(KeyPress::Meta('\x7f')), // Delete _ => { - // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::ESC, seq1).unwrap(); + // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::Esc, seq1).unwrap(); Ok(KeyPress::UNKNOWN_ESC_SEQ) } } @@ -971,13 +973,13 @@ impl RawReader { let utf16 = key_event.UnicodeChar; if utf16 == 0 { match key_event.wVirtualKeyCode as i32 { - winapi::VK_LEFT => return Ok(KeyPress::CTRL_B), - winapi::VK_RIGHT => return Ok(KeyPress::CTRL_F), - winapi::VK_UP => return Ok(KeyPress::CTRL_P), - winapi::VK_DOWN => return Ok(KeyPress::CTRL_N), - winapi::VK_DELETE => return Ok(KeyPress::ESC_SEQ_DELETE), - winapi::VK_HOME => return Ok(KeyPress::CTRL_A), - winapi::VK_END => return Ok(KeyPress::CTRL_E), + winapi::VK_LEFT => return Ok(KeyPress::Left), + winapi::VK_RIGHT => return Ok(KeyPress::Right), + winapi::VK_UP => return Ok(KeyPress::Up), + winapi::VK_DOWN => return Ok(KeyPress::Down), + winapi::VK_DELETE => return Ok(KeyPress::Delete), + winapi::VK_HOME => return Ok(KeyPress::Home), + winapi::VK_END => return Ok(KeyPress::End), _ => continue, }; } else if utf16 == 27 { @@ -993,14 +995,14 @@ impl RawReader { let c = try!(orc.unwrap()); if meta { match c { - 'b' | 'B' => return Ok(KeyPress::ESC_B), - 'c' | 'C' => return Ok(KeyPress::ESC_C), - 'd' | 'D' => return Ok(KeyPress::ESC_D), - 'f' | 'F' => return Ok(KeyPress::ESC_F), - 'l' | 'L' => return Ok(KeyPress::ESC_L), - 't' | 'T' => return Ok(KeyPress::ESC_T), - 'u' | 'U' => return Ok(KeyPress::ESC_U), - 'y' | 'Y' => return Ok(KeyPress::ESC_Y), + 'b' | 'B' => return Ok(KeyPress::Meta('B')), + 'c' | 'C' => return Ok(KeyPress::Meta('C')), + 'd' | 'D' => return Ok(KeyPress::Meta('D')), + 'f' | 'F' => return Ok(KeyPress::Meta('F')), + 'l' | 'L' => return Ok(KeyPress::Meta('L')), + 't' | 'T' => return Ok(KeyPress::Meta('T')), + 'u' | 'U' => return Ok(KeyPress::Meta('U')), + 'y' | 'Y' => return Ok(KeyPress::Meta('Y')), _ => return Ok(KeyPress::UNKNOWN_ESC_SEQ), } } else { @@ -1065,7 +1067,7 @@ fn readline_edit(prompt: &str, } // autocomplete - if key == KeyPress::TAB && completer.is_some() { + if key == KeyPress::Tab && completer.is_some() { let next = try!(complete_line(&mut rdr, &mut s, completer.unwrap())); if next.is_some() { kill_ring.reset(); @@ -1077,7 +1079,7 @@ fn readline_edit(prompt: &str, } else { continue; } - } else if key == KeyPress::CTRL_R { + } else if key == KeyPress::Ctrl('R') { // Search history backward let next = try!(reverse_incremental_search(&mut rdr, &mut s, history)); if next.is_some() { @@ -1090,21 +1092,23 @@ fn readline_edit(prompt: &str, } match key { - KeyPress::CTRL_A => { + KeyPress::Ctrl('A') | + KeyPress::Home => { kill_ring.reset(); // Move to the beginning of line. try!(edit_move_home(&mut s)) } - KeyPress::CTRL_B => { + KeyPress::Ctrl('B') | + KeyPress::Left => { kill_ring.reset(); // Move back a character. try!(edit_move_left(&mut s)) } - KeyPress::CTRL_C => { + KeyPress::Ctrl('C') => { kill_ring.reset(); return Err(error::ReadlineError::Interrupted); } - KeyPress::CTRL_D => { + KeyPress::Ctrl('D') => { kill_ring.reset(); if s.line.is_empty() { return Err(error::ReadlineError::Eof); @@ -1113,54 +1117,59 @@ fn readline_edit(prompt: &str, try!(edit_delete(&mut s)) } } - KeyPress::CTRL_E => { + KeyPress::Ctrl('E') | + KeyPress::End => { kill_ring.reset(); // Move to the end of line. try!(edit_move_end(&mut s)) } - KeyPress::CTRL_F => { + KeyPress::Ctrl('F') | + KeyPress::Right => { kill_ring.reset(); // Move forward a character. try!(edit_move_right(&mut s)) } - KeyPress::CTRL_H | KeyPress::BACKSPACE => { + KeyPress::Ctrl('H') | + KeyPress::Backspace => { kill_ring.reset(); // Delete one character backward. try!(edit_backspace(&mut s)) } - KeyPress::CTRL_K => { + KeyPress::Ctrl('K') => { // Kill the text from point to the end of the line. if let Some(text) = try!(edit_kill_line(&mut s)) { kill_ring.kill(&text, true) } } - KeyPress::CTRL_L => { + KeyPress::Ctrl('L') => { // Clear the screen leaving the current line at the top of the screen. try!(clear_screen(&mut s)); try!(s.refresh_line()) } - KeyPress::CTRL_N => { + KeyPress::Ctrl('N') | + KeyPress::Down => { kill_ring.reset(); // Fetch the next command from the history list. try!(edit_history_next(&mut s, history, false)) } - KeyPress::CTRL_P => { + KeyPress::Ctrl('P') | + KeyPress::Up => { kill_ring.reset(); // Fetch the previous command from the history list. try!(edit_history_next(&mut s, history, true)) } - KeyPress::CTRL_T => { + KeyPress::Ctrl('T') => { kill_ring.reset(); // Exchange the char before cursor with the character at cursor. try!(edit_transpose_chars(&mut s)) } - KeyPress::CTRL_U => { + KeyPress::Ctrl('U') => { // Kill backward from point to the beginning of the line. if let Some(text) = try!(edit_discard_line(&mut s)) { kill_ring.kill(&text, false) } } - KeyPress::CTRL_V => { + KeyPress::Ctrl('V') => { // Quoted insert kill_ring.reset(); let rk = rdr.next_key(); @@ -1169,33 +1178,35 @@ fn readline_edit(prompt: &str, try!(edit_insert(&mut s, c)) } } - KeyPress::CTRL_W => { + KeyPress::Ctrl('W') => { // Kill the word behind point, using white space as a word boundary if let Some(text) = try!(edit_delete_prev_word(&mut s, char::is_whitespace)) { kill_ring.kill(&text, false) } } - KeyPress::CTRL_Y => { + KeyPress::Ctrl('Y') => { // retrieve (yank) last item killed if let Some(text) = kill_ring.yank() { try!(edit_yank(&mut s, text)) } } #[cfg(unix)] - KeyPress::CTRL_Z => { + KeyPress::Ctrl('Z') => { try!(disable_raw_mode(original_mode)); try!(signal::raise(signal::SIGSTOP)); try!(enable_raw_mode()); // TODO original_mode may have changed try!(s.refresh_line()) } // TODO CTRL-_ // undo - KeyPress::ENTER | KeyPress::CTRL_J => { + KeyPress::Enter | + KeyPress::Ctrl('J') => { // Accept the line regardless of where the cursor is. kill_ring.reset(); try!(edit_move_end(&mut s)); break; } - KeyPress::ESC_BACKSPACE => { + KeyPress::Meta('\x08') | + KeyPress::Meta('\x7f') => { // kill one word backward // Kill from the cursor to the start of the current word, or, if between words, to the start of the previous word. if let Some(text) = try!(edit_delete_prev_word(&mut s, @@ -1203,49 +1214,49 @@ fn readline_edit(prompt: &str, kill_ring.kill(&text, false) } } - KeyPress::ESC_B => { + KeyPress::Meta('B') => { // move backwards one word kill_ring.reset(); try!(edit_move_to_prev_word(&mut s)) } - KeyPress::ESC_C => { + KeyPress::Meta('C') => { // capitalize word after point kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - KeyPress::ESC_D => { + KeyPress::Meta('D') => { // kill one word forward if let Some(text) = try!(edit_delete_word(&mut s)) { kill_ring.kill(&text, true) } } - KeyPress::ESC_F => { + KeyPress::Meta('F') => { // move forwards one word kill_ring.reset(); try!(edit_move_to_next_word(&mut s)) } - KeyPress::ESC_L => { + KeyPress::Meta('L') => { // lowercase word after point kill_ring.reset(); try!(edit_word(&mut s, WordAction::LOWERCASE)) } - KeyPress::ESC_T => { + KeyPress::Meta('T') => { // transpose words kill_ring.reset(); try!(edit_transpose_words(&mut s)) } - KeyPress::ESC_U => { + KeyPress::Meta('U') => { // uppercase word after point kill_ring.reset(); try!(edit_word(&mut s, WordAction::UPPERCASE)) } - KeyPress::ESC_Y => { + KeyPress::Meta('Y') => { // yank-pop if let Some((yank_size, text)) = kill_ring.yank_pop() { try!(edit_yank_pop(&mut s, yank_size, text)) } } - KeyPress::ESC_SEQ_DELETE => { + KeyPress::Delete => { kill_ring.reset(); try!(edit_delete(&mut s)) } @@ -1515,7 +1526,7 @@ mod test { let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; let key = super::complete_line(&mut rdr, &mut s, &completer).unwrap(); - assert_eq!(Some(KeyPress::CTRL_J), key); + assert_eq!(Some(KeyPress::Ctrl('J')), key); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); } From 8f18ef77cbdc6914f241fc3360076ab86bd4c266 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 24 Jul 2016 12:42:31 +0200 Subject: [PATCH 0084/1201] Add moving to the first/last entry in history. --- README.md | 16 +++++++++------- src/lib.rs | 45 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index aaaafd9e89..841e38eef7 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Ctrl-D | (if line *is* empty) End of File Ctrl-E, End | Move cursor to end of line Ctrl-F, Right| Move cursor one character right Ctrl-H, BackSpace | Delete character before cursor -Ctrl-I, Tab | Next completion +Ctrl-I, Tab | Next completion Ctrl-J, Ctrl-M, Enter | Finish the line entry Ctrl-K | Delete from cursor to end of line Ctrl-L | Clear screen @@ -94,14 +94,16 @@ Ctrl-U | Delete from start of line to cursor Ctrl-V | Insert any special character without perfoming its associated action Ctrl-W | Delete word leading up to cursor (using white space as a word boundary) Ctrl-Y | Paste from Yank buffer (Meta-Y to paste next yank instead) +Meta-< | Move to first entry in history +Meta-> | Move to last entry in history Meta-B, Alt-Left | Move cursor to previous word -Meta-C | Capitalize the current word -Meta-D | Delete forwards one word +Meta-C | Capitalize the current word +Meta-D | Delete forwards one word Meta-F, Alt-Right | Move cursor to next word -Meta-L | Lower-case the next word -Meta-T | Transpose words -Meta-U | Upper-case the next word -Meta-Y | See Ctrl-Y +Meta-L | Lower-case the next word +Meta-T | Transpose words +Meta-U | Upper-case the next word +Meta-Y | See Ctrl-Y Meta-BackSpace | Kill from the start of the current word, or, if between words, to the start of the previous word ## ToDo diff --git a/src/lib.rs b/src/lib.rs index bd065a116b..eb3e2cc345 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -690,7 +690,34 @@ fn edit_history_next(s: &mut State, history: &History, prev: bool) -> Result<()> } else { // Restore current edited line s.snapshot(); - }; + } + s.refresh_line() +} + +/// Substitute the currently edited line with the first/last history entry. +fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { + if history.is_empty() { + return Ok(()); + } + if s.history_index == history.len() { + if first { + // Save the current edited line before to overwrite it + s.snapshot(); + } else { + return Ok(()); + } + } else if s.history_index == 0 && first { + return Ok(()); + } + if first { + s.history_index = 0; + let buf = history.get(s.history_index).unwrap(); + s.line.update(buf, buf.len()); + } else { + s.history_index = history.len(); + // Restore current edited line + s.snapshot(); + } s.refresh_line() } @@ -897,9 +924,10 @@ impl RawReader { // TODO ESC-N (n): search history forward not interactively // TODO ESC-P (p): search history backward not interactively // TODO ESC-R (r): Undo all changes made to this line. - // TODO ESC-<: move to first entry in history - // TODO ESC->: move to last entry in history match seq1 { + '\x08' => Ok(KeyPress::Meta('\x08')), // Backspace + '<' => Ok(KeyPress::Meta('<')), + '>' => Ok(KeyPress::Meta('>')), 'b' | 'B' => Ok(KeyPress::Meta('B')), 'c' | 'C' => Ok(KeyPress::Meta('C')), 'd' | 'D' => Ok(KeyPress::Meta('D')), @@ -908,7 +936,6 @@ impl RawReader { 't' | 'T' => Ok(KeyPress::Meta('T')), 'u' | 'U' => Ok(KeyPress::Meta('U')), 'y' | 'Y' => Ok(KeyPress::Meta('Y')), - '\x08' => Ok(KeyPress::Meta('\x08')), // Backspace '\x7f' => Ok(KeyPress::Meta('\x7f')), // Delete _ => { // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::Esc, seq1).unwrap(); @@ -1214,6 +1241,16 @@ fn readline_edit(prompt: &str, kill_ring.kill(&text, false) } } + KeyPress::Meta('<') => { + // move to first entry in history + kill_ring.reset(); + try!(edit_history(&mut s, history, true)) + } + KeyPress::Meta('>') => { + // move to last entry in history + kill_ring.reset(); + try!(edit_history(&mut s, history, false)) + } KeyPress::Meta('B') => { // move backwards one word kill_ring.reset(); From 3ae84f0315a271d8b349e2bd879f0d102c3e1593 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 24 Jul 2016 15:03:54 +0200 Subject: [PATCH 0085/1201] Fix backspace key regression --- src/consts.rs | 2 +- src/lib.rs | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index bd7ffcd62e..5fc32ea3b4 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -50,7 +50,7 @@ pub fn char_to_key_press(c: char) -> KeyPress { '\x19' => KeyPress::Ctrl('Y'), '\x1a' => KeyPress::Ctrl('Z'), '\x1b' => KeyPress::Esc, - '\x7f' => KeyPress::Delete, + '\x7f' => KeyPress::Backspace, // TODO Validate _ => KeyPress::Null, } } diff --git a/src/lib.rs b/src/lib.rs index eb3e2cc345..f0f683f891 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -747,7 +747,7 @@ fn complete_line(rdr: &mut RawReader, s.snapshot(); } - key = try!(rdr.next_key()); + key = try!(rdr.next_key(false)); match key { KeyPress::Tab => { i = (i + 1) % (candidates.len() + 1); // Circular @@ -799,7 +799,7 @@ fn reverse_incremental_search(rdr: &mut RawReader, }; try!(s.refresh_prompt_and_line(&prompt)); - key = try!(rdr.next_key()); + key = try!(rdr.next_key(true)); if let KeyPress::Char(c) = key { search_buf.push(c); } else { @@ -862,11 +862,13 @@ impl RawReader { Ok(RawReader { chars: stdin.chars() }) } - fn next_key(&mut self) -> Result { + // As there is no read timeout to properly handle single ESC key, + // we make possible to deactivate escape sequence processing. + fn next_key(&mut self, esc_seq: bool) -> Result { let c = try!(self.next_char()); let mut key = consts::char_to_key_press(c); - if key == KeyPress::Esc { + if esc_seq && key == KeyPress::Esc { // escape sequence key = try!(self.escape_sequence()); } @@ -964,13 +966,14 @@ impl RawReader { }) } - fn next_key(&mut self) -> Result { + fn next_key(&mut self, _: bool) -> Result { use std::char::decode_utf16; let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; let mut count = 0; let mut esc_seen = false; loop { + // TODO GetNumberOfConsoleInputEvents check!(kernel32::ReadConsoleInputW(self.handle, &mut rec, 1 as winapi::DWORD, @@ -1080,7 +1083,7 @@ fn readline_edit(prompt: &str, let mut rdr = try!(RawReader::new(io::stdin())); loop { - let rk = rdr.next_key(); + let rk = rdr.next_key(true); if rk.is_err() && SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { s.update_columns(); try!(s.refresh_line()); @@ -1196,14 +1199,12 @@ fn readline_edit(prompt: &str, kill_ring.kill(&text, false) } } + #[cfg(unix)] KeyPress::Ctrl('V') => { // Quoted insert kill_ring.reset(); - let rk = rdr.next_key(); - let key = try!(rk); - if let KeyPress::Char(c) = key { - try!(edit_insert(&mut s, c)) - } + let c = try!(rdr.next_char()); + try!(edit_insert(&mut s, c)) // FIXME } KeyPress::Ctrl('W') => { // Kill the word behind point, using white space as a word boundary From a85983e85e84f198f852a10e85801be6c10745ca Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 25 Jul 2016 22:16:15 +0200 Subject: [PATCH 0086/1201] Try to fix windows build. --- src/tty/unix.rs | 6 +++--- src/tty/windows.rs | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index fceb3e85ca..9d26f894e3 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,9 +1,9 @@ -extern crate nix; -extern crate libc; - use std; use std::io::Read; +use libc; +use nix; use nix::sys::termios; + use char_iter; use consts::{self, KeyPress}; use ::Result; diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 2244d8ce12..1195e3ebe3 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,8 +1,8 @@ -extern crate kernel32; -extern crate winapi; - use std::io; use std::marker::PhantomData; +use kernel32; +use winapi; + use ::Result; pub type Handle = winapi::HANDLE; @@ -21,6 +21,7 @@ fn get_std_handle(fd: winapi::DWORD) -> Result { Ok(handle) } +#[macro_export] macro_rules! check { ($funcall:expr) => { { From 30a466408edcf6a81ea8276a03dd60920a6eb736 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 25 Jul 2016 22:33:24 +0200 Subject: [PATCH 0087/1201] Fix windows build. --- src/lib.rs | 1 + src/tty/mod.rs | 4 +++- src/tty/windows.rs | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4912cbccf6..019a67ad9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ mod kill_ring; pub mod line_buffer; mod char_iter; +#[macro_use] mod tty; use std::fmt; diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 3a15003c03..0405a38adf 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -3,7 +3,9 @@ extern crate libc; // If on Windows platform import Windows TTY module // and re-export into mod.rs scope -#[cfg(windows)]mod windows; +#[macro_use] +#[cfg(windows)] +mod windows; #[cfg(windows)] pub use self::windows::*; diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 1195e3ebe3..85ccb2bfbf 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,9 +1,16 @@ use std::io; +use std::io::Read; use std::marker::PhantomData; +use std::mem; +use std::sync::atomic; + use kernel32; use winapi; +use consts::{self, KeyPress}; +use ::error; use ::Result; +use SIGWINCH; pub type Handle = winapi::HANDLE; pub type Mode = winapi::DWORD; From 3b620b8c6c49316afed03e93e954d35c697ed007 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 26 Jul 2016 19:02:21 +0200 Subject: [PATCH 0088/1201] Fix warnings. --- src/error.rs | 12 ++++++++---- src/lib.rs | 9 +++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/error.rs b/src/error.rs index e516a1957a..d97c4e7a55 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,6 +7,7 @@ use std::fmt; #[cfg(unix)] use nix; +#[cfg(unix)] use char_iter; /// The error type for Rustyline errors that can arise from @@ -15,12 +16,13 @@ use char_iter; pub enum ReadlineError { /// I/O Error Io(io::Error), - /// Chars Error - Char(char_iter::CharsError), /// EOF (Ctrl-d) Eof, /// Ctrl-C Interrupted, + /// Chars Error + #[cfg(unix)] + Char(char_iter::CharsError), /// Unix Error from syscall #[cfg(unix)] Errno(nix::Error), @@ -34,10 +36,11 @@ impl fmt::Display for ReadlineError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ReadlineError::Io(ref err) => err.fmt(f), - ReadlineError::Char(ref err) => err.fmt(f), ReadlineError::Eof => write!(f, "EOF"), ReadlineError::Interrupted => write!(f, "Interrupted"), #[cfg(unix)] + ReadlineError::Char(ref err) => err.fmt(f), + #[cfg(unix)] ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()), #[cfg(windows)] ReadlineError::WindowResize => write!(f, "WindowResize"), @@ -51,10 +54,11 @@ impl error::Error for ReadlineError { fn description(&self) -> &str { match *self { ReadlineError::Io(ref err) => err.description(), - ReadlineError::Char(ref err) => err.description(), ReadlineError::Eof => "EOF", ReadlineError::Interrupted => "Interrupted", #[cfg(unix)] + ReadlineError::Char(ref err) => err.description(), + #[cfg(unix)] ReadlineError::Errno(ref err) => err.errno().desc(), #[cfg(windows)] ReadlineError::WindowResize => "WindowResize", diff --git a/src/lib.rs b/src/lib.rs index 019a67ad9e..e8069a2311 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,6 @@ pub mod error; pub mod history; mod kill_ring; pub mod line_buffer; -mod char_iter; #[macro_use] mod tty; @@ -1093,11 +1092,10 @@ mod test { use history::History; #[cfg(unix)] use completion::Completer; - #[cfg(unix)] - use consts::KeyPress; use State; + #[cfg(unix)] use super::Result; - use tty::{Handle, RawReader}; + use tty::Handle; #[cfg(unix)] fn default_handle() -> Handle { @@ -1178,6 +1176,9 @@ mod test { #[test] #[cfg(unix)] fn complete_line() { + use consts::KeyPress; + use tty::RawReader; + let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); let input = b"\n"; From 00d834fe344d4f8b5df2815a876d231b07aa51d6 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 26 Jul 2016 19:07:00 +0200 Subject: [PATCH 0089/1201] Oops --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e8069a2311..91978c69bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,8 @@ pub mod error; pub mod history; mod kill_ring; pub mod line_buffer; +#[cfg(unix)] +mod char_iter; #[macro_use] mod tty; From baf33231f42d07f7b1fe7037a003090e007fb451 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 26 Jul 2016 20:12:42 +0200 Subject: [PATCH 0090/1201] Exclude one test on windows. --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 91978c69bd..8a8ed33930 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -175,9 +175,6 @@ impl<'out, 'prompt> State<'out, 'prompt> { #[cfg(windows)] fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> { let handle = self.output_handle; - if cfg!(test) && handle.is_null() { - return Ok(()); - } // calculate the position of the end of the input line let end_pos = calculate_position(&self.line, prompt_size, self.cols); // calculate the desired position of the cursor @@ -1129,6 +1126,7 @@ mod test { } #[test] + #[cfg(unix)] fn edit_history_next() { let mut out = ::std::io::sink(); let line = "current edited line"; From 659f4c59e35772fb45298a424dca8f85f8af7369 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 26 Jul 2016 20:22:19 +0200 Subject: [PATCH 0091/1201] Fix warnings on windows. --- src/error.rs | 3 +++ src/lib.rs | 15 +++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/error.rs b/src/error.rs index 98c148fdd6..c9846016f1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,6 +17,7 @@ pub enum ReadlineError { #[cfg(unix)] Errno(nix::Error), /// Chars Error + #[cfg(unix)] Char(io::CharsError), /// EOF (Ctrl-d) Eof, @@ -34,6 +35,7 @@ impl fmt::Display for ReadlineError { ReadlineError::Io(ref err) => err.fmt(f), #[cfg(unix)] ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()), + #[cfg(unix)] ReadlineError::Char(ref err) => err.fmt(f), ReadlineError::Eof => write!(f, "EOF"), ReadlineError::Interrupted => write!(f, "Interrupted"), @@ -51,6 +53,7 @@ impl error::Error for ReadlineError { ReadlineError::Io(ref err) => err.description(), #[cfg(unix)] ReadlineError::Errno(ref err) => err.errno().desc(), + #[cfg(unix)] ReadlineError::Char(ref err) => err.description(), ReadlineError::Eof => "EOF", ReadlineError::Interrupted => "Interrupted", diff --git a/src/lib.rs b/src/lib.rs index f0f683f891..87e59815f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -191,9 +191,6 @@ impl<'out, 'prompt> State<'out, 'prompt> { #[cfg(windows)] fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> { let handle = self.output_handle; - if cfg!(test) && handle.is_null() { - return Ok(()); - } // calculate the position of the end of the input line let end_pos = calculate_position(&self.line, prompt_size, self.cols); // calculate the desired position of the cursor @@ -993,9 +990,9 @@ impl RawReader { continue; } - let ctrl = key_event.dwControlKeyState & + /*let ctrl = key_event.dwControlKeyState & (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) != - 0; + 0;*/ let meta = (key_event.dwControlKeyState & (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED) != 0) || esc_seen; @@ -1474,10 +1471,8 @@ mod test { use history::History; #[cfg(unix)] use completion::Completer; - #[cfg(unix)] - use consts::KeyPress; use State; - use super::{Handle, RawReader, Result}; + use super::{Handle, Result}; #[cfg(unix)] fn default_handle() -> Handle { @@ -1509,6 +1504,7 @@ mod test { } #[test] + #[cfg(unix)] fn edit_history_next() { let mut out = ::std::io::sink(); let line = "current edited line"; @@ -1558,6 +1554,9 @@ mod test { #[test] #[cfg(unix)] fn complete_line() { + use consts::KeyPress; + use super::RawReader; + let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); let input = b"\n"; From 075adac4ced1cf046522a4bfbd4d5ee69ce28f93 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 26 Jul 2016 20:29:15 +0200 Subject: [PATCH 0092/1201] Disable some tests on windows --- src/lib.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 87e59815f1..505660cc06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1464,25 +1464,18 @@ fn install_sigwinch_handler() { // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT } -#[cfg(test)] +#[cfg(all(unix,test))] mod test { use std::io::Write; use line_buffer::LineBuffer; use history::History; - #[cfg(unix)] use completion::Completer; use State; use super::{Handle, Result}; - #[cfg(unix)] fn default_handle() -> Handle { () } - #[cfg(windows)] - fn default_handle() -> Handle { - ::std::ptr::null_mut() - // super::get_std_handle(super::STDOUT_FILENO).expect("Valid stdout") - } fn init_state<'out>(out: &'out mut Write, line: &str, @@ -1504,7 +1497,6 @@ mod test { } #[test] - #[cfg(unix)] fn edit_history_next() { let mut out = ::std::io::sink(); let line = "current edited line"; @@ -1542,9 +1534,7 @@ mod test { assert_eq!(line, s.line.as_str()); } - #[cfg(unix)] struct SimpleCompleter; - #[cfg(unix)] impl Completer for SimpleCompleter { fn complete(&self, line: &str, _pos: usize) -> Result<(usize, Vec)> { Ok((0, vec![line.to_string() + "t"])) @@ -1552,7 +1542,6 @@ mod test { } #[test] - #[cfg(unix)] fn complete_line() { use consts::KeyPress; use super::RawReader; From 6c84ec27d0501581ab515b9a8a17f7c5d0d884f4 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 27 Jul 2016 05:49:34 +0200 Subject: [PATCH 0093/1201] Fix some redundant import. --- src/tty/mod.rs | 1 - src/tty/unix.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 0405a38adf..22c6793842 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -1,5 +1,4 @@ //! This module implements and describes common TTY methods & traits -extern crate libc; // If on Windows platform import Windows TTY module // and re-export into mod.rs scope diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 9d26f894e3..aadcefea12 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -31,7 +31,6 @@ const TIOCGWINSZ: libc::c_int = 0x5413; pub fn get_columns(_: Handle) -> usize { use std::mem::zeroed; use libc::c_ushort; - use libc; unsafe { #[repr(C)] From 83d295670d97715922cf640fe90c9345a1744b01 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 27 Jul 2016 05:56:01 +0200 Subject: [PATCH 0094/1201] Fix visibility issue in windows module --- src/tty/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 85ccb2bfbf..ca87f2274e 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -55,7 +55,7 @@ pub fn is_unsupported_term() -> bool { false } -pub fn get_console_mode(handle: winapi::HANDLE) -> Result { +fn get_console_mode(handle: winapi::HANDLE) -> Result { let mut original_mode = 0; check!(kernel32::GetConsoleMode(handle, &mut original_mode)); Ok(original_mode) From 0b65a24f76bf431aec0fd00c7d9bcc1a5e009f8e Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 27 Jul 2016 06:05:51 +0200 Subject: [PATCH 0095/1201] Last stable version is 1.10.0 --- .travis.yml | 2 +- appveyor.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0280c43c19..130b992efb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: rust rust: - - 1.9.0 + - 1.10.0 - beta - nightly script: diff --git a/appveyor.yml b/appveyor.yml index e0a1efeeb5..9a62dabbdb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ environment: matrix: - - TARGET: 1.9.0-x86_64-pc-windows-msvc - - TARGET: 1.9.0-x86_64-pc-windows-gnu + - TARGET: 1.10.0-x86_64-pc-windows-msvc + - TARGET: 1.10.0-x86_64-pc-windows-gnu - TARGET: beta-x86_64-pc-windows-msvc - TARGET: beta-x86_64-pc-windows-gnu install: From b49b2e0ca8b15688029673f686942d151e13f7ec Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 28 Jul 2016 21:48:43 +0200 Subject: [PATCH 0096/1201] Try to handle AltGr key and resize event on windows --- src/lib.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 505660cc06..6d4c04f276 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -331,9 +331,15 @@ fn enable_raw_mode() -> Result { fn enable_raw_mode() -> Result { let handle = try!(get_std_handle(STDIN_FILENO)); let original_mode = try!(get_console_mode(handle)); + // Disable these modes let raw = original_mode & !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT | winapi::wincon::ENABLE_PROCESSED_INPUT); + // Enable these modes + let raw = raw | winapi::wincon::ENABLE_EXTENDED_FLAGS; + let raw = raw | winapi::wincon::ENABLE_INSERT_MODE; + let raw = raw | winapi::wincon::ENABLE_QUICK_EDIT_MODE; + let raw = raw | winapi::wincon::ENABLE_WINDOW_INPUT; check!(kernel32::SetConsoleMode(handle, raw)); Ok(original_mode) } @@ -990,12 +996,10 @@ impl RawReader { continue; } - /*let ctrl = key_event.dwControlKeyState & - (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) != - 0;*/ - let meta = (key_event.dwControlKeyState & - (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED) != - 0) || esc_seen; + let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) == (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); + let alt = key_event.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) == (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); + let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) == (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); + let meta = alt || esc_seen; let utf16 = key_event.UnicodeChar; if utf16 == 0 { From 2bac1a7255581d9cba7c1e95a6951a5d4dc4949d Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 28 Jul 2016 21:54:42 +0200 Subject: [PATCH 0097/1201] Fix missing use. --- src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6d4c04f276..5ae0a2c881 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -971,6 +971,7 @@ impl RawReader { fn next_key(&mut self, _: bool) -> Result { use std::char::decode_utf16; + use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; let mut count = 0; @@ -996,9 +997,12 @@ impl RawReader { continue; } - let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) == (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); - let alt = key_event.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) == (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); - let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) == (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); + let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) == + (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); + let alt = key_event.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) == + (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); + let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) == + (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); let meta = alt || esc_seen; let utf16 = key_event.UnicodeChar; From ffbdc45f79c398ab129815ed6b4bbd19c122591d Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 29 Jul 2016 19:10:56 +0200 Subject: [PATCH 0098/1201] Fix #55. Attempt to fix #63. Fix AltGr key. --- src/tty/windows.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index ca87f2274e..42805dc8b5 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -77,9 +77,15 @@ pub fn is_a_tty(fd: winapi::DWORD) -> bool { pub fn enable_raw_mode() -> Result { let handle = try!(get_std_handle(STDIN_FILENO)); let original_mode = try!(get_console_mode(handle)); + // Disable these modes let raw = original_mode & !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT | winapi::wincon::ENABLE_PROCESSED_INPUT); + // Enable these modes + let raw = raw | winapi::wincon::ENABLE_EXTENDED_FLAGS; + let raw = raw | winapi::wincon::ENABLE_INSERT_MODE; + let raw = raw | winapi::wincon::ENABLE_QUICK_EDIT_MODE; + let raw = raw | winapi::wincon::ENABLE_WINDOW_INPUT; check!(kernel32::SetConsoleMode(handle, raw)); Ok(original_mode) } @@ -115,6 +121,7 @@ impl RawReader { pub fn next_key(&mut self, _: bool) -> Result { use std::char::decode_utf16; + use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; let mut count = 0; @@ -140,12 +147,13 @@ impl RawReader { continue; } - let ctrl = key_event.dwControlKeyState & - (winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) != - 0; - let meta = (key_event.dwControlKeyState & - (winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED) != - 0) || esc_seen; + // let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) == + // (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); + let alt = key_event.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) == + (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); + // let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) == + // (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); + let meta = alt || esc_seen; let utf16 = key_event.UnicodeChar; if utf16 == 0 { From 96e8e36bb585a1f0964173a7f82843cb5850bc60 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 29 Jul 2016 20:05:11 +0200 Subject: [PATCH 0099/1201] Fix warnings --- src/tty/windows.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 42805dc8b5..fef3199dc7 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -121,7 +121,8 @@ impl RawReader { pub fn next_key(&mut self, _: bool) -> Result { use std::char::decode_utf16; - use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; + //use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; + use winapi::{LEFT_ALT_PRESSED, RIGHT_ALT_PRESSED}; let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; let mut count = 0; From fc0193bbd2409ed37eaedb316f22cf76268bf655 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 29 Jul 2016 20:13:14 +0200 Subject: [PATCH 0100/1201] Fix windows problems --- src/lib.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5ae0a2c881..742097d45a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -423,9 +423,10 @@ fn clear_screen(s: &mut State) -> Result<()> { let coord = winapi::COORD { X: 0, Y: 0 }; check!(kernel32::SetConsoleCursorPosition(handle, coord)); let mut _count = 0; + let n = info.dwSize.X as winapi::DWORD * info.dwSize.Y as winapi::DWORD; check!(kernel32::FillConsoleOutputCharacterA(handle, ' ' as winapi::CHAR, - (info.dwSize.X * info.dwSize.Y) as winapi::DWORD, + n, coord, &mut _count)); Ok(()) @@ -971,7 +972,8 @@ impl RawReader { fn next_key(&mut self, _: bool) -> Result { use std::char::decode_utf16; - use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; + //use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; + use winapi::{LEFT_ALT_PRESSED, RIGHT_ALT_PRESSED}; let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; let mut count = 0; @@ -997,12 +999,12 @@ impl RawReader { continue; } - let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) == - (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); + /*let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) == + (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED);*/ let alt = key_event.dwControlKeyState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) == (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); - let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) == - (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); + /*let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) == + (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED);*/ let meta = alt || esc_seen; let utf16 = key_event.UnicodeChar; From 2b1d545a9904a1b9d64a1acfa33e8c6831c768bd Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 30 Jul 2016 08:35:57 +0200 Subject: [PATCH 0101/1201] Move clear_screen in tty module. --- src/lib.rs | 23 +---------------------- src/tty/unix.rs | 9 ++++++++- src/tty/windows.rs | 17 ++++++++++++++++- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8a8ed33930..d9dd423f42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -237,27 +237,6 @@ fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> { Ok(()) } -/// Clear the screen. Used to handle ctrl+l -#[cfg(unix)] -fn clear_screen(s: &mut State) -> Result<()> { - write_and_flush(s.out, b"\x1b[H\x1b[2J") -} -#[cfg(windows)] -fn clear_screen(s: &mut State) -> Result<()> { - let handle = s.output_handle; - let mut info = unsafe { mem::zeroed() }; - check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); - let coord = winapi::COORD { X: 0, Y: 0 }; - check!(kernel32::SetConsoleCursorPosition(handle, coord)); - let mut _count = 0; - check!(kernel32::FillConsoleOutputCharacterA(handle, - ' ' as winapi::CHAR, - (info.dwSize.X * info.dwSize.Y) as winapi::DWORD, - coord, - &mut _count)); - Ok(()) -} - /// Beep, used for completion when there is nothing to complete or when all /// the choices were already shown. fn beep() -> Result<()> { @@ -788,7 +767,7 @@ fn readline_edit(prompt: &str, } KeyPress::Ctrl('L') => { // Clear the screen leaving the current line at the top of the screen. - try!(clear_screen(&mut s)); + try!(tty::clear_screen(&mut s.out, s.output_handle)); try!(s.refresh_line()) } KeyPress::Ctrl('N') | diff --git a/src/tty/unix.rs b/src/tty/unix.rs index aadcefea12..95662d7600 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,5 +1,5 @@ use std; -use std::io::Read; +use std::io::{Read, Write}; use libc; use nix; use nix::sys::termios; @@ -105,6 +105,13 @@ pub fn stdout_handle() -> Result { Ok(()) } +/// Clear the screen. Used to handle ctrl+l +pub fn clear_screen(w: &mut Write, _: Handle) -> Result<()> { + try!(w.write_all(b"\x1b[H\x1b[2J")); + try!(w.flush()); + Ok(()) +} + /// Console input reader pub struct RawReader { chars: char_iter::Chars, diff --git a/src/tty/windows.rs b/src/tty/windows.rs index fef3199dc7..65a4769a9c 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -102,6 +102,21 @@ pub fn stdout_handle() -> Result { Ok(handle) } +/// Clear the screen. Used to handle ctrl+l +pub fn clear_screen(_: &mut Write, handle: Handle) -> Result<()> { + let mut info = unsafe { mem::zeroed() }; + check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); + let coord = winapi::COORD { X: 0, Y: 0 }; + check!(kernel32::SetConsoleCursorPosition(handle, coord)); + let mut _count = 0; + check!(kernel32::FillConsoleOutputCharacterA(handle, + ' ' as winapi::CHAR, + (info.dwSize.X * info.dwSize.Y) as winapi::DWORD, + coord, + &mut _count)); + Ok(()) +} + /// Console input reader pub struct RawReader { handle: winapi::HANDLE, @@ -121,7 +136,7 @@ impl RawReader { pub fn next_key(&mut self, _: bool) -> Result { use std::char::decode_utf16; - //use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; + // use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; use winapi::{LEFT_ALT_PRESSED, RIGHT_ALT_PRESSED}; let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; From e66a3746772bfee521f2a34622786debe56379e7 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 30 Jul 2016 08:39:47 +0200 Subject: [PATCH 0102/1201] Fix missing use for windows --- src/tty/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 65a4769a9c..7d728808c3 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,5 +1,5 @@ use std::io; -use std::io::Read; +use std::io::{Read, Write}; use std::marker::PhantomData; use std::mem; use std::sync::atomic; From a50da6b6f63682486c757604f74f46a124af909f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 30 Jul 2016 08:44:40 +0200 Subject: [PATCH 0103/1201] Fix warnings --- src/lib.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d9dd423f42..9d09ef516d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1063,27 +1063,19 @@ fn install_sigwinch_handler() { // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT } -#[cfg(test)] +#[cfg(all(unix,test))] mod test { use std::io::Write; use line_buffer::LineBuffer; use history::History; - #[cfg(unix)] use completion::Completer; use State; - #[cfg(unix)] use super::Result; use tty::Handle; - #[cfg(unix)] fn default_handle() -> Handle { () } - #[cfg(windows)] - fn default_handle() -> Handle { - ::std::ptr::null_mut() - // super::get_std_handle(super::STDOUT_FILENO).expect("Valid stdout") - } fn init_state<'out>(out: &'out mut Write, line: &str, @@ -1105,7 +1097,6 @@ mod test { } #[test] - #[cfg(unix)] fn edit_history_next() { let mut out = ::std::io::sink(); let line = "current edited line"; @@ -1143,9 +1134,7 @@ mod test { assert_eq!(line, s.line.as_str()); } - #[cfg(unix)] struct SimpleCompleter; - #[cfg(unix)] impl Completer for SimpleCompleter { fn complete(&self, line: &str, _pos: usize) -> Result<(usize, Vec)> { Ok((0, vec![line.to_string() + "t"])) @@ -1153,7 +1142,6 @@ mod test { } #[test] - #[cfg(unix)] fn complete_line() { use consts::KeyPress; use tty::RawReader; From b10384aa94aba1e7901c43045da3197f7eb6b0af Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 30 Jul 2016 19:50:28 +0200 Subject: [PATCH 0104/1201] Move `Completer` trait bound to the struct declaration. I may be wrong but I don't understant why it is not already the case... And add some missing documentation. --- src/completion.rs | 4 ++++ src/error.rs | 2 +- src/history.rs | 7 ++++++ src/lib.rs | 57 +++++++++++++++++++++++------------------------ 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 2359316744..ff87bb85fb 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -134,6 +134,10 @@ fn filename_complete(path: &str) -> Result> { Ok(entries) } +/// Given a `line` and a cursor `pos`ition, +/// try to find backward the start of a word. +/// Return (0, `line[..pos]`) if no break char has been found. +/// Return the word and its start position (idx, `line[idx..pos]`) otherwise. pub fn extract_word<'l>(line: &'l str, pos: usize, break_chars: &BTreeSet) diff --git a/src/error.rs b/src/error.rs index d97c4e7a55..009ec65f1d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,7 +16,7 @@ use char_iter; pub enum ReadlineError { /// I/O Error Io(io::Error), - /// EOF (Ctrl-d) + /// EOF (Ctrl-D) Eof, /// Ctrl-C Interrupted, diff --git a/src/history.rs b/src/history.rs index 8b28f24987..f58ff9a9b3 100644 --- a/src/history.rs +++ b/src/history.rs @@ -25,10 +25,14 @@ impl History { } } + /// Tell if lines which begin with a space character are saved or not in the history list. + /// By default, they are saved. pub fn ignore_space(&mut self, yes: bool) { self.ignore_space = yes; } + /// Tell if lines which match the previous history entry are saved or not in the history list. + /// By default, they are ignored. pub fn ignore_dups(&mut self, yes: bool) { self.ignore_dups = yes; } @@ -122,6 +126,9 @@ impl History { } /// Search history (start position inclusive [0, len-1]) + /// Return the absolute index of the nearest history entry that matches `term`. + /// Return None if no entry contains `term` between [start, len -1] for forward search + /// or between [0, start] for reverse search. pub fn search(&self, term: &str, start: usize, reverse: bool) -> Option { if term.is_empty() || start >= self.len() { return None; diff --git a/src/lib.rs b/src/lib.rs index 9d09ef516d..049685ab9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -936,20 +936,17 @@ fn readline_direct() -> Result { } /// Line editor -pub struct Editor { +pub struct Editor { unsupported_term: bool, stdin_isatty: bool, stdout_isatty: bool, - // cols: usize, // Number of columns in terminal history: History, completer: Option, kill_ring: KillRing, } -impl Editor { +impl Editor { pub fn new() -> Editor { - // TODO check what is done in rl_initialize() - // if the number of columns is stored here, we need a SIGWINCH handler... let editor = Editor { unsupported_term: tty::is_unsupported_term(), stdin_isatty: tty::is_a_tty(tty::STDIN_FILENO), @@ -964,11 +961,35 @@ impl Editor { editor } + /// This method will read a line from STDIN and will display a `prompt` + #[cfg_attr(feature="clippy", allow(if_not_else))] + pub fn readline(&mut self, prompt: &str) -> Result { + if self.unsupported_term { + // Write prompt and flush it to stdout + let mut stdout = io::stdout(); + try!(write_and_flush(&mut stdout, prompt.as_bytes())); + + readline_direct() + } else if !self.stdin_isatty { + // Not a tty: read from file / pipe. + readline_direct() + } else { + readline_raw(prompt, + &mut self.history, + self.completer.as_ref().map(|c| c as &Completer), + &mut self.kill_ring) + } + } + + /// Tell if lines which match the previous history entry are saved or not in the history list. + /// By default, they are ignored. pub fn history_ignore_dups(mut self, yes: bool) -> Editor { self.history.ignore_dups(yes); self } + /// Tell if lines which begin with a space character are saved or not in the history list. + /// By default, they are saved. pub fn history_ignore_space(mut self, yes: bool) -> Editor { self.history.ignore_space(yes); self @@ -998,28 +1019,6 @@ impl Editor { pub fn get_history(&mut self) -> &mut History { &mut self.history } -} - -impl Editor { - /// This method will read a line from STDIN and will display a `prompt` - #[cfg_attr(feature="clippy", allow(if_not_else))] - pub fn readline(&mut self, prompt: &str) -> Result { - if self.unsupported_term { - // Write prompt and flush it to stdout - let mut stdout = io::stdout(); - try!(write_and_flush(&mut stdout, prompt.as_bytes())); - - readline_direct() - } else if !self.stdin_isatty { - // Not a tty: read from file / pipe. - readline_direct() - } else { - readline_raw(prompt, - &mut self.history, - self.completer.as_ref().map(|c| c as &Completer), - &mut self.kill_ring) - } - } /// Register a callback function to be called for tab-completion. pub fn set_completer(&mut self, completer: Option) { @@ -1027,13 +1026,13 @@ impl Editor { } } -impl Default for Editor { +impl Default for Editor { fn default() -> Editor { Editor::new() } } -impl fmt::Debug for Editor { +impl fmt::Debug for Editor { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("State") .field("unsupported_term", &self.unsupported_term) From 31ace434fe0605d0598dc3577e4bb97edd67ce3e Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 31 Jul 2016 05:31:52 +0200 Subject: [PATCH 0105/1201] Remove one TODO --- src/tty/unix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 95662d7600..5e2d8b71f6 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -138,7 +138,7 @@ impl RawReader { pub fn next_char(&mut self) -> Result { match self.chars.next() { Some(c) => { - Ok(try!(c)) // TODO SIGWINCH + Ok(try!(c)) } None => Err(error::ReadlineError::Eof), } From 738d946d4a8fdb2f093b042aa8e4360bf18869af Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 31 Jul 2016 05:46:49 +0200 Subject: [PATCH 0106/1201] Move SIGWINCH stuff into tty module --- src/lib.rs | 27 ++------------------------- src/tty/unix.rs | 23 ++++++++++++++++++++--- src/tty/windows.rs | 8 ++++++-- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 049685ab9e..32ae3f6ed5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,8 +43,6 @@ use std::io::{self, Read, Write}; use std::mem; use std::path::Path; use std::result; -#[cfg(unix)] -use std::sync; use std::sync::atomic; #[cfg(unix)] use nix::sys::signal; @@ -678,7 +676,7 @@ fn readline_edit(prompt: &str, loop { let rk = rdr.next_key(true); - if rk.is_err() && SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { + if rk.is_err() && tty::SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { s.update_columns(); try!(s.refresh_line()); continue; @@ -956,7 +954,7 @@ impl Editor { kill_ring: KillRing::new(60), }; if !editor.unsupported_term && editor.stdin_isatty && editor.stdout_isatty { - install_sigwinch_handler(); + tty::install_sigwinch_handler(); } editor } @@ -1041,27 +1039,6 @@ impl fmt::Debug for Editor { } } -#[cfg(unix)] -static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT; -static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; -#[cfg(unix)] -fn install_sigwinch_handler() { - SIGWINCH_ONCE.call_once(|| unsafe { - let sigwinch = signal::SigAction::new(signal::SigHandler::Handler(sigwinch_handler), - signal::SaFlag::empty(), - signal::SigSet::empty()); - let _ = signal::sigaction(signal::SIGWINCH, &sigwinch); - }); -} -#[cfg(unix)] -extern "C" fn sigwinch_handler(_: signal::SigNum) { - SIGWINCH.store(true, atomic::Ordering::SeqCst); -} -#[cfg(windows)] -fn install_sigwinch_handler() { - // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT -} - #[cfg(all(unix,test))] mod test { use std::io::Write; diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 5e2d8b71f6..a2665994cd 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,7 +1,10 @@ use std; use std::io::{Read, Write}; +use std::sync; +use std::sync::atomic; use libc; use nix; +use nix::sys::signal; use nix::sys::termios; use char_iter; @@ -137,9 +140,7 @@ impl RawReader { pub fn next_char(&mut self) -> Result { match self.chars.next() { - Some(c) => { - Ok(try!(c)) - } + Some(c) => Ok(try!(c)), None => Err(error::ReadlineError::Eof), } } @@ -207,3 +208,19 @@ impl RawReader { } } } + +static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT; +pub static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; + +pub fn install_sigwinch_handler() { + SIGWINCH_ONCE.call_once(|| unsafe { + let sigwinch = signal::SigAction::new(signal::SigHandler::Handler(sigwinch_handler), + signal::SaFlag::empty(), + signal::SigSet::empty()); + let _ = signal::sigaction(signal::SIGWINCH, &sigwinch); + }); +} + +extern "C" fn sigwinch_handler(_: signal::SigNum) { + SIGWINCH.store(true, atomic::Ordering::SeqCst); +} diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 7d728808c3..d9650aa827 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -10,7 +10,6 @@ use winapi; use consts::{self, KeyPress}; use ::error; use ::Result; -use SIGWINCH; pub type Handle = winapi::HANDLE; pub type Mode = winapi::DWORD; @@ -149,7 +148,6 @@ impl RawReader { 1 as winapi::DWORD, &mut count)); - // TODO ENABLE_WINDOW_INPUT ??? if rec.EventType == winapi::WINDOW_BUFFER_SIZE_EVENT { SIGWINCH.store(true, atomic::Ordering::SeqCst); return Err(error::ReadlineError::WindowResize); @@ -223,3 +221,9 @@ impl Iterator for RawReader { buf } } + +pub static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; + +fn install_sigwinch_handler() { + // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT +} From a8a04ffe6fd50adfe5fc8cef2ba7e2a59fb2823d Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 31 Jul 2016 05:49:47 +0200 Subject: [PATCH 0107/1201] Fix visibility problem on windows --- src/tty/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index d9650aa827..799d90c7f0 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -224,6 +224,6 @@ impl Iterator for RawReader { pub static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; -fn install_sigwinch_handler() { +pub fn install_sigwinch_handler() { // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT } From 1237c18b6e7528c599dfced7161007283df3f561 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 31 Jul 2016 06:49:28 +0200 Subject: [PATCH 0108/1201] Doc --- src/history.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/history.rs b/src/history.rs index 8b28f24987..10a2bfba46 100644 --- a/src/history.rs +++ b/src/history.rs @@ -89,6 +89,8 @@ impl History { } /// Save the history in the specified file. + /// TODO append_history http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX30 + /// TODO history_truncate_file http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX31 pub fn save + ?Sized>(&self, path: &P) -> Result<()> { use std::io::{BufWriter, Write}; @@ -121,7 +123,10 @@ impl History { self.entries.clear() } - /// Search history (start position inclusive [0, len-1]) + /// Search history (start position inclusive [0, len-1]). + /// Return the absolute index of the nearest history entry that matches `term`. + /// Return None if no entry contains `term` between [start, len -1] for forward search + /// or between [0, start] for reverse search. pub fn search(&self, term: &str, start: usize, reverse: bool) -> Option { if term.is_empty() || start >= self.len() { return None; From ada0330c30b6bb5b6cf4135e6eb3a4686b8c2861 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 31 Jul 2016 06:54:39 +0200 Subject: [PATCH 0109/1201] Really fix #55 Sorry, I forgot to report this change --- src/tty/windows.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 799d90c7f0..f6427958fe 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -108,9 +108,10 @@ pub fn clear_screen(_: &mut Write, handle: Handle) -> Result<()> { let coord = winapi::COORD { X: 0, Y: 0 }; check!(kernel32::SetConsoleCursorPosition(handle, coord)); let mut _count = 0; + let n = info.dwSize.X as winapi::DWORD * info.dwSize.Y as winapi::DWORD; check!(kernel32::FillConsoleOutputCharacterA(handle, ' ' as winapi::CHAR, - (info.dwSize.X * info.dwSize.Y) as winapi::DWORD, + n, coord, &mut _count)); Ok(()) From cd76aba0452147f1ebec8a85763071e5c74ba50e Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 8 Aug 2016 14:53:44 +0200 Subject: [PATCH 0110/1201] Fix clippy warnings --- src/completion.rs | 1 - src/consts.rs | 4 ++-- src/lib.rs | 10 ++++------ src/tty/unix.rs | 10 +++++----- src/tty/windows.rs | 2 +- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index ff87bb85fb..dca94d6d8b 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -86,7 +86,6 @@ impl Completer for FilenameCompleter { } } -#[cfg_attr(feature="clippy", allow(single_char_pattern))] fn filename_complete(path: &str) -> Result> { use std::env::{current_dir, home_dir}; diff --git a/src/consts.rs b/src/consts.rs index 5fc32ea3b4..375afe632c 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,7 +1,7 @@ #[derive(Debug, Clone, PartialEq, Copy)] pub enum KeyPress { - UNKNOWN_ESC_SEQ, + UnknownEscSeq, Backspace, Char(char), Ctrl(char), @@ -19,7 +19,7 @@ pub enum KeyPress { Up, } -#[cfg_attr(feature="clippy", allow(match_same_arms))] +#[allow(match_same_arms)] pub fn char_to_key_press(c: char) -> KeyPress { if !c.is_control() { return KeyPress::Char(c); diff --git a/src/lib.rs b/src/lib.rs index 8d3d5a6b50..9ea0c94180 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ //! ``` #![feature(io)] #![feature(unicode)] +#![allow(unknown_lints)] extern crate libc; #[cfg(unix)] @@ -27,7 +28,6 @@ extern crate winapi; extern crate kernel32; pub mod completion; -#[allow(non_camel_case_types)] mod consts; pub mod error; pub mod history; @@ -243,7 +243,7 @@ fn beep() -> Result<()> { /// starting at `orig`. /// Control characters are treated as having zero width. /// Characters with 2 column width are correctly handled (not splitted). -#[cfg_attr(feature="clippy", allow(if_same_then_else))] +#[allow(if_same_then_else)] fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { let mut pos = orig; let mut esc_seq = 0; @@ -578,7 +578,6 @@ fn complete_line(rdr: &mut tty::RawReader, } /// Incremental search -#[cfg_attr(feature="clippy", allow(if_not_else))] fn reverse_incremental_search(rdr: &mut tty::RawReader, s: &mut State, history: &History) @@ -658,7 +657,7 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, /// Handles reading and editting the readline buffer. /// It will also handle special inputs in an appropriate fashion /// (e.g., C-c will exit readline) -#[cfg_attr(feature="clippy", allow(cyclomatic_complexity))] +#[allow(let_unit_value)] fn readline_edit(prompt: &str, history: &mut History, completer: Option<&Completer>, @@ -709,7 +708,7 @@ fn readline_edit(prompt: &str, } else { continue; } - } else if key == KeyPress::UNKNOWN_ESC_SEQ { + } else if key == KeyPress::UnknownEscSeq { continue; } @@ -960,7 +959,6 @@ impl Editor { } /// This method will read a line from STDIN and will display a `prompt` - #[cfg_attr(feature="clippy", allow(if_not_else))] pub fn readline(&mut self, prompt: &str) -> Result { if self.unsupported_term { // Write prompt and flush it to stdout diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 3b46c63e86..4a420c20ba 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -158,10 +158,10 @@ impl RawReader { '3' => Ok(KeyPress::Delete), // TODO '1' // Home // TODO '4' // End - _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), + _ => Ok(KeyPress::UnknownEscSeq), } } else { - Ok(KeyPress::UNKNOWN_ESC_SEQ) + Ok(KeyPress::UnknownEscSeq) } } else { match seq2 { @@ -171,7 +171,7 @@ impl RawReader { 'D' => Ok(KeyPress::Left), 'F' => Ok(KeyPress::End), 'H' => Ok(KeyPress::Home), - _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), + _ => Ok(KeyPress::UnknownEscSeq), } } } else if seq1 == 'O' { @@ -180,7 +180,7 @@ impl RawReader { match seq2 { 'F' => Ok(KeyPress::End), 'H' => Ok(KeyPress::Home), - _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), + _ => Ok(KeyPress::UnknownEscSeq), } } else { // TODO ESC-N (n): search history forward not interactively @@ -201,7 +201,7 @@ impl RawReader { '\x7f' => Ok(KeyPress::Meta('\x7f')), // Delete _ => { // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::Esc, seq1).unwrap(); - Ok(KeyPress::UNKNOWN_ESC_SEQ) + Ok(KeyPress::UnknownEscSeq) } } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index f6427958fe..3d01af532c 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -203,7 +203,7 @@ impl RawReader { 't' | 'T' => return Ok(KeyPress::Meta('T')), 'u' | 'U' => return Ok(KeyPress::Meta('U')), 'y' | 'Y' => return Ok(KeyPress::Meta('Y')), - _ => return Ok(KeyPress::UNKNOWN_ESC_SEQ), + _ => return Ok(KeyPress::UnknownEscSeq), } } else { return Ok(consts::char_to_key_press(c)); From 3dc9140802cc3689c351f4eb0e71cf8ee64f9e60 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 8 Aug 2016 14:53:44 +0200 Subject: [PATCH 0111/1201] Fix clippy warnings --- src/completion.rs | 1 - src/consts.rs | 4 ++-- src/lib.rs | 10 ++++------ src/tty/unix.rs | 10 +++++----- src/tty/windows.rs | 2 +- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index ff87bb85fb..dca94d6d8b 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -86,7 +86,6 @@ impl Completer for FilenameCompleter { } } -#[cfg_attr(feature="clippy", allow(single_char_pattern))] fn filename_complete(path: &str) -> Result> { use std::env::{current_dir, home_dir}; diff --git a/src/consts.rs b/src/consts.rs index 5fc32ea3b4..375afe632c 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,7 +1,7 @@ #[derive(Debug, Clone, PartialEq, Copy)] pub enum KeyPress { - UNKNOWN_ESC_SEQ, + UnknownEscSeq, Backspace, Char(char), Ctrl(char), @@ -19,7 +19,7 @@ pub enum KeyPress { Up, } -#[cfg_attr(feature="clippy", allow(match_same_arms))] +#[allow(match_same_arms)] pub fn char_to_key_press(c: char) -> KeyPress { if !c.is_control() { return KeyPress::Char(c); diff --git a/src/lib.rs b/src/lib.rs index 32ae3f6ed5..27391ec27b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ //! Err(_) => println!("No input"), //! } //! ``` +#![allow(unknown_lints)] extern crate libc; #[cfg(unix)] @@ -26,7 +27,6 @@ extern crate winapi; extern crate kernel32; pub mod completion; -#[allow(non_camel_case_types)] mod consts; pub mod error; pub mod history; @@ -245,7 +245,7 @@ fn beep() -> Result<()> { /// starting at `orig`. /// Control characters are treated as having zero width. /// Characters with 2 column width are correctly handled (not splitted). -#[cfg_attr(feature="clippy", allow(if_same_then_else))] +#[allow(if_same_then_else)] fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { let mut pos = orig; let mut esc_seq = 0; @@ -578,7 +578,6 @@ fn complete_line(rdr: &mut tty::RawReader, } /// Incremental search -#[cfg_attr(feature="clippy", allow(if_not_else))] fn reverse_incremental_search(rdr: &mut tty::RawReader, s: &mut State, history: &History) @@ -658,7 +657,7 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, /// Handles reading and editting the readline buffer. /// It will also handle special inputs in an appropriate fashion /// (e.g., C-c will exit readline) -#[cfg_attr(feature="clippy", allow(cyclomatic_complexity))] +#[allow(let_unit_value)] fn readline_edit(prompt: &str, history: &mut History, completer: Option<&Completer>, @@ -709,7 +708,7 @@ fn readline_edit(prompt: &str, } else { continue; } - } else if key == KeyPress::UNKNOWN_ESC_SEQ { + } else if key == KeyPress::UnknownEscSeq { continue; } @@ -960,7 +959,6 @@ impl Editor { } /// This method will read a line from STDIN and will display a `prompt` - #[cfg_attr(feature="clippy", allow(if_not_else))] pub fn readline(&mut self, prompt: &str) -> Result { if self.unsupported_term { // Write prompt and flush it to stdout diff --git a/src/tty/unix.rs b/src/tty/unix.rs index a2665994cd..106d3120db 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -159,10 +159,10 @@ impl RawReader { '3' => Ok(KeyPress::Delete), // TODO '1' // Home // TODO '4' // End - _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), + _ => Ok(KeyPress::UnknownEscSeq), } } else { - Ok(KeyPress::UNKNOWN_ESC_SEQ) + Ok(KeyPress::UnknownEscSeq) } } else { match seq2 { @@ -172,7 +172,7 @@ impl RawReader { 'D' => Ok(KeyPress::Left), 'F' => Ok(KeyPress::End), 'H' => Ok(KeyPress::Home), - _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), + _ => Ok(KeyPress::UnknownEscSeq), } } } else if seq1 == 'O' { @@ -181,7 +181,7 @@ impl RawReader { match seq2 { 'F' => Ok(KeyPress::End), 'H' => Ok(KeyPress::Home), - _ => Ok(KeyPress::UNKNOWN_ESC_SEQ), + _ => Ok(KeyPress::UnknownEscSeq), } } else { // TODO ESC-N (n): search history forward not interactively @@ -202,7 +202,7 @@ impl RawReader { '\x7f' => Ok(KeyPress::Meta('\x7f')), // Delete _ => { // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::Esc, seq1).unwrap(); - Ok(KeyPress::UNKNOWN_ESC_SEQ) + Ok(KeyPress::UnknownEscSeq) } } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index f6427958fe..3d01af532c 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -203,7 +203,7 @@ impl RawReader { 't' | 'T' => return Ok(KeyPress::Meta('T')), 'u' | 'U' => return Ok(KeyPress::Meta('U')), 'y' | 'Y' => return Ok(KeyPress::Meta('Y')), - _ => return Ok(KeyPress::UNKNOWN_ESC_SEQ), + _ => return Ok(KeyPress::UnknownEscSeq), } } else { return Ok(consts::char_to_key_press(c)); From 2e3ddb5fc81b8c6f2c93bf1d2659a48d96ec927b Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 12 Aug 2016 17:51:42 +0200 Subject: [PATCH 0112/1201] Fix file completion on windows. --- src/completion.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/completion.rs b/src/completion.rs index dca94d6d8b..4ef0216a4a 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -63,8 +63,13 @@ pub struct FilenameCompleter { break_chars: BTreeSet, } +#[cfg(unix)] static DEFAULT_BREAK_CHARS: [char; 18] = [' ', '\t', '\n', '"', '\\', '\'', '`', '@', '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0']; +// Remove \ to make file completion works on windows +#[cfg(windows)] +static DEFAULT_BREAK_CHARS: [char; 17] = [' ', '\t', '\n', '"', '\'', '`', '@', '$', '>', + '<', '=', ';', '|', '&', '{', '(', '\0']; impl FilenameCompleter { pub fn new() -> FilenameCompleter { From b8e934c774a4296485a1c60dddd4794137673bf7 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 12 Aug 2016 17:51:42 +0200 Subject: [PATCH 0113/1201] Fix file completion on windows. --- src/completion.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/completion.rs b/src/completion.rs index dca94d6d8b..4ef0216a4a 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -63,8 +63,13 @@ pub struct FilenameCompleter { break_chars: BTreeSet, } +#[cfg(unix)] static DEFAULT_BREAK_CHARS: [char; 18] = [' ', '\t', '\n', '"', '\\', '\'', '`', '@', '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0']; +// Remove \ to make file completion works on windows +#[cfg(windows)] +static DEFAULT_BREAK_CHARS: [char; 17] = [' ', '\t', '\n', '"', '\'', '`', '@', '$', '>', + '<', '=', ';', '|', '&', '{', '(', '\0']; impl FilenameCompleter { pub fn new() -> FilenameCompleter { From 76cf4d9c367492494db5443bac4659242b8a9ea0 Mon Sep 17 00:00:00 2001 From: kkawakam Date: Sat, 20 Aug 2016 22:51:06 -0400 Subject: [PATCH 0114/1201] Prepare for 1.0.0 release --- .travis.yml | 2 +- README.md | 13 ++++++++----- appveyor.yml | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 130b992efb..f30b67cbda 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: rust rust: - - 1.10.0 + - 1.11.0 - beta - nightly script: diff --git a/README.md b/README.md index 22872813fd..49fed1fe6a 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,23 @@ # RustyLine [![Build Status](https://travis-ci.org/kkawakam/rustyline.svg?branch=master)](https://travis-ci.org/kkawakam/rustyline) +[![Build status](https://ci.appveyor.com/api/projects/status/ls7sty8nt25rdfkq/branch/master?svg=true)](https://ci.appveyor.com/project/kkawakam/rustyline/branch/master) [![Clippy Linting Result](https://clippy.bashy.io/github/kkawakam/rustyline/master/badge.svg)](https://clippy.bashy.io/github/kkawakam/rustyline/master/log) [![](http://meritbadge.herokuapp.com/rustyline)](https://crates.io/crates/rustyline) -[![Build status](https://ci.appveyor.com/api/projects/status/ls7sty8nt25rdfkq/branch/master?svg=true)](https://ci.appveyor.com/project/kkawakam/rustyline/branch/master) Readline implementation in Rust that is based on [Antirez' Linenoise](https://github.com/antirez/linenoise) [Documentation](https://kkawakam.github.io/rustyline) - **Supported Platforms** * Linux -* Windows - Work in Progress (Issue #37), modifier keys do not work +* Windows + * cmd.exe + * Powershell + +**Note**: Powershell ISE is not supported, check [issue #56](https://github.com/kkawakam/rustyline/issues/56) ## Build -This project uses Cargo and Rust Nightly +This project uses Cargo and Rust stable ```bash cargo build --release ``` @@ -63,7 +66,7 @@ to your `Cargo.toml`: ```toml [dependencies] -rustyline = "0.2.3" +rustyline = "1.0.0" ``` ## Features diff --git a/appveyor.yml b/appveyor.yml index 9a62dabbdb..98dde4258e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ environment: matrix: - - TARGET: 1.10.0-x86_64-pc-windows-msvc - - TARGET: 1.10.0-x86_64-pc-windows-gnu + - TARGET: 1.11.0-x86_64-pc-windows-msvc + - TARGET: 1.11.0-x86_64-pc-windows-gnu - TARGET: beta-x86_64-pc-windows-msvc - TARGET: beta-x86_64-pc-windows-gnu install: From bb2bcc6f8009db76b2c6bef8cc941d847b240660 Mon Sep 17 00:00:00 2001 From: kkawakam Date: Sat, 20 Aug 2016 22:53:15 -0400 Subject: [PATCH 0115/1201] Update Cargo.toml to specify 1.0.0 rustyline version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bb21214d86..9b09d189bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustyline" -version = "0.2.3" +version = "1.0.0" authors = ["Katsu Kawakami "] description = "Rustyline, a readline implementation based on Antirez's Linenoise" documentation = "http://kkawakam.github.io/rustyline/rustyline" From ff70828f2225690403b92d0b549c097f8f64bdac Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 23 Aug 2016 18:53:11 +0200 Subject: [PATCH 0116/1201] Prepare support to completion list --- src/completion.rs | 2 +- src/history.rs | 2 +- src/lib.rs | 135 ++++++++++++++++++++++++++-------------------- 3 files changed, 79 insertions(+), 60 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 5069f97bcf..042c98736b 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -55,8 +55,8 @@ macro_rules! box_completer { } } -use std::sync::Arc; use std::rc::Rc; +use std::sync::Arc; box_completer! { Box Rc Arc } pub struct FilenameCompleter { diff --git a/src/history.rs b/src/history.rs index 210d8a2f45..a3222e2af7 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,8 +1,8 @@ //! History API use std::collections::VecDeque; -use std::path::Path; use std::fs::File; +use std::path::Path; use super::Result; diff --git a/src/lib.rs b/src/lib.rs index 9ea0c94180..efcf868a89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -526,16 +526,29 @@ fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { s.refresh_line() } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CompletionMode { + /// Complete the next full match (like in Vim by default) + Circular, + /// Complete till longest match. + /// When more than one match, list all matches + /// (like in Bash/Readline). + List, +} + /// Completes the line/word fn complete_line(rdr: &mut tty::RawReader, s: &mut State, - completer: &Completer) + completer: &Completer, + completion_mode: CompletionMode) -> Result> { + // get a list of completions let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); + // if no completions, we are done if candidates.is_empty() { try!(beep()); Ok(None) - } else { + } else if CompletionMode::Circular == completion_mode { // Save the current edited line before to overwrite it s.backup(); let mut key; @@ -574,6 +587,9 @@ fn complete_line(rdr: &mut tty::RawReader, } } Ok(Some(key)) + } else { + // TODO ... + Ok(None) } } @@ -658,17 +674,17 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, /// It will also handle special inputs in an appropriate fashion /// (e.g., C-c will exit readline) #[allow(let_unit_value)] -fn readline_edit(prompt: &str, - history: &mut History, - completer: Option<&Completer>, - kill_ring: &mut KillRing, - original_mode: tty::Mode) - -> Result { +fn readline_edit(prompt: &str, + editor: &mut Editor, + original_mode: tty::Mode) + -> Result { + let completer = editor.completer.as_ref().map(|c| c as &Completer); + let mut stdout = io::stdout(); let stdout_handle = try!(tty::stdout_handle()); - kill_ring.reset(); - let mut s = State::new(&mut stdout, stdout_handle, prompt, history.len()); + editor.kill_ring.reset(); + let mut s = State::new(&mut stdout, stdout_handle, prompt, editor.history.len()); try!(s.refresh_line()); let mut rdr = try!(tty::RawReader::new(io::stdin())); @@ -682,16 +698,17 @@ fn readline_edit(prompt: &str, } let mut key = try!(rk); if let KeyPress::Char(c) = key { - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_insert(&mut s, c)); continue; } // autocomplete if key == KeyPress::Tab && completer.is_some() { - let next = try!(complete_line(&mut rdr, &mut s, completer.unwrap())); + let next = + try!(complete_line(&mut rdr, &mut s, completer.unwrap(), editor.completion_mode)); if next.is_some() { - kill_ring.reset(); + editor.kill_ring.reset(); key = next.unwrap(); if let KeyPress::Char(c) = key { try!(edit_insert(&mut s, c)); @@ -702,7 +719,7 @@ fn readline_edit(prompt: &str, } } else if key == KeyPress::Ctrl('R') { // Search history backward - let next = try!(reverse_incremental_search(&mut rdr, &mut s, history)); + let next = try!(reverse_incremental_search(&mut rdr, &mut s, &editor.history)); if next.is_some() { key = next.unwrap(); } else { @@ -715,22 +732,22 @@ fn readline_edit(prompt: &str, match key { KeyPress::Ctrl('A') | KeyPress::Home => { - kill_ring.reset(); + editor.kill_ring.reset(); // Move to the beginning of line. try!(edit_move_home(&mut s)) } KeyPress::Ctrl('B') | KeyPress::Left => { - kill_ring.reset(); + editor.kill_ring.reset(); // Move back a character. try!(edit_move_left(&mut s)) } KeyPress::Ctrl('C') => { - kill_ring.reset(); + editor.kill_ring.reset(); return Err(error::ReadlineError::Interrupted); } KeyPress::Ctrl('D') => { - kill_ring.reset(); + editor.kill_ring.reset(); if s.line.is_empty() { return Err(error::ReadlineError::Eof); } else { @@ -740,26 +757,26 @@ fn readline_edit(prompt: &str, } KeyPress::Ctrl('E') | KeyPress::End => { - kill_ring.reset(); + editor.kill_ring.reset(); // Move to the end of line. try!(edit_move_end(&mut s)) } KeyPress::Ctrl('F') | KeyPress::Right => { - kill_ring.reset(); + editor.kill_ring.reset(); // Move forward a character. try!(edit_move_right(&mut s)) } KeyPress::Ctrl('H') | KeyPress::Backspace => { - kill_ring.reset(); + editor.kill_ring.reset(); // Delete one character backward. try!(edit_backspace(&mut s)) } KeyPress::Ctrl('K') => { // Kill the text from point to the end of the line. if let Some(text) = try!(edit_kill_line(&mut s)) { - kill_ring.kill(&text, true) + editor.kill_ring.kill(&text, true) } } KeyPress::Ctrl('L') => { @@ -769,43 +786,43 @@ fn readline_edit(prompt: &str, } KeyPress::Ctrl('N') | KeyPress::Down => { - kill_ring.reset(); + editor.kill_ring.reset(); // Fetch the next command from the history list. - try!(edit_history_next(&mut s, history, false)) + try!(edit_history_next(&mut s, &editor.history, false)) } KeyPress::Ctrl('P') | KeyPress::Up => { - kill_ring.reset(); + editor.kill_ring.reset(); // Fetch the previous command from the history list. - try!(edit_history_next(&mut s, history, true)) + try!(edit_history_next(&mut s, &editor.history, true)) } KeyPress::Ctrl('T') => { - kill_ring.reset(); + editor.kill_ring.reset(); // Exchange the char before cursor with the character at cursor. try!(edit_transpose_chars(&mut s)) } KeyPress::Ctrl('U') => { // Kill backward from point to the beginning of the line. if let Some(text) = try!(edit_discard_line(&mut s)) { - kill_ring.kill(&text, false) + editor.kill_ring.kill(&text, false) } } #[cfg(unix)] KeyPress::Ctrl('V') => { // Quoted insert - kill_ring.reset(); + editor.kill_ring.reset(); let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c)) // FIXME } KeyPress::Ctrl('W') => { // Kill the word behind point, using white space as a word boundary if let Some(text) = try!(edit_delete_prev_word(&mut s, char::is_whitespace)) { - kill_ring.kill(&text, false) + editor.kill_ring.kill(&text, false) } } KeyPress::Ctrl('Y') => { // retrieve (yank) last item killed - if let Some(text) = kill_ring.yank() { + if let Some(text) = editor.kill_ring.yank() { try!(edit_yank(&mut s, text)) } } @@ -820,7 +837,7 @@ fn readline_edit(prompt: &str, KeyPress::Enter | KeyPress::Ctrl('J') => { // Accept the line regardless of where the cursor is. - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_move_end(&mut s)); break; } @@ -830,67 +847,67 @@ fn readline_edit(prompt: &str, // Kill from the cursor to the start of the current word, or, if between words, to the start of the previous word. if let Some(text) = try!(edit_delete_prev_word(&mut s, |ch| !ch.is_alphanumeric())) { - kill_ring.kill(&text, false) + editor.kill_ring.kill(&text, false) } } KeyPress::Meta('<') => { // move to first entry in history - kill_ring.reset(); - try!(edit_history(&mut s, history, true)) + editor.kill_ring.reset(); + try!(edit_history(&mut s, &editor.history, true)) } KeyPress::Meta('>') => { // move to last entry in history - kill_ring.reset(); - try!(edit_history(&mut s, history, false)) + editor.kill_ring.reset(); + try!(edit_history(&mut s, &editor.history, false)) } KeyPress::Meta('B') => { // move backwards one word - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_move_to_prev_word(&mut s)) } KeyPress::Meta('C') => { // capitalize word after point - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } KeyPress::Meta('D') => { // kill one word forward if let Some(text) = try!(edit_delete_word(&mut s)) { - kill_ring.kill(&text, true) + editor.kill_ring.kill(&text, true) } } KeyPress::Meta('F') => { // move forwards one word - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_move_to_next_word(&mut s)) } KeyPress::Meta('L') => { // lowercase word after point - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::LOWERCASE)) } KeyPress::Meta('T') => { // transpose words - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_transpose_words(&mut s)) } KeyPress::Meta('U') => { // uppercase word after point - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::UPPERCASE)) } KeyPress::Meta('Y') => { // yank-pop - if let Some((yank_size, text)) = kill_ring.yank_pop() { + if let Some((yank_size, text)) = editor.kill_ring.yank_pop() { try!(edit_yank_pop(&mut s, yank_size, text)) } } KeyPress::Delete => { - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_delete(&mut s)) } _ => { - kill_ring.reset(); + editor.kill_ring.reset(); // Ignore the character typed. } } @@ -910,14 +927,10 @@ impl Drop for Guard { /// Readline method that will enable RAW mode, call the `readline_edit()` /// method and disable raw mode -fn readline_raw(prompt: &str, - history: &mut History, - completer: Option<&Completer>, - kill_ring: &mut KillRing) - -> Result { +fn readline_raw(prompt: &str, editor: &mut Editor) -> Result { let original_mode = try!(tty::enable_raw_mode()); let guard = Guard(original_mode); - let user_input = readline_edit(prompt, history, completer, kill_ring, original_mode); + let user_input = readline_edit(prompt, editor, original_mode); drop(guard); // try!(disable_raw_mode(original_mode)); println!(""); user_input @@ -940,6 +953,7 @@ pub struct Editor { history: History, completer: Option, kill_ring: KillRing, + completion_mode: CompletionMode, } impl Editor { @@ -951,6 +965,7 @@ impl Editor { history: History::new(), completer: None, kill_ring: KillRing::new(60), + completion_mode: CompletionMode::Circular, }; if !editor.unsupported_term && editor.stdin_isatty && editor.stdout_isatty { tty::install_sigwinch_handler(); @@ -970,10 +985,7 @@ impl Editor { // Not a tty: read from file / pipe. readline_direct() } else { - readline_raw(prompt, - &mut self.history, - self.completer.as_ref().map(|c| c as &Completer), - &mut self.kill_ring) + readline_raw(prompt, self) } } @@ -1020,6 +1032,11 @@ impl Editor { pub fn set_completer(&mut self, completer: Option) { self.completer = completer; } + + /// Set completion mode. + pub fn set_completion_mode(&mut self, completion_mode: CompletionMode) { + self.completion_mode = completion_mode; + } } impl Default for Editor { @@ -1043,6 +1060,7 @@ mod test { use line_buffer::LineBuffer; use history::History; use completion::Completer; + use CompletionMode; use State; use super::Result; use tty::Handle; @@ -1125,7 +1143,8 @@ mod test { let input = b"\n"; let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; - let key = super::complete_line(&mut rdr, &mut s, &completer).unwrap(); + let key = super::complete_line(&mut rdr, &mut s, &completer, CompletionMode::Circular) + .unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); From 8b9d97cd64d872d5aeac82f6cd2fb44a501076d2 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 24 Aug 2016 20:34:39 +0200 Subject: [PATCH 0117/1201] Introduce longest_common_prefix function --- src/completion.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 18 +++++++++++++++-- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 042c98736b..0c9ebbd416 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -159,6 +159,33 @@ pub fn extract_word<'l>(line: &'l str, } } +pub fn longest_common_prefix(candidates: &[String]) -> Option { + if candidates.is_empty() { + return None; + } else if candidates.len() == 1 { + return Some(candidates[0].clone()); + } + let mut longest_common_prefix = 0; + 'o: loop { + for i in 0..candidates.len() - 1 { + let b1 = candidates[i].as_bytes(); + let b2 = candidates[i + 1].as_bytes(); + if b1.len() <= longest_common_prefix || b2.len() <= longest_common_prefix || + b1[i] != b2[i] { + break 'o; + } + } + longest_common_prefix += 1; + } + while !candidates[0].is_char_boundary(longest_common_prefix) { + longest_common_prefix -= 1; + } + if longest_common_prefix == 0 { + return None; + } + Some(String::from(&candidates[0][0..longest_common_prefix])) +} + #[cfg(test)] mod tests { use std::collections::BTreeSet; @@ -170,4 +197,26 @@ mod tests { assert_eq!((4, "/usr/local/b"), super::extract_word(line, line.len(), &break_chars)); } + + #[test] + pub fn longest_common_prefix() { + let mut candidates = vec![]; + let lcp = super::longest_common_prefix(&candidates); + assert!(lcp.is_none()); + + let c1 = String::from("User"); + candidates.push(c1.clone()); + let lcp = super::longest_common_prefix(&candidates); + assert_eq!(Some(c1.clone()), lcp); + + let c2 = String::from("Users"); + candidates.push(c2.clone()); + let lcp = super::longest_common_prefix(&candidates); + assert_eq!(Some(c1), lcp); + + let c3 = String::from(""); + candidates.push(c3.clone()); + let lcp = super::longest_common_prefix(&candidates); + assert!(lcp.is_none()); + } } diff --git a/src/lib.rs b/src/lib.rs index efcf868a89..1dca7924e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,7 @@ use std::sync::atomic; #[cfg(unix)] use nix::sys::signal; -use completion::Completer; +use completion::{Completer, longest_common_prefix}; use consts::KeyPress; use history::History; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; @@ -587,9 +587,23 @@ fn complete_line(rdr: &mut tty::RawReader, } } Ok(Some(key)) - } else { + } else if CompletionMode::List == completion_mode { + // beep if ambiguous + if candidates.len() > 1 { + try!(beep()); + } + if let Some(lcp) = longest_common_prefix(&candidates) { + // if we can extend the item, extend it and return to main loop + if lcp.len() > s.line.pos() - start { + completer.update(&mut s.line, start, &lcp); + try!(s.refresh_line()); + return Ok(None); + } + } // TODO ... Ok(None) + } else { + Ok(None) } } From ee71de69cfa80881a0db18bbaf1c33f1ab2497df Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 24 Aug 2016 20:57:53 +0200 Subject: [PATCH 0118/1201] Fix longest_common_prefix --- src/completion.rs | 33 +++++++++++++++++++++------------ src/lib.rs | 2 +- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 0c9ebbd416..9b656354b0 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -159,11 +159,11 @@ pub fn extract_word<'l>(line: &'l str, } } -pub fn longest_common_prefix(candidates: &[String]) -> Option { +pub fn longest_common_prefix(candidates: &[String]) -> Option<&str> { if candidates.is_empty() { return None; } else if candidates.len() == 1 { - return Some(candidates[0].clone()); + return Some(&candidates[0]); } let mut longest_common_prefix = 0; 'o: loop { @@ -183,7 +183,7 @@ pub fn longest_common_prefix(candidates: &[String]) -> Option { if longest_common_prefix == 0 { return None; } - Some(String::from(&candidates[0][0..longest_common_prefix])) + Some(&candidates[0][0..longest_common_prefix]) } #[cfg(test)] @@ -201,22 +201,31 @@ mod tests { #[test] pub fn longest_common_prefix() { let mut candidates = vec![]; - let lcp = super::longest_common_prefix(&candidates); - assert!(lcp.is_none()); + { + let lcp = super::longest_common_prefix(&candidates); + assert!(lcp.is_none()); + } - let c1 = String::from("User"); + let s = "User"; + let c1 = String::from(s); candidates.push(c1.clone()); - let lcp = super::longest_common_prefix(&candidates); - assert_eq!(Some(c1.clone()), lcp); + { + let lcp = super::longest_common_prefix(&candidates); + assert_eq!(Some(s), lcp); + } let c2 = String::from("Users"); candidates.push(c2.clone()); - let lcp = super::longest_common_prefix(&candidates); - assert_eq!(Some(c1), lcp); + { + let lcp = super::longest_common_prefix(&candidates); + assert_eq!(Some(s), lcp); + } let c3 = String::from(""); candidates.push(c3.clone()); - let lcp = super::longest_common_prefix(&candidates); - assert!(lcp.is_none()); + { + let lcp = super::longest_common_prefix(&candidates); + assert!(lcp.is_none()); + } } } diff --git a/src/lib.rs b/src/lib.rs index 1dca7924e4..661be72479 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -595,7 +595,7 @@ fn complete_line(rdr: &mut tty::RawReader, if let Some(lcp) = longest_common_prefix(&candidates) { // if we can extend the item, extend it and return to main loop if lcp.len() > s.line.pos() - start { - completer.update(&mut s.line, start, &lcp); + completer.update(&mut s.line, start, lcp); try!(s.refresh_line()); return Ok(None); } From ad15ac38d7438183bc69a0cebaec740f8cb428a4 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 25 Aug 2016 18:40:10 +0200 Subject: [PATCH 0119/1201] Fix longest_common_prefix --- src/completion.rs | 6 +++++- src/lib.rs | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/completion.rs b/src/completion.rs index 9b656354b0..8210fd4edf 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -171,7 +171,7 @@ pub fn longest_common_prefix(candidates: &[String]) -> Option<&str> { let b1 = candidates[i].as_bytes(); let b2 = candidates[i + 1].as_bytes(); if b1.len() <= longest_common_prefix || b2.len() <= longest_common_prefix || - b1[i] != b2[i] { + b1[longest_common_prefix] != b2[longest_common_prefix] { break 'o; } } @@ -227,5 +227,9 @@ mod tests { let lcp = super::longest_common_prefix(&candidates); assert!(lcp.is_none()); } + + let candidates = vec![String::from("fée"), String::from("fête")]; + let lcp = super::longest_common_prefix(&candidates); + assert_eq!(Some("f"), lcp); } } diff --git a/src/lib.rs b/src/lib.rs index 661be72479..efce569b0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -600,6 +600,13 @@ fn complete_line(rdr: &mut tty::RawReader, return Ok(None); } } + // we can't complete any further, wait for second tab + let key = try!(rdr.next_key(false)); + // if any character other than tab, pass it to the main loop + if key != KeyPress::Tab { + return Ok(Some(key)) + } + // we got a second tab, maybe show list of possible completions // TODO ... Ok(None) } else { From 43ab7f2bb230a9d68c3c9ea71ae97f242e1c1000 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 25 Aug 2016 18:40:28 +0200 Subject: [PATCH 0120/1201] Misc --- src/completion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/completion.rs b/src/completion.rs index 8210fd4edf..2917bd21e1 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -123,7 +123,7 @@ fn filename_complete(path: &str) -> Result> { }; let mut entries: Vec = Vec::new(); - for entry in try!(fs::read_dir(dir)) { + for entry in try!(dir.read_dir()) { let entry = try!(entry); if let Some(s) = entry.file_name().to_str() { if s.starts_with(file_name) { From 367ada41e7627bbd30eb05f56d95dd2700e0304e Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 25 Aug 2016 21:33:11 +0200 Subject: [PATCH 0121/1201] Introduce Config struct. BREAKING CHANGE --- examples/example.rs | 7 ++- src/config.rs | 124 ++++++++++++++++++++++++++++++++++++++++++++ src/history.rs | 43 +++++---------- src/lib.rs | 84 +++++++++--------------------- 4 files changed, 168 insertions(+), 90 deletions(-) create mode 100644 src/config.rs diff --git a/examples/example.rs b/examples/example.rs index 62fb4fe609..18bf58b4b7 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -2,7 +2,7 @@ extern crate rustyline; use rustyline::completion::FilenameCompleter; use rustyline::error::ReadlineError; -use rustyline::Editor; +use rustyline::{Config, Editor}; // On unix platforms you can use ANSI escape sequences #[cfg(unix)] @@ -14,8 +14,11 @@ static PROMPT: &'static str = "\x1b[1;32m>>\x1b[0m "; static PROMPT: &'static str = ">> "; fn main() { + let config = Config::builder() + .history_ignore_space(true) + .build(); let c = FilenameCompleter::new(); - let mut rl = Editor::new().history_ignore_space(true); + let mut rl = Editor::new(config); rl.set_completer(Some(c)); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000000..49add02a4e --- /dev/null +++ b/src/config.rs @@ -0,0 +1,124 @@ +//! Customize line editor +use std::default::Default; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Config { + /// When listing completion alternatives, only display + /// one screen of possibilities at a time. + max_history_size: usize, + history_duplicates: HistoryDuplicates, + history_ignore_space: bool, + completion_type: CompletionType, + /// When listing completion alternatives, only display + /// one screen of possibilities at a time. + completion_prompt_limit: usize, +} + +impl Config { + pub fn builder() -> Builder { + Builder::new() + } + + /// Tell the maximum length for the history. + pub fn max_history_size(&self) -> usize { + self.max_history_size + } + + /// Tell if lines which match the previous history entry are saved or not in the history list. + /// By default, they are ignored. + pub fn history_duplicates(&self) -> HistoryDuplicates { + self.history_duplicates + } + + /// Tell if lines which begin with a space character are saved or not in the history list. + /// By default, they are saved. + pub fn history_ignore_space(&self) -> bool { + self.history_ignore_space + } + + pub fn completion_type(&self) -> CompletionType { + self.completion_type + } + + pub fn completion_prompt_limit(&self) -> usize { + self.completion_prompt_limit + } +} + +impl Default for Config { + fn default() -> Config { + Config { + max_history_size: 100, + history_duplicates: HistoryDuplicates::IgnoreConsecutive, + history_ignore_space: false, + completion_type: CompletionType::Circular, // TODO Validate + completion_prompt_limit: 100, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum HistoryDuplicates { + AlwaysAdd, + IgnoreConsecutive, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CompletionType { + /// Complete the next full match (like in Vim by default) + Circular, + /// Complete till longest match. + /// When more than one match, list all matches + /// (like in Bash/Readline). + List, +} + +#[derive(Debug)] +pub struct Builder { + p: Config, +} + +impl Builder { + pub fn new() -> Builder { + Builder { p: Config::default() } + } + + /// Set the maximum length for the history. + pub fn max_history_size(mut self, max_size: usize) -> Builder { + self.p.max_history_size = max_size; + self + } + + /// Tell if lines which match the previous history entry are saved or not in the history list. + /// By default, they are ignored. + pub fn history_ignore_dups(mut self, yes: bool) -> Builder { + self.p.history_duplicates = if yes { + HistoryDuplicates::IgnoreConsecutive + } else { + HistoryDuplicates::AlwaysAdd + }; + self + } + + /// Tell if lines which begin with a space character are saved or not in the history list. + /// By default, they are saved. + pub fn history_ignore_space(mut self, yes: bool) -> Builder { + self.p.history_ignore_space = yes; + self + } + + /// Set `completion_type`. + pub fn completion_type(mut self, completion_type: CompletionType) -> Builder { + self.p.completion_type = completion_type; + self + } + + pub fn completion_prompt_limit(mut self, completion_prompt_limit: usize) -> Builder { + self.p.completion_prompt_limit = completion_prompt_limit; + self + } + + pub fn build(self) -> Config { + self.p + } +} diff --git a/src/history.rs b/src/history.rs index a3222e2af7..5d40d5e38d 100644 --- a/src/history.rs +++ b/src/history.rs @@ -5,6 +5,7 @@ use std::fs::File; use std::path::Path; use super::Result; +use config::{Config, HistoryDuplicates}; pub struct History { entries: VecDeque, @@ -13,30 +14,16 @@ pub struct History { ignore_dups: bool, } -const DEFAULT_HISTORY_MAX_LEN: usize = 100; - impl History { - pub fn new() -> History { + pub fn new(config: Config) -> History { History { entries: VecDeque::new(), - max_len: DEFAULT_HISTORY_MAX_LEN, - ignore_space: false, - ignore_dups: true, + max_len: config.max_history_size(), + ignore_space: config.history_duplicates() == HistoryDuplicates::IgnoreConsecutive, + ignore_dups: config.history_ignore_space(), } } - /// Tell if lines which begin with a space character are saved or not in the history list. - /// By default, they are saved. - pub fn ignore_space(&mut self, yes: bool) { - self.ignore_space = yes; - } - - /// Tell if lines which match the previous history entry are saved or not in the history list. - /// By default, they are ignored. - pub fn ignore_dups(&mut self, yes: bool) { - self.ignore_dups = yes; - } - /// Return the history entry at position `index`, starting from 0. pub fn get(&self, index: usize) -> Option<&String> { self.entries.get(index) @@ -149,19 +136,14 @@ impl History { } } -impl Default for History { - fn default() -> History { - History::new() - } -} - #[cfg(test)] mod tests { extern crate tempdir; use std::path::Path; + use config::Config; fn init() -> super::History { - let mut history = super::History::new(); + let mut history = super::History::new(Config::default()); assert!(history.add("line1")); assert!(history.add("line2")); assert!(history.add("line3")); @@ -170,15 +152,18 @@ mod tests { #[test] fn new() { - let history = super::History::new(); - assert_eq!(super::DEFAULT_HISTORY_MAX_LEN, history.max_len); + let config = Config::default(); + let history = super::History::new(config); + assert_eq!(config.max_history_size(), history.max_len); assert_eq!(0, history.entries.len()); } #[test] fn add() { - let mut history = super::History::new(); - history.ignore_space(true); + let config = Config::builder() + .history_ignore_space(true) + .build(); + let mut history = super::History::new(config); assert!(history.add("line1")); assert!(history.add("line2")); assert!(!history.add("line2")); diff --git a/src/lib.rs b/src/lib.rs index efce569b0f..4d5c094202 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,8 @@ //! Usage //! //! ``` -//! let mut rl = rustyline::Editor::<()>::new(); +//! let config = rustyline::Config::default(); +//! let mut rl = rustyline::Editor::<()>::new(config); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -33,6 +34,7 @@ pub mod error; pub mod history; mod kill_ring; pub mod line_buffer; +pub mod config; #[macro_use] mod tty; @@ -51,6 +53,7 @@ use consts::KeyPress; use history::History; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; use kill_ring::KillRing; +pub use config::{CompletionType, Config, HistoryDuplicates}; /// The error type for I/O and Linux Syscalls (Errno) pub type Result = result::Result; @@ -83,7 +86,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { -> State<'out, 'prompt> { let capacity = MAX_LINE; let cols = tty::get_columns(output_handle); - let prompt_size = calculate_position(prompt, Default::default(), cols); + let prompt_size = calculate_position(prompt, Position::default(), cols); State { out: out, prompt: prompt, @@ -114,7 +117,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { } fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> { - let prompt_size = calculate_position(prompt, Default::default(), self.cols); + let prompt_size = calculate_position(prompt, Position::default(), self.cols); self.refresh(prompt, prompt_size) } @@ -526,21 +529,11 @@ fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { s.refresh_line() } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum CompletionMode { - /// Complete the next full match (like in Vim by default) - Circular, - /// Complete till longest match. - /// When more than one match, list all matches - /// (like in Bash/Readline). - List, -} - /// Completes the line/word fn complete_line(rdr: &mut tty::RawReader, s: &mut State, completer: &Completer, - completion_mode: CompletionMode) + completion_type: CompletionType) -> Result> { // get a list of completions let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); @@ -548,7 +541,7 @@ fn complete_line(rdr: &mut tty::RawReader, if candidates.is_empty() { try!(beep()); Ok(None) - } else if CompletionMode::Circular == completion_mode { + } else if CompletionType::Circular == completion_type { // Save the current edited line before to overwrite it s.backup(); let mut key; @@ -587,7 +580,7 @@ fn complete_line(rdr: &mut tty::RawReader, } } Ok(Some(key)) - } else if CompletionMode::List == completion_mode { + } else if CompletionType::List == completion_type { // beep if ambiguous if candidates.len() > 1 { try!(beep()); @@ -604,7 +597,7 @@ fn complete_line(rdr: &mut tty::RawReader, let key = try!(rdr.next_key(false)); // if any character other than tab, pass it to the main loop if key != KeyPress::Tab { - return Ok(Some(key)) + return Ok(Some(key)); } // we got a second tab, maybe show list of possible completions // TODO ... @@ -726,8 +719,10 @@ fn readline_edit(prompt: &str, // autocomplete if key == KeyPress::Tab && completer.is_some() { - let next = - try!(complete_line(&mut rdr, &mut s, completer.unwrap(), editor.completion_mode)); + let next = try!(complete_line(&mut rdr, + &mut s, + completer.unwrap(), + editor.config.completion_type())); if next.is_some() { editor.kill_ring.reset(); key = next.unwrap(); @@ -974,19 +969,19 @@ pub struct Editor { history: History, completer: Option, kill_ring: KillRing, - completion_mode: CompletionMode, + config: Config, } impl Editor { - pub fn new() -> Editor { + pub fn new(config: Config) -> Editor { let editor = Editor { unsupported_term: tty::is_unsupported_term(), stdin_isatty: tty::is_a_tty(tty::STDIN_FILENO), stdout_isatty: tty::is_a_tty(tty::STDOUT_FILENO), - history: History::new(), + history: History::new(config), completer: None, kill_ring: KillRing::new(60), - completion_mode: CompletionMode::Circular, + config: config, }; if !editor.unsupported_term && editor.stdin_isatty && editor.stdout_isatty { tty::install_sigwinch_handler(); @@ -1010,20 +1005,6 @@ impl Editor { } } - /// Tell if lines which match the previous history entry are saved or not in the history list. - /// By default, they are ignored. - pub fn history_ignore_dups(mut self, yes: bool) -> Editor { - self.history.ignore_dups(yes); - self - } - - /// Tell if lines which begin with a space character are saved or not in the history list. - /// By default, they are saved. - pub fn history_ignore_space(mut self, yes: bool) -> Editor { - self.history.ignore_space(yes); - self - } - /// Load the history from the specified file. pub fn load_history + ?Sized>(&mut self, path: &P) -> Result<()> { self.history.load(path) @@ -1036,10 +1017,6 @@ impl Editor { pub fn add_history_entry(&mut self, line: &str) -> bool { self.history.add(line) } - /// Set the maximum length for the history. - pub fn set_history_max_len(&mut self, max_len: usize) { - self.history.set_max_len(max_len) - } /// Clear history. pub fn clear_history(&mut self) { self.history.clear() @@ -1053,17 +1030,6 @@ impl Editor { pub fn set_completer(&mut self, completer: Option) { self.completer = completer; } - - /// Set completion mode. - pub fn set_completion_mode(&mut self, completion_mode: CompletionMode) { - self.completion_mode = completion_mode; - } -} - -impl Default for Editor { - fn default() -> Editor { - Editor::new() - } } impl fmt::Debug for Editor { @@ -1081,8 +1047,8 @@ mod test { use line_buffer::LineBuffer; use history::History; use completion::Completer; - use CompletionMode; - use State; + use config::{Config, CompletionType}; + use {Position, State}; use super::Result; use tty::Handle; @@ -1098,9 +1064,9 @@ mod test { State { out: out, prompt: "", - prompt_size: Default::default(), + prompt_size: Position::default(), line: LineBuffer::init(line, pos), - cursor: Default::default(), + cursor: Position::default(), cols: cols, old_rows: 0, history_index: 0, @@ -1114,7 +1080,7 @@ mod test { let mut out = ::std::io::sink(); let line = "current edited line"; let mut s = init_state(&mut out, line, 6, 80); - let mut history = History::new(); + let mut history = History::new(Config::default()); history.add("line0"); history.add("line1"); s.history_index = history.len(); @@ -1164,7 +1130,7 @@ mod test { let input = b"\n"; let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; - let key = super::complete_line(&mut rdr, &mut s, &completer, CompletionMode::Circular) + let key = super::complete_line(&mut rdr, &mut s, &completer, CompletionType::Circular) .unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); assert_eq!("rust", s.line.as_str()); @@ -1173,7 +1139,7 @@ mod test { #[test] fn prompt_with_ansi_escape_codes() { - let pos = super::calculate_position("\x1b[1;32m>>\x1b[0m ", Default::default(), 80); + let pos = super::calculate_position("\x1b[1;32m>>\x1b[0m ", Position::default(), 80); assert_eq!(3, pos.col); assert_eq!(0, pos.row); } From 7dfcf859e93ddb7e1db84cddb1f94d234222514f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 28 Aug 2016 11:13:28 +0200 Subject: [PATCH 0122/1201] Start page completions --- src/lib.rs | 53 ++++++++++++++++++++++++++++++++++------------ src/line_buffer.rs | 4 ++++ 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4d5c094202..d5d5ae6516 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -533,7 +533,7 @@ fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { fn complete_line(rdr: &mut tty::RawReader, s: &mut State, completer: &Completer, - completion_type: CompletionType) + config: &Config) -> Result> { // get a list of completions let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); @@ -541,7 +541,7 @@ fn complete_line(rdr: &mut tty::RawReader, if candidates.is_empty() { try!(beep()); Ok(None) - } else if CompletionType::Circular == completion_type { + } else if CompletionType::Circular == config.completion_type() { // Save the current edited line before to overwrite it s.backup(); let mut key; @@ -580,7 +580,7 @@ fn complete_line(rdr: &mut tty::RawReader, } } Ok(Some(key)) - } else if CompletionType::List == completion_type { + } else if CompletionType::List == config.completion_type() { // beep if ambiguous if candidates.len() > 1 { try!(beep()); @@ -594,19 +594,50 @@ fn complete_line(rdr: &mut tty::RawReader, } } // we can't complete any further, wait for second tab - let key = try!(rdr.next_key(false)); + let mut key = try!(rdr.next_key(false)); // if any character other than tab, pass it to the main loop if key != KeyPress::Tab { return Ok(Some(key)); } // we got a second tab, maybe show list of possible completions - // TODO ... - Ok(None) + let mut show_completions = true; + if candidates.len() > config.completion_prompt_limit() { + // move cursor to EOL to avoid overwriting the command line + let save_pos = s.line.pos(); + try!(edit_move_end(s)); + s.line.set_pos(save_pos); + let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len()); + try!(write_and_flush(s.out, msg.as_bytes())); + s.old_rows += 1; + while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && + key != KeyPress::Char('n') && key != KeyPress::Char('N') && + key != KeyPress::Backspace { + key = try!(rdr.next_key(true)); + } + show_completions = match key { + KeyPress::Char('y') | + KeyPress::Char('Y') => true, + _ => false, + }; + } + if show_completions { + page_completions(rdr, s) + } else { + try!(s.refresh_line()); + Ok(None) + } } else { Ok(None) } } +fn page_completions(r: &mut tty::RawReader, + s: &mut State) + -> Result> { + // TODO + Ok(None) +} + /// Incremental search fn reverse_incremental_search(rdr: &mut tty::RawReader, s: &mut State, @@ -719,10 +750,7 @@ fn readline_edit(prompt: &str, // autocomplete if key == KeyPress::Tab && completer.is_some() { - let next = try!(complete_line(&mut rdr, - &mut s, - completer.unwrap(), - editor.config.completion_type())); + let next = try!(complete_line(&mut rdr, &mut s, completer.unwrap(), &editor.config)); if next.is_some() { editor.kill_ring.reset(); key = next.unwrap(); @@ -1047,7 +1075,7 @@ mod test { use line_buffer::LineBuffer; use history::History; use completion::Completer; - use config::{Config, CompletionType}; + use config::Config; use {Position, State}; use super::Result; use tty::Handle; @@ -1130,8 +1158,7 @@ mod test { let input = b"\n"; let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; - let key = super::complete_line(&mut rdr, &mut s, &completer, CompletionType::Circular) - .unwrap(); + let key = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); diff --git a/src/line_buffer.rs b/src/line_buffer.rs index e1c4602ae9..abfe1856dc 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -47,6 +47,10 @@ impl LineBuffer { pub fn pos(&self) -> usize { self.pos } + pub fn set_pos(&mut self, pos: usize) { + assert!(pos <= self.buf.len()); + self.pos = pos; + } /// Returns the length of this buffer, in bytes. pub fn len(&self) -> usize { From e7aaef7d4de7963c17b04723c0aa544d8bccaac1 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 28 Aug 2016 16:08:14 +0200 Subject: [PATCH 0123/1201] More page completions --- src/lib.rs | 73 +++++++++++++++++++++++++++++++++++++++++----- src/tty/unix.rs | 16 ++++++++-- src/tty/windows.rs | 19 ++++++++++-- 3 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d5d5ae6516..12d395420c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -599,13 +599,13 @@ fn complete_line(rdr: &mut tty::RawReader, if key != KeyPress::Tab { return Ok(Some(key)); } + // move cursor to EOL to avoid overwriting the command line + let save_pos = s.line.pos(); + try!(edit_move_end(s)); + s.line.set_pos(save_pos); // we got a second tab, maybe show list of possible completions let mut show_completions = true; if candidates.len() > config.completion_prompt_limit() { - // move cursor to EOL to avoid overwriting the command line - let save_pos = s.line.pos(); - try!(edit_move_end(s)); - s.line.set_pos(save_pos); let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len()); try!(write_and_flush(s.out, msg.as_bytes())); s.old_rows += 1; @@ -621,7 +621,7 @@ fn complete_line(rdr: &mut tty::RawReader, }; } if show_completions { - page_completions(rdr, s) + page_completions(rdr, s, &candidates) } else { try!(s.refresh_line()); Ok(None) @@ -631,9 +631,66 @@ fn complete_line(rdr: &mut tty::RawReader, } } -fn page_completions(r: &mut tty::RawReader, - s: &mut State) - -> Result> { +fn page_completions(rdr: &mut tty::RawReader, + s: &mut State, + candidates: &[String]) + -> Result> { + use std::cmp; + use unicode_width::UnicodeWidthStr; + + let min_col_pad = 2; + let max_width = cmp::min(s.cols, + candidates.into_iter() + .map(|s| UnicodeWidthStr::width(s.as_str())) + .max() + .unwrap() + min_col_pad); + let num_cols = s.cols / max_width; + + let mut pause_row = tty::get_rows(s.output_handle) - 1; + let num_rows = (candidates.len() + num_cols - 1) / num_cols; + let mut ab = String::new(); + for row in 0..num_rows { + if row == pause_row { + try!(write_and_flush(s.out, b"\n--More--")); + let mut key = KeyPress::Null; + while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && + key != KeyPress::Char('n') && key != KeyPress::Char('N') && + key != KeyPress::Char('q') && + key != KeyPress::Char('Q') && + key != KeyPress::Char(' ') && + key != KeyPress::Backspace && key != KeyPress::Enter { + key = try!(rdr.next_key(true)); + } + match key { + KeyPress::Char('y') | + KeyPress::Char('Y') | + KeyPress::Char(' ') => { + pause_row += tty::get_rows(s.output_handle) - 1; + } + KeyPress::Enter => { + pause_row += 1; + } + _ => break, + } + } else { + try!(write_and_flush(s.out, b"\n")); + } + ab.clear(); + for col in 0..num_cols { + let i = (col * num_rows) + row; + if i < candidates.len() { + let candidate = &candidates[i]; + ab.push_str(candidate); + let width = UnicodeWidthStr::width(candidate.as_str()); + if ((col + 1) * num_rows) + row < candidates.len() { + for _ in width..max_width { + ab.push(' '); + } + } + } + } + try!(write_and_flush(s.out, ab.as_bytes())); + } // TODO Ok(None) } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 4a420c20ba..bf09a3d4fe 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -31,6 +31,18 @@ const TIOCGWINSZ: libc::c_int = 0x5413; /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. pub fn get_columns(_: Handle) -> usize { + let (cols, _) = get_win_size(); + cols +} + +/// Try to get the number of rows in the current terminal, +/// or assume 24 if it fails. +pub fn get_rows(_: Handle) -> usize { + let (_, rows) = get_win_size(); + rows +} + +fn get_win_size() -> (usize, usize) { use std::mem::zeroed; use libc::c_ushort; @@ -45,8 +57,8 @@ pub fn get_columns(_: Handle) -> usize { let mut size: winsize = zeroed(); match libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size) { - 0 => size.ws_col as usize, // TODO getCursorPosition - _ => 80, + 0 => (size.ws_col as usize, size.ws_row as usize), // TODO getCursorPosition + _ => (80, 24), } } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 3d01af532c..7bcbdd5613 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -40,12 +40,25 @@ macro_rules! check { }; } -/// Try to get the number of columns in the current terminal, or assume 80 if it fails. +/// Try to get the number of columns in the current terminal, +/// or assume 80 if it fails. pub fn get_columns(handle: Handle) -> usize { + let (cols, _) = get_win_size(); + cols +} + +/// Try to get the number of rows in the current terminal, +/// or assume 24 if it fails. +pub fn get_rows(_: Handle) -> usize { + let (_, rows) = get_win_size(); + rows +} + +fn get_win_size() -> (usize, usize) { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { - 0 => 80, - _ => info.dwSize.X as usize, + 0 => (80, 24), + _ => (info.dwSize.X as usize, 1 + inf.srWindow.Bottom - inf.srWindow.Top), } } From a1a06439a145113be55f78e8a80f19d777cae6f5 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 28 Aug 2016 17:41:49 +0200 Subject: [PATCH 0124/1201] Fix windows part --- src/tty/windows.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 7bcbdd5613..4776f0150b 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -43,22 +43,22 @@ macro_rules! check { /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. pub fn get_columns(handle: Handle) -> usize { - let (cols, _) = get_win_size(); + let (cols, _) = get_win_size(handle); cols } /// Try to get the number of rows in the current terminal, /// or assume 24 if it fails. -pub fn get_rows(_: Handle) -> usize { - let (_, rows) = get_win_size(); +pub fn get_rows(handle: Handle) -> usize { + let (_, rows) = get_win_size(handle); rows } -fn get_win_size() -> (usize, usize) { +fn get_win_size(handle: Handle) -> (usize, usize) { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { 0 => (80, 24), - _ => (info.dwSize.X as usize, 1 + inf.srWindow.Bottom - inf.srWindow.Top), + _ => (info.dwSize.X as usize, 1 + info.srWindow.Bottom - info.srWindow.Top), } } From fb28799259683d1c153bbdb82b66f7fff7d6836e Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 28 Aug 2016 17:52:18 +0200 Subject: [PATCH 0125/1201] Fix windows part --- src/tty/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 4776f0150b..f1b0958753 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -58,7 +58,7 @@ fn get_win_size(handle: Handle) -> (usize, usize) { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { 0 => (80, 24), - _ => (info.dwSize.X as usize, 1 + info.srWindow.Bottom - info.srWindow.Top), + _ => (info.dwSize.X as usize, (1 + info.srWindow.Bottom - info.srWindow.Top) as usize), } } From 9b8ff06bddbf93f8c18611f299810e21708404b6 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 29 Aug 2016 20:37:37 +0200 Subject: [PATCH 0126/1201] Update links to docs.rs --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9b09d189bd..ec6e507f87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "rustyline" version = "1.0.0" authors = ["Katsu Kawakami "] description = "Rustyline, a readline implementation based on Antirez's Linenoise" -documentation = "http://kkawakam.github.io/rustyline/rustyline" +documentation = "http://docs.rs/rustyline" repository = "https://github.com/kkawakam/rustyline" readme = "README.md" keywords = ["readline"] diff --git a/README.md b/README.md index 49fed1fe6a..a151f9fdeb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Readline implementation in Rust that is based on [Antirez' Linenoise](https://github.com/antirez/linenoise) -[Documentation](https://kkawakam.github.io/rustyline) +[Documentation](https://docs.rs/rustyline) **Supported Platforms** * Linux From d4c494fc86a14acb2907b7e19b7be56b6b7584aa Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 4 Sep 2016 06:57:54 +0200 Subject: [PATCH 0127/1201] Fix file completion --- src/completion.rs | 112 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 9 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 2917bd21e1..2cc59cb1a6 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -1,4 +1,5 @@ //! Completion API +use std::borrow::Cow::{self, Borrowed, Owned}; use std::collections::BTreeSet; use std::fs; use std::path::{self, Path}; @@ -66,10 +67,14 @@ pub struct FilenameCompleter { #[cfg(unix)] static DEFAULT_BREAK_CHARS: [char; 18] = [' ', '\t', '\n', '"', '\\', '\'', '`', '@', '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0']; +#[cfg(unix)] +static ESCAPE_CHAR: Option = Some('\\'); // Remove \ to make file completion works on windows #[cfg(windows)] static DEFAULT_BREAK_CHARS: [char; 17] = [' ', '\t', '\n', '"', '\'', '`', '@', '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0']; +#[cfg(windows)] +static ESCAPE_CHAR: Option = None; impl FilenameCompleter { pub fn new() -> FilenameCompleter { @@ -85,13 +90,61 @@ impl Default for FilenameCompleter { impl Completer for FilenameCompleter { fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec)> { - let (start, path) = extract_word(line, pos, &self.break_chars); - let matches = try!(filename_complete(path)); + let (start, path) = extract_word(line, pos, ESCAPE_CHAR, &self.break_chars); + let path = unescape(path, ESCAPE_CHAR); + let matches = try!(filename_complete(&path, ESCAPE_CHAR, &self.break_chars)); Ok((start, matches)) } } -fn filename_complete(path: &str) -> Result> { +/// Remove escape char +pub fn unescape(input: &str, esc_char: Option) -> Cow { + if esc_char.is_none() { + return Borrowed(input); + } + let esc_char = esc_char.unwrap(); + let n = input.chars().filter(|&c| c == esc_char).count(); + if n == 0 { + return Borrowed(input); + } + let mut result = String::with_capacity(input.len() - n); + let mut chars = input.chars(); + while let Some(ch) = chars.next() { + if ch == esc_char { + if let Some(ch) = chars.next() { + result.push(ch); + } + } else { + result.push(ch); + } + } + Owned(result) +} + +pub fn escape(input: String, esc_char: Option, break_chars: &BTreeSet) -> String { + if esc_char.is_none() { + return input; + } + let esc_char = esc_char.unwrap(); + let n = input.chars().filter(|c| break_chars.contains(c)).count(); + if n == 0 { + return input; + } + let mut result = String::with_capacity(input.len() + n); + + for c in input.chars() { + if break_chars.contains(&c) { + result.push(esc_char); + } + result.push(c); + } + result +} + +fn filename_complete(path: &str, + esc_char: Option, + break_chars: &BTreeSet) + -> Result> { use std::env::{current_dir, home_dir}; let sep = path::MAIN_SEPARATOR; @@ -131,7 +184,7 @@ fn filename_complete(path: &str) -> Result> { if try!(fs::metadata(entry.path())).is_dir() { path.push(sep); } - entries.push(path); + entries.push(escape(path, esc_char, break_chars)); } } } @@ -144,17 +197,34 @@ fn filename_complete(path: &str) -> Result> { /// Return the word and its start position (idx, `line[idx..pos]`) otherwise. pub fn extract_word<'l>(line: &'l str, pos: usize, + esc_char: Option, break_chars: &BTreeSet) -> (usize, &'l str) { let line = &line[..pos]; if line.is_empty() { return (0, line); } - match line.char_indices().rev().find(|&(_, c)| break_chars.contains(&c)) { - Some((i, c)) => { - let start = i + c.len_utf8(); - (start, &line[start..]) + let mut start = None; + for (i, c) in line.char_indices().rev() { + if esc_char.is_some() && start.is_some() { + if esc_char.unwrap() == c { + // escaped break char + start = None; + continue; + } else { + break; + } } + if break_chars.contains(&c) { + start = Some(i + c.len_utf8()); + if esc_char.is_none() { + break; + } // else maybe escaped... + } + } + + match start { + Some(start) => (start, &line[start..]), None => (0, line), } } @@ -195,7 +265,31 @@ mod tests { let break_chars: BTreeSet = super::DEFAULT_BREAK_CHARS.iter().cloned().collect(); let line = "ls '/usr/local/b"; assert_eq!((4, "/usr/local/b"), - super::extract_word(line, line.len(), &break_chars)); + super::extract_word(line, line.len(), Some('\\'), &break_chars)); + let line = "ls /User\\ Information"; + assert_eq!((3, "/User\\ Information"), + super::extract_word(line, line.len(), Some('\\'), &break_chars)); + } + + #[test] + pub fn unescape() { + use std::borrow::Cow::{self, Borrowed, Owned}; + let input = "/usr/local/b"; + assert_eq!(Borrowed(input), super::unescape(input, Some('\\'))); + let input = "/User\\ Information"; + let result: Cow = Owned(String::from("/User Information")); + assert_eq!(result, super::unescape(input, Some('\\'))); + } + + #[test] + pub fn escape() { + let break_chars: BTreeSet = super::DEFAULT_BREAK_CHARS.iter().cloned().collect(); + let input = String::from("/usr/local/b"); + assert_eq!(input.clone(), + super::escape(input, Some('\\'), &break_chars)); + let input = String::from("/User Information"); + let result = String::from("/User\\ Information"); + assert_eq!(result, super::escape(input, Some('\\'), &break_chars)); } #[test] From 713e5a90a74c12faedd4a7066d15c7bd518a849f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 4 Sep 2016 06:57:54 +0200 Subject: [PATCH 0128/1201] Fix file completion --- src/completion.rs | 116 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 11 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 4ef0216a4a..dec6c51500 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -1,4 +1,5 @@ //! Completion API +use std::borrow::Cow::{self, Borrowed, Owned}; use std::collections::BTreeSet; use std::fs; use std::path::{self, Path}; @@ -66,10 +67,14 @@ pub struct FilenameCompleter { #[cfg(unix)] static DEFAULT_BREAK_CHARS: [char; 18] = [' ', '\t', '\n', '"', '\\', '\'', '`', '@', '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0']; +#[cfg(unix)] +static ESCAPE_CHAR: Option = Some('\\'); // Remove \ to make file completion works on windows #[cfg(windows)] -static DEFAULT_BREAK_CHARS: [char; 17] = [' ', '\t', '\n', '"', '\'', '`', '@', '$', '>', - '<', '=', ';', '|', '&', '{', '(', '\0']; +static DEFAULT_BREAK_CHARS: [char; 17] = [' ', '\t', '\n', '"', '\'', '`', '@', '$', '>', '<', + '=', ';', '|', '&', '{', '(', '\0']; +#[cfg(windows)] +static ESCAPE_CHAR: Option = None; impl FilenameCompleter { pub fn new() -> FilenameCompleter { @@ -85,13 +90,61 @@ impl Default for FilenameCompleter { impl Completer for FilenameCompleter { fn complete(&self, line: &str, pos: usize) -> Result<(usize, Vec)> { - let (start, path) = extract_word(line, pos, &self.break_chars); - let matches = try!(filename_complete(path)); + let (start, path) = extract_word(line, pos, ESCAPE_CHAR, &self.break_chars); + let path = unescape(path, ESCAPE_CHAR); + let matches = try!(filename_complete(&path, ESCAPE_CHAR, &self.break_chars)); Ok((start, matches)) } } -fn filename_complete(path: &str) -> Result> { +/// Remove escape char +pub fn unescape(input: &str, esc_char: Option) -> Cow { + if esc_char.is_none() { + return Borrowed(input); + } + let esc_char = esc_char.unwrap(); + let n = input.chars().filter(|&c| c == esc_char).count(); + if n == 0 { + return Borrowed(input); + } + let mut result = String::with_capacity(input.len() - n); + let mut chars = input.chars(); + while let Some(ch) = chars.next() { + if ch == esc_char { + if let Some(ch) = chars.next() { + result.push(ch); + } + } else { + result.push(ch); + } + } + Owned(result) +} + +pub fn escape(input: String, esc_char: Option, break_chars: &BTreeSet) -> String { + if esc_char.is_none() { + return input; + } + let esc_char = esc_char.unwrap(); + let n = input.chars().filter(|c| break_chars.contains(c)).count(); + if n == 0 { + return input; + } + let mut result = String::with_capacity(input.len() + n); + + for c in input.chars() { + if break_chars.contains(&c) { + result.push(esc_char); + } + result.push(c); + } + result +} + +fn filename_complete(path: &str, + esc_char: Option, + break_chars: &BTreeSet) + -> Result> { use std::env::{current_dir, home_dir}; let sep = path::MAIN_SEPARATOR; @@ -131,7 +184,7 @@ fn filename_complete(path: &str) -> Result> { if try!(fs::metadata(entry.path())).is_dir() { path.push(sep); } - entries.push(path); + entries.push(escape(path, esc_char, break_chars)); } } } @@ -144,17 +197,34 @@ fn filename_complete(path: &str) -> Result> { /// Return the word and its start position (idx, `line[idx..pos]`) otherwise. pub fn extract_word<'l>(line: &'l str, pos: usize, + esc_char: Option, break_chars: &BTreeSet) -> (usize, &'l str) { let line = &line[..pos]; if line.is_empty() { return (0, line); } - match line.char_indices().rev().find(|&(_, c)| break_chars.contains(&c)) { - Some((i, c)) => { - let start = i + c.len_utf8(); - (start, &line[start..]) + let mut start = None; + for (i, c) in line.char_indices().rev() { + if esc_char.is_some() && start.is_some() { + if esc_char.unwrap() == c { + // escaped break char + start = None; + continue; + } else { + break; + } } + if break_chars.contains(&c) { + start = Some(i + c.len_utf8()); + if esc_char.is_none() { + break; + } // else maybe escaped... + } + } + + match start { + Some(start) => (start, &line[start..]), None => (0, line), } } @@ -168,6 +238,30 @@ mod tests { let break_chars: BTreeSet = super::DEFAULT_BREAK_CHARS.iter().cloned().collect(); let line = "ls '/usr/local/b"; assert_eq!((4, "/usr/local/b"), - super::extract_word(line, line.len(), &break_chars)); + super::extract_word(line, line.len(), Some('\\'), &break_chars)); + let line = "ls /User\\ Information"; + assert_eq!((3, "/User\\ Information"), + super::extract_word(line, line.len(), Some('\\'), &break_chars)); + } + + #[test] + pub fn unescape() { + use std::borrow::Cow::{self, Borrowed, Owned}; + let input = "/usr/local/b"; + assert_eq!(Borrowed(input), super::unescape(input, Some('\\'))); + let input = "/User\\ Information"; + let result: Cow = Owned(String::from("/User Information")); + assert_eq!(result, super::unescape(input, Some('\\'))); + } + + #[test] + pub fn escape() { + let break_chars: BTreeSet = super::DEFAULT_BREAK_CHARS.iter().cloned().collect(); + let input = String::from("/usr/local/b"); + assert_eq!(input.clone(), + super::escape(input, Some('\\'), &break_chars)); + let input = String::from("/User Information"); + let result = String::from("/User\\ Information"); + assert_eq!(result, super::escape(input, Some('\\'), &break_chars)); } } From d97fe53ca17d980017c9ac86e80a1fa92e9db219 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 4 Sep 2016 11:40:28 +0200 Subject: [PATCH 0129/1201] Page completions --- examples/example.rs | 3 ++- src/lib.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 18bf58b4b7..8d49697fa6 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -2,7 +2,7 @@ extern crate rustyline; use rustyline::completion::FilenameCompleter; use rustyline::error::ReadlineError; -use rustyline::{Config, Editor}; +use rustyline::{Config, CompletionType, Editor}; // On unix platforms you can use ANSI escape sequences #[cfg(unix)] @@ -16,6 +16,7 @@ static PROMPT: &'static str = ">> "; fn main() { let config = Config::builder() .history_ignore_space(true) + .completion_type(CompletionType::List) .build(); let c = FilenameCompleter::new(); let mut rl = Editor::new(config); diff --git a/src/lib.rs b/src/lib.rs index 12d395420c..0b486fed29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -672,6 +672,7 @@ fn page_completions(rdr: &mut tty::RawReader, } _ => break, } + try!(write_and_flush(s.out, b"\n")); } else { try!(write_and_flush(s.out, b"\n")); } @@ -691,7 +692,8 @@ fn page_completions(rdr: &mut tty::RawReader, } try!(write_and_flush(s.out, ab.as_bytes())); } - // TODO + try!(write_and_flush(s.out, b"\n")); + try!(s.refresh_line()); Ok(None) } From 642c0f762539b1a98755e0a8913f844a2082f1cc Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 23 Aug 2016 18:53:11 +0200 Subject: [PATCH 0130/1201] Prepare support to completion list --- src/completion.rs | 2 +- src/history.rs | 2 +- src/lib.rs | 135 ++++++++++++++++++++++++++-------------------- 3 files changed, 79 insertions(+), 60 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 4ef0216a4a..d30eee7250 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -55,8 +55,8 @@ macro_rules! box_completer { } } -use std::sync::Arc; use std::rc::Rc; +use std::sync::Arc; box_completer! { Box Rc Arc } pub struct FilenameCompleter { diff --git a/src/history.rs b/src/history.rs index f58ff9a9b3..5e7d89c4a3 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,8 +1,8 @@ //! History API use std::collections::VecDeque; -use std::path::Path; use std::fs::File; +use std::path::Path; use super::Result; diff --git a/src/lib.rs b/src/lib.rs index 27391ec27b..989fdc40f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -526,16 +526,29 @@ fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { s.refresh_line() } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CompletionMode { + /// Complete the next full match (like in Vim by default) + Circular, + /// Complete till longest match. + /// When more than one match, list all matches + /// (like in Bash/Readline). + List, +} + /// Completes the line/word fn complete_line(rdr: &mut tty::RawReader, s: &mut State, - completer: &Completer) + completer: &Completer, + completion_mode: CompletionMode) -> Result> { + // get a list of completions let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); + // if no completions, we are done if candidates.is_empty() { try!(beep()); Ok(None) - } else { + } else if CompletionMode::Circular == completion_mode { // Save the current edited line before to overwrite it s.backup(); let mut key; @@ -574,6 +587,9 @@ fn complete_line(rdr: &mut tty::RawReader, } } Ok(Some(key)) + } else { + // TODO ... + Ok(None) } } @@ -658,17 +674,17 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, /// It will also handle special inputs in an appropriate fashion /// (e.g., C-c will exit readline) #[allow(let_unit_value)] -fn readline_edit(prompt: &str, - history: &mut History, - completer: Option<&Completer>, - kill_ring: &mut KillRing, - original_mode: tty::Mode) - -> Result { +fn readline_edit(prompt: &str, + editor: &mut Editor, + original_mode: tty::Mode) + -> Result { + let completer = editor.completer.as_ref().map(|c| c as &Completer); + let mut stdout = io::stdout(); let stdout_handle = try!(tty::stdout_handle()); - kill_ring.reset(); - let mut s = State::new(&mut stdout, stdout_handle, prompt, history.len()); + editor.kill_ring.reset(); + let mut s = State::new(&mut stdout, stdout_handle, prompt, editor.history.len()); try!(s.refresh_line()); let mut rdr = try!(tty::RawReader::new(io::stdin())); @@ -682,16 +698,17 @@ fn readline_edit(prompt: &str, } let mut key = try!(rk); if let KeyPress::Char(c) = key { - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_insert(&mut s, c)); continue; } // autocomplete if key == KeyPress::Tab && completer.is_some() { - let next = try!(complete_line(&mut rdr, &mut s, completer.unwrap())); + let next = + try!(complete_line(&mut rdr, &mut s, completer.unwrap(), editor.completion_mode)); if next.is_some() { - kill_ring.reset(); + editor.kill_ring.reset(); key = next.unwrap(); if let KeyPress::Char(c) = key { try!(edit_insert(&mut s, c)); @@ -702,7 +719,7 @@ fn readline_edit(prompt: &str, } } else if key == KeyPress::Ctrl('R') { // Search history backward - let next = try!(reverse_incremental_search(&mut rdr, &mut s, history)); + let next = try!(reverse_incremental_search(&mut rdr, &mut s, &editor.history)); if next.is_some() { key = next.unwrap(); } else { @@ -715,22 +732,22 @@ fn readline_edit(prompt: &str, match key { KeyPress::Ctrl('A') | KeyPress::Home => { - kill_ring.reset(); + editor.kill_ring.reset(); // Move to the beginning of line. try!(edit_move_home(&mut s)) } KeyPress::Ctrl('B') | KeyPress::Left => { - kill_ring.reset(); + editor.kill_ring.reset(); // Move back a character. try!(edit_move_left(&mut s)) } KeyPress::Ctrl('C') => { - kill_ring.reset(); + editor.kill_ring.reset(); return Err(error::ReadlineError::Interrupted); } KeyPress::Ctrl('D') => { - kill_ring.reset(); + editor.kill_ring.reset(); if s.line.is_empty() { return Err(error::ReadlineError::Eof); } else { @@ -740,26 +757,26 @@ fn readline_edit(prompt: &str, } KeyPress::Ctrl('E') | KeyPress::End => { - kill_ring.reset(); + editor.kill_ring.reset(); // Move to the end of line. try!(edit_move_end(&mut s)) } KeyPress::Ctrl('F') | KeyPress::Right => { - kill_ring.reset(); + editor.kill_ring.reset(); // Move forward a character. try!(edit_move_right(&mut s)) } KeyPress::Ctrl('H') | KeyPress::Backspace => { - kill_ring.reset(); + editor.kill_ring.reset(); // Delete one character backward. try!(edit_backspace(&mut s)) } KeyPress::Ctrl('K') => { // Kill the text from point to the end of the line. if let Some(text) = try!(edit_kill_line(&mut s)) { - kill_ring.kill(&text, true) + editor.kill_ring.kill(&text, true) } } KeyPress::Ctrl('L') => { @@ -769,43 +786,43 @@ fn readline_edit(prompt: &str, } KeyPress::Ctrl('N') | KeyPress::Down => { - kill_ring.reset(); + editor.kill_ring.reset(); // Fetch the next command from the history list. - try!(edit_history_next(&mut s, history, false)) + try!(edit_history_next(&mut s, &editor.history, false)) } KeyPress::Ctrl('P') | KeyPress::Up => { - kill_ring.reset(); + editor.kill_ring.reset(); // Fetch the previous command from the history list. - try!(edit_history_next(&mut s, history, true)) + try!(edit_history_next(&mut s, &editor.history, true)) } KeyPress::Ctrl('T') => { - kill_ring.reset(); + editor.kill_ring.reset(); // Exchange the char before cursor with the character at cursor. try!(edit_transpose_chars(&mut s)) } KeyPress::Ctrl('U') => { // Kill backward from point to the beginning of the line. if let Some(text) = try!(edit_discard_line(&mut s)) { - kill_ring.kill(&text, false) + editor.kill_ring.kill(&text, false) } } #[cfg(unix)] KeyPress::Ctrl('V') => { // Quoted insert - kill_ring.reset(); + editor.kill_ring.reset(); let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c)) // FIXME } KeyPress::Ctrl('W') => { // Kill the word behind point, using white space as a word boundary if let Some(text) = try!(edit_delete_prev_word(&mut s, char::is_whitespace)) { - kill_ring.kill(&text, false) + editor.kill_ring.kill(&text, false) } } KeyPress::Ctrl('Y') => { // retrieve (yank) last item killed - if let Some(text) = kill_ring.yank() { + if let Some(text) = editor.kill_ring.yank() { try!(edit_yank(&mut s, text)) } } @@ -820,7 +837,7 @@ fn readline_edit(prompt: &str, KeyPress::Enter | KeyPress::Ctrl('J') => { // Accept the line regardless of where the cursor is. - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_move_end(&mut s)); break; } @@ -830,67 +847,67 @@ fn readline_edit(prompt: &str, // Kill from the cursor to the start of the current word, or, if between words, to the start of the previous word. if let Some(text) = try!(edit_delete_prev_word(&mut s, |ch| !ch.is_alphanumeric())) { - kill_ring.kill(&text, false) + editor.kill_ring.kill(&text, false) } } KeyPress::Meta('<') => { // move to first entry in history - kill_ring.reset(); - try!(edit_history(&mut s, history, true)) + editor.kill_ring.reset(); + try!(edit_history(&mut s, &editor.history, true)) } KeyPress::Meta('>') => { // move to last entry in history - kill_ring.reset(); - try!(edit_history(&mut s, history, false)) + editor.kill_ring.reset(); + try!(edit_history(&mut s, &editor.history, false)) } KeyPress::Meta('B') => { // move backwards one word - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_move_to_prev_word(&mut s)) } KeyPress::Meta('C') => { // capitalize word after point - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } KeyPress::Meta('D') => { // kill one word forward if let Some(text) = try!(edit_delete_word(&mut s)) { - kill_ring.kill(&text, true) + editor.kill_ring.kill(&text, true) } } KeyPress::Meta('F') => { // move forwards one word - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_move_to_next_word(&mut s)) } KeyPress::Meta('L') => { // lowercase word after point - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::LOWERCASE)) } KeyPress::Meta('T') => { // transpose words - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_transpose_words(&mut s)) } KeyPress::Meta('U') => { // uppercase word after point - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::UPPERCASE)) } KeyPress::Meta('Y') => { // yank-pop - if let Some((yank_size, text)) = kill_ring.yank_pop() { + if let Some((yank_size, text)) = editor.kill_ring.yank_pop() { try!(edit_yank_pop(&mut s, yank_size, text)) } } KeyPress::Delete => { - kill_ring.reset(); + editor.kill_ring.reset(); try!(edit_delete(&mut s)) } _ => { - kill_ring.reset(); + editor.kill_ring.reset(); // Ignore the character typed. } } @@ -910,14 +927,10 @@ impl Drop for Guard { /// Readline method that will enable RAW mode, call the `readline_edit()` /// method and disable raw mode -fn readline_raw(prompt: &str, - history: &mut History, - completer: Option<&Completer>, - kill_ring: &mut KillRing) - -> Result { +fn readline_raw(prompt: &str, editor: &mut Editor) -> Result { let original_mode = try!(tty::enable_raw_mode()); let guard = Guard(original_mode); - let user_input = readline_edit(prompt, history, completer, kill_ring, original_mode); + let user_input = readline_edit(prompt, editor, original_mode); drop(guard); // try!(disable_raw_mode(original_mode)); println!(""); user_input @@ -940,6 +953,7 @@ pub struct Editor { history: History, completer: Option, kill_ring: KillRing, + completion_mode: CompletionMode, } impl Editor { @@ -951,6 +965,7 @@ impl Editor { history: History::new(), completer: None, kill_ring: KillRing::new(60), + completion_mode: CompletionMode::Circular, }; if !editor.unsupported_term && editor.stdin_isatty && editor.stdout_isatty { tty::install_sigwinch_handler(); @@ -970,10 +985,7 @@ impl Editor { // Not a tty: read from file / pipe. readline_direct() } else { - readline_raw(prompt, - &mut self.history, - self.completer.as_ref().map(|c| c as &Completer), - &mut self.kill_ring) + readline_raw(prompt, self) } } @@ -1020,6 +1032,11 @@ impl Editor { pub fn set_completer(&mut self, completer: Option) { self.completer = completer; } + + /// Set completion mode. + pub fn set_completion_mode(&mut self, completion_mode: CompletionMode) { + self.completion_mode = completion_mode; + } } impl Default for Editor { @@ -1043,6 +1060,7 @@ mod test { use line_buffer::LineBuffer; use history::History; use completion::Completer; + use CompletionMode; use State; use super::Result; use tty::Handle; @@ -1125,7 +1143,8 @@ mod test { let input = b"\n"; let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; - let key = super::complete_line(&mut rdr, &mut s, &completer).unwrap(); + let key = super::complete_line(&mut rdr, &mut s, &completer, CompletionMode::Circular) + .unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); From 5a3dfb064b33ad6c21a91b3a37c390edc4b88f21 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 24 Aug 2016 20:34:39 +0200 Subject: [PATCH 0131/1201] Introduce longest_common_prefix function --- src/completion.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 18 +++++++++++++++-- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index d30eee7250..2283f61bd0 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -159,6 +159,33 @@ pub fn extract_word<'l>(line: &'l str, } } +pub fn longest_common_prefix(candidates: &[String]) -> Option { + if candidates.is_empty() { + return None; + } else if candidates.len() == 1 { + return Some(candidates[0].clone()); + } + let mut longest_common_prefix = 0; + 'o: loop { + for i in 0..candidates.len() - 1 { + let b1 = candidates[i].as_bytes(); + let b2 = candidates[i + 1].as_bytes(); + if b1.len() <= longest_common_prefix || b2.len() <= longest_common_prefix || + b1[i] != b2[i] { + break 'o; + } + } + longest_common_prefix += 1; + } + while !candidates[0].is_char_boundary(longest_common_prefix) { + longest_common_prefix -= 1; + } + if longest_common_prefix == 0 { + return None; + } + Some(String::from(&candidates[0][0..longest_common_prefix])) +} + #[cfg(test)] mod tests { use std::collections::BTreeSet; @@ -170,4 +197,26 @@ mod tests { assert_eq!((4, "/usr/local/b"), super::extract_word(line, line.len(), &break_chars)); } + + #[test] + pub fn longest_common_prefix() { + let mut candidates = vec![]; + let lcp = super::longest_common_prefix(&candidates); + assert!(lcp.is_none()); + + let c1 = String::from("User"); + candidates.push(c1.clone()); + let lcp = super::longest_common_prefix(&candidates); + assert_eq!(Some(c1.clone()), lcp); + + let c2 = String::from("Users"); + candidates.push(c2.clone()); + let lcp = super::longest_common_prefix(&candidates); + assert_eq!(Some(c1), lcp); + + let c3 = String::from(""); + candidates.push(c3.clone()); + let lcp = super::longest_common_prefix(&candidates); + assert!(lcp.is_none()); + } } diff --git a/src/lib.rs b/src/lib.rs index 989fdc40f6..4c23eb0c95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ use std::sync::atomic; use nix::sys::signal; use encode_unicode::CharExt; -use completion::Completer; +use completion::{Completer, longest_common_prefix}; use consts::KeyPress; use history::History; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; @@ -587,9 +587,23 @@ fn complete_line(rdr: &mut tty::RawReader, } } Ok(Some(key)) - } else { + } else if CompletionMode::List == completion_mode { + // beep if ambiguous + if candidates.len() > 1 { + try!(beep()); + } + if let Some(lcp) = longest_common_prefix(&candidates) { + // if we can extend the item, extend it and return to main loop + if lcp.len() > s.line.pos() - start { + completer.update(&mut s.line, start, &lcp); + try!(s.refresh_line()); + return Ok(None); + } + } // TODO ... Ok(None) + } else { + Ok(None) } } From b892da8a7ad8ac977ad68a1d0fd06e111b4c1329 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 24 Aug 2016 20:57:53 +0200 Subject: [PATCH 0132/1201] Fix longest_common_prefix --- src/completion.rs | 33 +++++++++++++++++++++------------ src/lib.rs | 2 +- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 2283f61bd0..ad0a93f0b4 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -159,11 +159,11 @@ pub fn extract_word<'l>(line: &'l str, } } -pub fn longest_common_prefix(candidates: &[String]) -> Option { +pub fn longest_common_prefix(candidates: &[String]) -> Option<&str> { if candidates.is_empty() { return None; } else if candidates.len() == 1 { - return Some(candidates[0].clone()); + return Some(&candidates[0]); } let mut longest_common_prefix = 0; 'o: loop { @@ -183,7 +183,7 @@ pub fn longest_common_prefix(candidates: &[String]) -> Option { if longest_common_prefix == 0 { return None; } - Some(String::from(&candidates[0][0..longest_common_prefix])) + Some(&candidates[0][0..longest_common_prefix]) } #[cfg(test)] @@ -201,22 +201,31 @@ mod tests { #[test] pub fn longest_common_prefix() { let mut candidates = vec![]; - let lcp = super::longest_common_prefix(&candidates); - assert!(lcp.is_none()); + { + let lcp = super::longest_common_prefix(&candidates); + assert!(lcp.is_none()); + } - let c1 = String::from("User"); + let s = "User"; + let c1 = String::from(s); candidates.push(c1.clone()); - let lcp = super::longest_common_prefix(&candidates); - assert_eq!(Some(c1.clone()), lcp); + { + let lcp = super::longest_common_prefix(&candidates); + assert_eq!(Some(s), lcp); + } let c2 = String::from("Users"); candidates.push(c2.clone()); - let lcp = super::longest_common_prefix(&candidates); - assert_eq!(Some(c1), lcp); + { + let lcp = super::longest_common_prefix(&candidates); + assert_eq!(Some(s), lcp); + } let c3 = String::from(""); candidates.push(c3.clone()); - let lcp = super::longest_common_prefix(&candidates); - assert!(lcp.is_none()); + { + let lcp = super::longest_common_prefix(&candidates); + assert!(lcp.is_none()); + } } } diff --git a/src/lib.rs b/src/lib.rs index 4c23eb0c95..ebcabdd4ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -595,7 +595,7 @@ fn complete_line(rdr: &mut tty::RawReader, if let Some(lcp) = longest_common_prefix(&candidates) { // if we can extend the item, extend it and return to main loop if lcp.len() > s.line.pos() - start { - completer.update(&mut s.line, start, &lcp); + completer.update(&mut s.line, start, lcp); try!(s.refresh_line()); return Ok(None); } From 03fcda2fe14a357107746ba5963a32b861c0533a Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 25 Aug 2016 18:40:10 +0200 Subject: [PATCH 0133/1201] Fix longest_common_prefix --- src/completion.rs | 6 +++++- src/lib.rs | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/completion.rs b/src/completion.rs index ad0a93f0b4..16448c5242 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -171,7 +171,7 @@ pub fn longest_common_prefix(candidates: &[String]) -> Option<&str> { let b1 = candidates[i].as_bytes(); let b2 = candidates[i + 1].as_bytes(); if b1.len() <= longest_common_prefix || b2.len() <= longest_common_prefix || - b1[i] != b2[i] { + b1[longest_common_prefix] != b2[longest_common_prefix] { break 'o; } } @@ -227,5 +227,9 @@ mod tests { let lcp = super::longest_common_prefix(&candidates); assert!(lcp.is_none()); } + + let candidates = vec![String::from("fée"), String::from("fête")]; + let lcp = super::longest_common_prefix(&candidates); + assert_eq!(Some("f"), lcp); } } diff --git a/src/lib.rs b/src/lib.rs index ebcabdd4ec..b115ed9bfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -600,6 +600,13 @@ fn complete_line(rdr: &mut tty::RawReader, return Ok(None); } } + // we can't complete any further, wait for second tab + let key = try!(rdr.next_key(false)); + // if any character other than tab, pass it to the main loop + if key != KeyPress::Tab { + return Ok(Some(key)) + } + // we got a second tab, maybe show list of possible completions // TODO ... Ok(None) } else { From 51ba87a81b46304f7fa622b99e203cb0c54acb3a Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 25 Aug 2016 18:40:28 +0200 Subject: [PATCH 0134/1201] Misc --- src/completion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/completion.rs b/src/completion.rs index 16448c5242..b24c2dc1e1 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -123,7 +123,7 @@ fn filename_complete(path: &str) -> Result> { }; let mut entries: Vec = Vec::new(); - for entry in try!(fs::read_dir(dir)) { + for entry in try!(dir.read_dir()) { let entry = try!(entry); if let Some(s) = entry.file_name().to_str() { if s.starts_with(file_name) { From ac74a32209828d13b2cba56c06613e2b0a32e736 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 25 Aug 2016 21:33:11 +0200 Subject: [PATCH 0135/1201] Introduce Config struct. BREAKING CHANGE --- examples/example.rs | 7 ++- src/config.rs | 124 ++++++++++++++++++++++++++++++++++++++++++++ src/history.rs | 43 +++++---------- src/lib.rs | 84 +++++++++--------------------- 4 files changed, 168 insertions(+), 90 deletions(-) create mode 100644 src/config.rs diff --git a/examples/example.rs b/examples/example.rs index 62fb4fe609..18bf58b4b7 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -2,7 +2,7 @@ extern crate rustyline; use rustyline::completion::FilenameCompleter; use rustyline::error::ReadlineError; -use rustyline::Editor; +use rustyline::{Config, Editor}; // On unix platforms you can use ANSI escape sequences #[cfg(unix)] @@ -14,8 +14,11 @@ static PROMPT: &'static str = "\x1b[1;32m>>\x1b[0m "; static PROMPT: &'static str = ">> "; fn main() { + let config = Config::builder() + .history_ignore_space(true) + .build(); let c = FilenameCompleter::new(); - let mut rl = Editor::new().history_ignore_space(true); + let mut rl = Editor::new(config); rl.set_completer(Some(c)); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000000..49add02a4e --- /dev/null +++ b/src/config.rs @@ -0,0 +1,124 @@ +//! Customize line editor +use std::default::Default; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Config { + /// When listing completion alternatives, only display + /// one screen of possibilities at a time. + max_history_size: usize, + history_duplicates: HistoryDuplicates, + history_ignore_space: bool, + completion_type: CompletionType, + /// When listing completion alternatives, only display + /// one screen of possibilities at a time. + completion_prompt_limit: usize, +} + +impl Config { + pub fn builder() -> Builder { + Builder::new() + } + + /// Tell the maximum length for the history. + pub fn max_history_size(&self) -> usize { + self.max_history_size + } + + /// Tell if lines which match the previous history entry are saved or not in the history list. + /// By default, they are ignored. + pub fn history_duplicates(&self) -> HistoryDuplicates { + self.history_duplicates + } + + /// Tell if lines which begin with a space character are saved or not in the history list. + /// By default, they are saved. + pub fn history_ignore_space(&self) -> bool { + self.history_ignore_space + } + + pub fn completion_type(&self) -> CompletionType { + self.completion_type + } + + pub fn completion_prompt_limit(&self) -> usize { + self.completion_prompt_limit + } +} + +impl Default for Config { + fn default() -> Config { + Config { + max_history_size: 100, + history_duplicates: HistoryDuplicates::IgnoreConsecutive, + history_ignore_space: false, + completion_type: CompletionType::Circular, // TODO Validate + completion_prompt_limit: 100, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum HistoryDuplicates { + AlwaysAdd, + IgnoreConsecutive, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CompletionType { + /// Complete the next full match (like in Vim by default) + Circular, + /// Complete till longest match. + /// When more than one match, list all matches + /// (like in Bash/Readline). + List, +} + +#[derive(Debug)] +pub struct Builder { + p: Config, +} + +impl Builder { + pub fn new() -> Builder { + Builder { p: Config::default() } + } + + /// Set the maximum length for the history. + pub fn max_history_size(mut self, max_size: usize) -> Builder { + self.p.max_history_size = max_size; + self + } + + /// Tell if lines which match the previous history entry are saved or not in the history list. + /// By default, they are ignored. + pub fn history_ignore_dups(mut self, yes: bool) -> Builder { + self.p.history_duplicates = if yes { + HistoryDuplicates::IgnoreConsecutive + } else { + HistoryDuplicates::AlwaysAdd + }; + self + } + + /// Tell if lines which begin with a space character are saved or not in the history list. + /// By default, they are saved. + pub fn history_ignore_space(mut self, yes: bool) -> Builder { + self.p.history_ignore_space = yes; + self + } + + /// Set `completion_type`. + pub fn completion_type(mut self, completion_type: CompletionType) -> Builder { + self.p.completion_type = completion_type; + self + } + + pub fn completion_prompt_limit(mut self, completion_prompt_limit: usize) -> Builder { + self.p.completion_prompt_limit = completion_prompt_limit; + self + } + + pub fn build(self) -> Config { + self.p + } +} diff --git a/src/history.rs b/src/history.rs index 5e7d89c4a3..59f3126262 100644 --- a/src/history.rs +++ b/src/history.rs @@ -5,6 +5,7 @@ use std::fs::File; use std::path::Path; use super::Result; +use config::{Config, HistoryDuplicates}; pub struct History { entries: VecDeque, @@ -13,30 +14,16 @@ pub struct History { ignore_dups: bool, } -const DEFAULT_HISTORY_MAX_LEN: usize = 100; - impl History { - pub fn new() -> History { + pub fn new(config: Config) -> History { History { entries: VecDeque::new(), - max_len: DEFAULT_HISTORY_MAX_LEN, - ignore_space: false, - ignore_dups: true, + max_len: config.max_history_size(), + ignore_space: config.history_duplicates() == HistoryDuplicates::IgnoreConsecutive, + ignore_dups: config.history_ignore_space(), } } - /// Tell if lines which begin with a space character are saved or not in the history list. - /// By default, they are saved. - pub fn ignore_space(&mut self, yes: bool) { - self.ignore_space = yes; - } - - /// Tell if lines which match the previous history entry are saved or not in the history list. - /// By default, they are ignored. - pub fn ignore_dups(&mut self, yes: bool) { - self.ignore_dups = yes; - } - /// Return the history entry at position `index`, starting from 0. pub fn get(&self, index: usize) -> Option<&String> { self.entries.get(index) @@ -147,19 +134,14 @@ impl History { } } -impl Default for History { - fn default() -> History { - History::new() - } -} - #[cfg(test)] mod tests { extern crate tempdir; use std::path::Path; + use config::Config; fn init() -> super::History { - let mut history = super::History::new(); + let mut history = super::History::new(Config::default()); assert!(history.add("line1")); assert!(history.add("line2")); assert!(history.add("line3")); @@ -168,15 +150,18 @@ mod tests { #[test] fn new() { - let history = super::History::new(); - assert_eq!(super::DEFAULT_HISTORY_MAX_LEN, history.max_len); + let config = Config::default(); + let history = super::History::new(config); + assert_eq!(config.max_history_size(), history.max_len); assert_eq!(0, history.entries.len()); } #[test] fn add() { - let mut history = super::History::new(); - history.ignore_space(true); + let config = Config::builder() + .history_ignore_space(true) + .build(); + let mut history = super::History::new(config); assert!(history.add("line1")); assert!(history.add("line2")); assert!(!history.add("line2")); diff --git a/src/lib.rs b/src/lib.rs index b115ed9bfc..bcbcd1bd09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,8 @@ //! Usage //! //! ``` -//! let mut rl = rustyline::Editor::<()>::new(); +//! let config = rustyline::Config::default(); +//! let mut rl = rustyline::Editor::<()>::new(config); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -34,6 +35,7 @@ mod kill_ring; pub mod line_buffer; #[cfg(unix)] mod char_iter; +pub mod config; #[macro_use] mod tty; @@ -53,6 +55,7 @@ use consts::KeyPress; use history::History; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; use kill_ring::KillRing; +pub use config::{CompletionType, Config, HistoryDuplicates}; /// The error type for I/O and Linux Syscalls (Errno) pub type Result = result::Result; @@ -85,7 +88,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { -> State<'out, 'prompt> { let capacity = MAX_LINE; let cols = tty::get_columns(output_handle); - let prompt_size = calculate_position(prompt, Default::default(), cols); + let prompt_size = calculate_position(prompt, Position::default(), cols); State { out: out, prompt: prompt, @@ -116,7 +119,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { } fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> { - let prompt_size = calculate_position(prompt, Default::default(), self.cols); + let prompt_size = calculate_position(prompt, Position::default(), self.cols); self.refresh(prompt, prompt_size) } @@ -526,21 +529,11 @@ fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { s.refresh_line() } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum CompletionMode { - /// Complete the next full match (like in Vim by default) - Circular, - /// Complete till longest match. - /// When more than one match, list all matches - /// (like in Bash/Readline). - List, -} - /// Completes the line/word fn complete_line(rdr: &mut tty::RawReader, s: &mut State, completer: &Completer, - completion_mode: CompletionMode) + completion_type: CompletionType) -> Result> { // get a list of completions let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); @@ -548,7 +541,7 @@ fn complete_line(rdr: &mut tty::RawReader, if candidates.is_empty() { try!(beep()); Ok(None) - } else if CompletionMode::Circular == completion_mode { + } else if CompletionType::Circular == completion_type { // Save the current edited line before to overwrite it s.backup(); let mut key; @@ -587,7 +580,7 @@ fn complete_line(rdr: &mut tty::RawReader, } } Ok(Some(key)) - } else if CompletionMode::List == completion_mode { + } else if CompletionType::List == completion_type { // beep if ambiguous if candidates.len() > 1 { try!(beep()); @@ -604,7 +597,7 @@ fn complete_line(rdr: &mut tty::RawReader, let key = try!(rdr.next_key(false)); // if any character other than tab, pass it to the main loop if key != KeyPress::Tab { - return Ok(Some(key)) + return Ok(Some(key)); } // we got a second tab, maybe show list of possible completions // TODO ... @@ -726,8 +719,10 @@ fn readline_edit(prompt: &str, // autocomplete if key == KeyPress::Tab && completer.is_some() { - let next = - try!(complete_line(&mut rdr, &mut s, completer.unwrap(), editor.completion_mode)); + let next = try!(complete_line(&mut rdr, + &mut s, + completer.unwrap(), + editor.config.completion_type())); if next.is_some() { editor.kill_ring.reset(); key = next.unwrap(); @@ -974,19 +969,19 @@ pub struct Editor { history: History, completer: Option, kill_ring: KillRing, - completion_mode: CompletionMode, + config: Config, } impl Editor { - pub fn new() -> Editor { + pub fn new(config: Config) -> Editor { let editor = Editor { unsupported_term: tty::is_unsupported_term(), stdin_isatty: tty::is_a_tty(tty::STDIN_FILENO), stdout_isatty: tty::is_a_tty(tty::STDOUT_FILENO), - history: History::new(), + history: History::new(config), completer: None, kill_ring: KillRing::new(60), - completion_mode: CompletionMode::Circular, + config: config, }; if !editor.unsupported_term && editor.stdin_isatty && editor.stdout_isatty { tty::install_sigwinch_handler(); @@ -1010,20 +1005,6 @@ impl Editor { } } - /// Tell if lines which match the previous history entry are saved or not in the history list. - /// By default, they are ignored. - pub fn history_ignore_dups(mut self, yes: bool) -> Editor { - self.history.ignore_dups(yes); - self - } - - /// Tell if lines which begin with a space character are saved or not in the history list. - /// By default, they are saved. - pub fn history_ignore_space(mut self, yes: bool) -> Editor { - self.history.ignore_space(yes); - self - } - /// Load the history from the specified file. pub fn load_history + ?Sized>(&mut self, path: &P) -> Result<()> { self.history.load(path) @@ -1036,10 +1017,6 @@ impl Editor { pub fn add_history_entry(&mut self, line: &str) -> bool { self.history.add(line) } - /// Set the maximum length for the history. - pub fn set_history_max_len(&mut self, max_len: usize) { - self.history.set_max_len(max_len) - } /// Clear history. pub fn clear_history(&mut self) { self.history.clear() @@ -1053,17 +1030,6 @@ impl Editor { pub fn set_completer(&mut self, completer: Option) { self.completer = completer; } - - /// Set completion mode. - pub fn set_completion_mode(&mut self, completion_mode: CompletionMode) { - self.completion_mode = completion_mode; - } -} - -impl Default for Editor { - fn default() -> Editor { - Editor::new() - } } impl fmt::Debug for Editor { @@ -1081,8 +1047,8 @@ mod test { use line_buffer::LineBuffer; use history::History; use completion::Completer; - use CompletionMode; - use State; + use config::{Config, CompletionType}; + use {Position, State}; use super::Result; use tty::Handle; @@ -1098,9 +1064,9 @@ mod test { State { out: out, prompt: "", - prompt_size: Default::default(), + prompt_size: Position::default(), line: LineBuffer::init(line, pos), - cursor: Default::default(), + cursor: Position::default(), cols: cols, old_rows: 0, history_index: 0, @@ -1114,7 +1080,7 @@ mod test { let mut out = ::std::io::sink(); let line = "current edited line"; let mut s = init_state(&mut out, line, 6, 80); - let mut history = History::new(); + let mut history = History::new(Config::default()); history.add("line0"); history.add("line1"); s.history_index = history.len(); @@ -1164,7 +1130,7 @@ mod test { let input = b"\n"; let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; - let key = super::complete_line(&mut rdr, &mut s, &completer, CompletionMode::Circular) + let key = super::complete_line(&mut rdr, &mut s, &completer, CompletionType::Circular) .unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); assert_eq!("rust", s.line.as_str()); @@ -1173,7 +1139,7 @@ mod test { #[test] fn prompt_with_ansi_escape_codes() { - let pos = super::calculate_position("\x1b[1;32m>>\x1b[0m ", Default::default(), 80); + let pos = super::calculate_position("\x1b[1;32m>>\x1b[0m ", Position::default(), 80); assert_eq!(3, pos.col); assert_eq!(0, pos.row); } From bee7e7285ef717b324a536a76dbaecb93ee42e85 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 28 Aug 2016 11:13:28 +0200 Subject: [PATCH 0136/1201] Start page completions --- src/lib.rs | 53 ++++++++++++++++++++++++++++++++++------------ src/line_buffer.rs | 4 ++++ 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bcbcd1bd09..c101a13480 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -533,7 +533,7 @@ fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { fn complete_line(rdr: &mut tty::RawReader, s: &mut State, completer: &Completer, - completion_type: CompletionType) + config: &Config) -> Result> { // get a list of completions let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); @@ -541,7 +541,7 @@ fn complete_line(rdr: &mut tty::RawReader, if candidates.is_empty() { try!(beep()); Ok(None) - } else if CompletionType::Circular == completion_type { + } else if CompletionType::Circular == config.completion_type() { // Save the current edited line before to overwrite it s.backup(); let mut key; @@ -580,7 +580,7 @@ fn complete_line(rdr: &mut tty::RawReader, } } Ok(Some(key)) - } else if CompletionType::List == completion_type { + } else if CompletionType::List == config.completion_type() { // beep if ambiguous if candidates.len() > 1 { try!(beep()); @@ -594,19 +594,50 @@ fn complete_line(rdr: &mut tty::RawReader, } } // we can't complete any further, wait for second tab - let key = try!(rdr.next_key(false)); + let mut key = try!(rdr.next_key(false)); // if any character other than tab, pass it to the main loop if key != KeyPress::Tab { return Ok(Some(key)); } // we got a second tab, maybe show list of possible completions - // TODO ... - Ok(None) + let mut show_completions = true; + if candidates.len() > config.completion_prompt_limit() { + // move cursor to EOL to avoid overwriting the command line + let save_pos = s.line.pos(); + try!(edit_move_end(s)); + s.line.set_pos(save_pos); + let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len()); + try!(write_and_flush(s.out, msg.as_bytes())); + s.old_rows += 1; + while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && + key != KeyPress::Char('n') && key != KeyPress::Char('N') && + key != KeyPress::Backspace { + key = try!(rdr.next_key(true)); + } + show_completions = match key { + KeyPress::Char('y') | + KeyPress::Char('Y') => true, + _ => false, + }; + } + if show_completions { + page_completions(rdr, s) + } else { + try!(s.refresh_line()); + Ok(None) + } } else { Ok(None) } } +fn page_completions(r: &mut tty::RawReader, + s: &mut State) + -> Result> { + // TODO + Ok(None) +} + /// Incremental search fn reverse_incremental_search(rdr: &mut tty::RawReader, s: &mut State, @@ -719,10 +750,7 @@ fn readline_edit(prompt: &str, // autocomplete if key == KeyPress::Tab && completer.is_some() { - let next = try!(complete_line(&mut rdr, - &mut s, - completer.unwrap(), - editor.config.completion_type())); + let next = try!(complete_line(&mut rdr, &mut s, completer.unwrap(), &editor.config)); if next.is_some() { editor.kill_ring.reset(); key = next.unwrap(); @@ -1047,7 +1075,7 @@ mod test { use line_buffer::LineBuffer; use history::History; use completion::Completer; - use config::{Config, CompletionType}; + use config::Config; use {Position, State}; use super::Result; use tty::Handle; @@ -1130,8 +1158,7 @@ mod test { let input = b"\n"; let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; - let key = super::complete_line(&mut rdr, &mut s, &completer, CompletionType::Circular) - .unwrap(); + let key = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 479c686564..ddaddb72a6 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -47,6 +47,10 @@ impl LineBuffer { pub fn pos(&self) -> usize { self.pos } + pub fn set_pos(&mut self, pos: usize) { + assert!(pos <= self.buf.len()); + self.pos = pos; + } /// Returns the length of this buffer, in bytes. pub fn len(&self) -> usize { From 284b7eae017f89b56ee4deb7e66a7d3029c5cdb3 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 28 Aug 2016 16:08:14 +0200 Subject: [PATCH 0137/1201] More page completions --- src/lib.rs | 73 +++++++++++++++++++++++++++++++++++++++++----- src/tty/unix.rs | 16 ++++++++-- src/tty/windows.rs | 19 ++++++++++-- 3 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c101a13480..a3cd8ab2af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -599,13 +599,13 @@ fn complete_line(rdr: &mut tty::RawReader, if key != KeyPress::Tab { return Ok(Some(key)); } + // move cursor to EOL to avoid overwriting the command line + let save_pos = s.line.pos(); + try!(edit_move_end(s)); + s.line.set_pos(save_pos); // we got a second tab, maybe show list of possible completions let mut show_completions = true; if candidates.len() > config.completion_prompt_limit() { - // move cursor to EOL to avoid overwriting the command line - let save_pos = s.line.pos(); - try!(edit_move_end(s)); - s.line.set_pos(save_pos); let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len()); try!(write_and_flush(s.out, msg.as_bytes())); s.old_rows += 1; @@ -621,7 +621,7 @@ fn complete_line(rdr: &mut tty::RawReader, }; } if show_completions { - page_completions(rdr, s) + page_completions(rdr, s, &candidates) } else { try!(s.refresh_line()); Ok(None) @@ -631,9 +631,66 @@ fn complete_line(rdr: &mut tty::RawReader, } } -fn page_completions(r: &mut tty::RawReader, - s: &mut State) - -> Result> { +fn page_completions(rdr: &mut tty::RawReader, + s: &mut State, + candidates: &[String]) + -> Result> { + use std::cmp; + use unicode_width::UnicodeWidthStr; + + let min_col_pad = 2; + let max_width = cmp::min(s.cols, + candidates.into_iter() + .map(|s| UnicodeWidthStr::width(s.as_str())) + .max() + .unwrap() + min_col_pad); + let num_cols = s.cols / max_width; + + let mut pause_row = tty::get_rows(s.output_handle) - 1; + let num_rows = (candidates.len() + num_cols - 1) / num_cols; + let mut ab = String::new(); + for row in 0..num_rows { + if row == pause_row { + try!(write_and_flush(s.out, b"\n--More--")); + let mut key = KeyPress::Null; + while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && + key != KeyPress::Char('n') && key != KeyPress::Char('N') && + key != KeyPress::Char('q') && + key != KeyPress::Char('Q') && + key != KeyPress::Char(' ') && + key != KeyPress::Backspace && key != KeyPress::Enter { + key = try!(rdr.next_key(true)); + } + match key { + KeyPress::Char('y') | + KeyPress::Char('Y') | + KeyPress::Char(' ') => { + pause_row += tty::get_rows(s.output_handle) - 1; + } + KeyPress::Enter => { + pause_row += 1; + } + _ => break, + } + } else { + try!(write_and_flush(s.out, b"\n")); + } + ab.clear(); + for col in 0..num_cols { + let i = (col * num_rows) + row; + if i < candidates.len() { + let candidate = &candidates[i]; + ab.push_str(candidate); + let width = UnicodeWidthStr::width(candidate.as_str()); + if ((col + 1) * num_rows) + row < candidates.len() { + for _ in width..max_width { + ab.push(' '); + } + } + } + } + try!(write_and_flush(s.out, ab.as_bytes())); + } // TODO Ok(None) } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 106d3120db..ff1d0b31d8 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -32,6 +32,18 @@ const TIOCGWINSZ: libc::c_int = 0x5413; /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. pub fn get_columns(_: Handle) -> usize { + let (cols, _) = get_win_size(); + cols +} + +/// Try to get the number of rows in the current terminal, +/// or assume 24 if it fails. +pub fn get_rows(_: Handle) -> usize { + let (_, rows) = get_win_size(); + rows +} + +fn get_win_size() -> (usize, usize) { use std::mem::zeroed; use libc::c_ushort; @@ -46,8 +58,8 @@ pub fn get_columns(_: Handle) -> usize { let mut size: winsize = zeroed(); match libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size) { - 0 => size.ws_col as usize, // TODO getCursorPosition - _ => 80, + 0 => (size.ws_col as usize, size.ws_row as usize), // TODO getCursorPosition + _ => (80, 24), } } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 3d01af532c..7bcbdd5613 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -40,12 +40,25 @@ macro_rules! check { }; } -/// Try to get the number of columns in the current terminal, or assume 80 if it fails. +/// Try to get the number of columns in the current terminal, +/// or assume 80 if it fails. pub fn get_columns(handle: Handle) -> usize { + let (cols, _) = get_win_size(); + cols +} + +/// Try to get the number of rows in the current terminal, +/// or assume 24 if it fails. +pub fn get_rows(_: Handle) -> usize { + let (_, rows) = get_win_size(); + rows +} + +fn get_win_size() -> (usize, usize) { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { - 0 => 80, - _ => info.dwSize.X as usize, + 0 => (80, 24), + _ => (info.dwSize.X as usize, 1 + inf.srWindow.Bottom - inf.srWindow.Top), } } From 7e9d95b485bb71cae258cafb05d3d8c264d016fa Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 28 Aug 2016 17:41:49 +0200 Subject: [PATCH 0138/1201] Fix windows part --- src/tty/windows.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 7bcbdd5613..4776f0150b 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -43,22 +43,22 @@ macro_rules! check { /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. pub fn get_columns(handle: Handle) -> usize { - let (cols, _) = get_win_size(); + let (cols, _) = get_win_size(handle); cols } /// Try to get the number of rows in the current terminal, /// or assume 24 if it fails. -pub fn get_rows(_: Handle) -> usize { - let (_, rows) = get_win_size(); +pub fn get_rows(handle: Handle) -> usize { + let (_, rows) = get_win_size(handle); rows } -fn get_win_size() -> (usize, usize) { +fn get_win_size(handle: Handle) -> (usize, usize) { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { 0 => (80, 24), - _ => (info.dwSize.X as usize, 1 + inf.srWindow.Bottom - inf.srWindow.Top), + _ => (info.dwSize.X as usize, 1 + info.srWindow.Bottom - info.srWindow.Top), } } From e0a167588f3500573e6291662df367bef2c8e8e9 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 28 Aug 2016 17:52:18 +0200 Subject: [PATCH 0139/1201] Fix windows part --- src/tty/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 4776f0150b..f1b0958753 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -58,7 +58,7 @@ fn get_win_size(handle: Handle) -> (usize, usize) { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { 0 => (80, 24), - _ => (info.dwSize.X as usize, 1 + info.srWindow.Bottom - info.srWindow.Top), + _ => (info.dwSize.X as usize, (1 + info.srWindow.Bottom - info.srWindow.Top) as usize), } } From 6a3bc5b4823217fe54c3a3397165b95678354c0d Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 4 Sep 2016 11:40:28 +0200 Subject: [PATCH 0140/1201] Page completions --- examples/example.rs | 3 ++- src/lib.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 18bf58b4b7..8d49697fa6 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -2,7 +2,7 @@ extern crate rustyline; use rustyline::completion::FilenameCompleter; use rustyline::error::ReadlineError; -use rustyline::{Config, Editor}; +use rustyline::{Config, CompletionType, Editor}; // On unix platforms you can use ANSI escape sequences #[cfg(unix)] @@ -16,6 +16,7 @@ static PROMPT: &'static str = ">> "; fn main() { let config = Config::builder() .history_ignore_space(true) + .completion_type(CompletionType::List) .build(); let c = FilenameCompleter::new(); let mut rl = Editor::new(config); diff --git a/src/lib.rs b/src/lib.rs index a3cd8ab2af..214aff1de6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -672,6 +672,7 @@ fn page_completions(rdr: &mut tty::RawReader, } _ => break, } + try!(write_and_flush(s.out, b"\n")); } else { try!(write_and_flush(s.out, b"\n")); } @@ -691,7 +692,8 @@ fn page_completions(rdr: &mut tty::RawReader, } try!(write_and_flush(s.out, ab.as_bytes())); } - // TODO + try!(write_and_flush(s.out, b"\n")); + try!(s.refresh_line()); Ok(None) } From 90bc3146a47717eeef9427547f963028d4edcbf2 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 9 Sep 2016 20:47:03 +0200 Subject: [PATCH 0141/1201] Refactor before loading terminfo capabilities. --- src/lib.rs | 87 ++++++++++++++------------------ src/tty/mod.rs | 1 - src/tty/unix.rs | 102 ++++++++++++++++++++++++++++++------- src/tty/windows.rs | 122 +++++++++++++++++++++++++++++++++++---------- 4 files changed, 219 insertions(+), 93 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0b486fed29..9971fae235 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,17 +36,18 @@ mod kill_ring; pub mod line_buffer; pub mod config; -#[macro_use] mod tty; +use std::cell::RefCell; use std::fmt; use std::io::{self, Read, Write}; use std::mem; use std::path::Path; +use std::rc::Rc; use std::result; -use std::sync::atomic; #[cfg(unix)] use nix::sys::signal; +use tty::Terminal; use completion::{Completer, longest_common_prefix}; use consts::KeyPress; @@ -69,7 +70,7 @@ struct State<'out, 'prompt> { old_rows: usize, // Number of rows used so far (from start of prompt to end of input) history_index: usize, // The history index we are currently editing snapshot: LineBuffer, // Current edited line before history browsing/completion - output_handle: tty::Handle, // output handle (for windows) + term: Rc>, // terminal } #[derive(Copy, Clone, Debug, Default)] @@ -80,12 +81,12 @@ struct Position { impl<'out, 'prompt> State<'out, 'prompt> { fn new(out: &'out mut Write, - output_handle: tty::Handle, + term: Rc>, prompt: &'prompt str, history_index: usize) -> State<'out, 'prompt> { let capacity = MAX_LINE; - let cols = tty::get_columns(output_handle); + let cols = term.borrow().get_columns(); let prompt_size = calculate_position(prompt, Position::default(), cols); State { out: out, @@ -97,7 +98,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { old_rows: prompt_size.row, history_index: history_index, snapshot: LineBuffer::with_capacity(capacity), - output_handle: output_handle, + term: term, } } @@ -173,24 +174,21 @@ impl<'out, 'prompt> State<'out, 'prompt> { #[cfg(windows)] fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> { - let handle = self.output_handle; // calculate the position of the end of the input line let end_pos = calculate_position(&self.line, prompt_size, self.cols); // calculate the desired position of the cursor let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols); + let mut term = self.term.borrow_mut(); // position at the start of the prompt, clear to end of previous input - let mut info = unsafe { mem::zeroed() }; - check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); + let mut info = try!(term.get_console_screen_buffer_info()); info.dwCursorPosition.X = 0; info.dwCursorPosition.Y -= self.cursor.row as i16; - check!(kernel32::SetConsoleCursorPosition(handle, info.dwCursorPosition)); + try!(term.set_console_cursor_position(info.dwCursorPosition)); let mut _count = 0; - check!(kernel32::FillConsoleOutputCharacterA(handle, - ' ' as winapi::CHAR, - (info.dwSize.X * (self.old_rows as i16 +1)) as winapi::DWORD, - info.dwCursorPosition, - &mut _count)); + try!(term.fill_console_output_character( + (info.dwSize.X * (self.old_rows as i16 +1)) as u32, + info.dwCursorPosition)); let mut ab = String::new(); // display the prompt ab.push_str(prompt); // TODO handle ansi escape code (SetConsoleTextAttribute) @@ -199,10 +197,10 @@ impl<'out, 'prompt> State<'out, 'prompt> { try!(write_and_flush(self.out, ab.as_bytes())); // position the cursor - check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); + let mut info = try!(term.get_console_screen_buffer_info()); info.dwCursorPosition.X = cursor.col as i16; info.dwCursorPosition.Y -= (end_pos.row - cursor.row) as i16; - check!(kernel32::SetConsoleCursorPosition(handle, info.dwCursorPosition)); + try!(term.set_console_cursor_position(info.dwCursorPosition)); self.cursor = cursor; self.old_rows = end_pos.row; @@ -211,7 +209,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { } fn update_columns(&mut self) { - self.cols = tty::get_columns(self.output_handle); + self.cols = self.term.borrow().get_columns(); } } @@ -646,7 +644,7 @@ fn page_completions(rdr: &mut tty::RawReader, .unwrap() + min_col_pad); let num_cols = s.cols / max_width; - let mut pause_row = tty::get_rows(s.output_handle) - 1; + let mut pause_row = s.term.borrow().get_rows() - 1; let num_rows = (candidates.len() + num_cols - 1) / num_cols; let mut ab = String::new(); for row in 0..num_rows { @@ -665,7 +663,7 @@ fn page_completions(rdr: &mut tty::RawReader, KeyPress::Char('y') | KeyPress::Char('Y') | KeyPress::Char(' ') => { - pause_row += tty::get_rows(s.output_handle) - 1; + pause_row += s.term.borrow().get_rows() - 1; } KeyPress::Enter => { pause_row += 1; @@ -785,17 +783,16 @@ fn readline_edit(prompt: &str, let completer = editor.completer.as_ref().map(|c| c as &Completer); let mut stdout = io::stdout(); - let stdout_handle = try!(tty::stdout_handle()); editor.kill_ring.reset(); - let mut s = State::new(&mut stdout, stdout_handle, prompt, editor.history.len()); + let mut s = State::new(&mut stdout, editor.term.clone(), prompt, editor.history.len()); try!(s.refresh_line()); - let mut rdr = try!(tty::RawReader::new(io::stdin())); + let mut rdr = try!(s.term.borrow().create_reader()); loop { let rk = rdr.next_key(true); - if rk.is_err() && tty::SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { + if rk.is_err() && s.term.borrow().sigwinch() { s.update_columns(); try!(s.refresh_line()); continue; @@ -884,7 +881,7 @@ fn readline_edit(prompt: &str, } KeyPress::Ctrl('L') => { // Clear the screen leaving the current line at the top of the screen. - try!(tty::clear_screen(&mut s.out, s.output_handle)); + try!(s.term.borrow_mut().clear_screen(&mut s.out)); try!(s.refresh_line()) } KeyPress::Ctrl('N') | @@ -931,9 +928,9 @@ fn readline_edit(prompt: &str, } #[cfg(unix)] KeyPress::Ctrl('Z') => { - try!(tty::disable_raw_mode(original_mode)); + try!(s.term.borrow_mut().disable_raw_mode(original_mode)); try!(signal::raise(signal::SIGSTOP)); - try!(tty::enable_raw_mode()); // TODO original_mode may have changed + try!(s.term.borrow_mut().enable_raw_mode()); // TODO original_mode may have changed try!(s.refresh_line()) } // TODO CTRL-_ // undo @@ -1050,9 +1047,7 @@ fn readline_direct() -> Result { /// Line editor pub struct Editor { - unsupported_term: bool, - stdin_isatty: bool, - stdout_isatty: bool, + term: Rc>, history: History, completer: Option, kill_ring: KillRing, @@ -1061,30 +1056,25 @@ pub struct Editor { impl Editor { pub fn new(config: Config) -> Editor { - let editor = Editor { - unsupported_term: tty::is_unsupported_term(), - stdin_isatty: tty::is_a_tty(tty::STDIN_FILENO), - stdout_isatty: tty::is_a_tty(tty::STDOUT_FILENO), + let term = Rc::new(RefCell::new(Terminal::new())); + Editor { + term: term, history: History::new(config), completer: None, kill_ring: KillRing::new(60), config: config, - }; - if !editor.unsupported_term && editor.stdin_isatty && editor.stdout_isatty { - tty::install_sigwinch_handler(); } - editor } /// This method will read a line from STDIN and will display a `prompt` pub fn readline(&mut self, prompt: &str) -> Result { - if self.unsupported_term { + if self.term.borrow().is_unsupported() { // Write prompt and flush it to stdout let mut stdout = io::stdout(); try!(write_and_flush(&mut stdout, prompt.as_bytes())); readline_direct() - } else if !self.stdin_isatty { + } else if !self.term.borrow().is_stdin_tty() { // Not a tty: read from file / pipe. readline_direct() } else { @@ -1121,33 +1111,32 @@ impl Editor { impl fmt::Debug for Editor { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("State") - .field("unsupported_term", &self.unsupported_term) - .field("stdin_isatty", &self.stdin_isatty) + f.debug_struct("Editor") + .field("term", &self.term) + .field("config", &self.config) .finish() } } #[cfg(all(unix,test))] mod test { + use std::cell::RefCell; use std::io::Write; + use std::rc::Rc; use line_buffer::LineBuffer; use history::History; use completion::Completer; use config::Config; use {Position, State}; use super::Result; - use tty::Handle; - - fn default_handle() -> Handle { - () - } + use tty::Terminal; fn init_state<'out>(out: &'out mut Write, line: &str, pos: usize, cols: usize) -> State<'out, 'static> { + let term = Rc::new(RefCell::new(Terminal::new())); State { out: out, prompt: "", @@ -1158,7 +1147,7 @@ mod test { old_rows: 0, history_index: 0, snapshot: LineBuffer::with_capacity(100), - output_handle: default_handle(), + term: term, } } diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 8848f717d2..4f81220287 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -2,7 +2,6 @@ // If on Windows platform import Windows TTY module // and re-export into mod.rs scope -#[macro_use] #[cfg(windows)] mod windows; #[cfg(windows)] diff --git a/src/tty/unix.rs b/src/tty/unix.rs index bf09a3d4fe..bf84dbb771 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -11,10 +11,9 @@ use consts::{self, KeyPress}; use ::Result; use ::error; -pub type Handle = (); pub type Mode = termios::Termios; -pub const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; -pub const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; +const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; +const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; /// Unsupported Terminals that don't support RAW mode static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"]; @@ -30,14 +29,14 @@ const TIOCGWINSZ: libc::c_int = 0x5413; /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. -pub fn get_columns(_: Handle) -> usize { +fn get_columns() -> usize { let (cols, _) = get_win_size(); cols } /// Try to get the number of rows in the current terminal, /// or assume 24 if it fails. -pub fn get_rows(_: Handle) -> usize { +fn get_rows() -> usize { let (_, rows) = get_win_size(); rows } @@ -65,7 +64,7 @@ fn get_win_size() -> (usize, usize) { /// Check TERM environment variable to see if current term is in our /// unsupported list -pub fn is_unsupported_term() -> bool { +fn is_unsupported_term() -> bool { use std::ascii::AsciiExt; match std::env::var("TERM") { Ok(term) => { @@ -81,11 +80,10 @@ pub fn is_unsupported_term() -> bool { /// Return whether or not STDIN, STDOUT or STDERR is a TTY -pub fn is_a_tty(fd: libc::c_int) -> bool { +fn is_a_tty(fd: libc::c_int) -> bool { unsafe { libc::isatty(fd) != 0 } } -/// Enable raw mode for the TERM pub fn enable_raw_mode() -> Result { use nix::errno::Errno::ENOTTY; use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, @@ -109,18 +107,12 @@ pub fn enable_raw_mode() -> Result { Ok(original_mode) } -/// Disable Raw mode for the term pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &original_mode)); Ok(()) } -pub fn stdout_handle() -> Result { - Ok(()) -} - -/// Clear the screen. Used to handle ctrl+l -pub fn clear_screen(w: &mut Write, _: Handle) -> Result<()> { +fn clear_screen(w: &mut Write) -> Result<()> { try!(w.write_all(b"\x1b[H\x1b[2J")); try!(w.flush()); Ok(()) @@ -221,9 +213,9 @@ impl RawReader { } static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT; -pub static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; +static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; -pub fn install_sigwinch_handler() { +fn install_sigwinch_handler() { SIGWINCH_ONCE.call_once(|| unsafe { let sigwinch = signal::SigAction::new(signal::SigHandler::Handler(sigwinch_handler), signal::SaFlag::empty(), @@ -235,3 +227,79 @@ pub fn install_sigwinch_handler() { extern "C" fn sigwinch_handler(_: signal::SigNum) { SIGWINCH.store(true, atomic::Ordering::SeqCst); } + +pub type Terminal = PosixTerminal; + +#[derive(Debug)] +pub struct PosixTerminal { + unsupported: bool, + stdin_isatty: bool, +} + +impl PosixTerminal { + pub fn new() -> PosixTerminal { + let term = PosixTerminal { + unsupported: is_unsupported_term(), + stdin_isatty: is_a_tty(STDIN_FILENO), + }; + if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) { + install_sigwinch_handler(); + } + term + } + + // Init checks: + + /// Check if current terminal can provide a rich line-editing user interface. + pub fn is_unsupported(&self) -> bool { + self.unsupported + } + + /// check if stdin is connected to a terminal. + pub fn is_stdin_tty(&self) -> bool { + self.stdin_isatty + } + + // Init if terminal-style mode: + + // pub fn load_capabilities(&mut self) -> Result<()> { + // Ok(()) + // } + + // Interactive loop: + + /// Enable RAW mode for the terminal. + pub fn enable_raw_mode(&mut self) -> Result { + enable_raw_mode() + } + + /// Disable RAW mode for the terminal. + pub fn disable_raw_mode(&mut self, mode: Mode) -> Result<()> { + disable_raw_mode(mode) + } + + /// Get the number of columns in the current terminal. + pub fn get_columns(&self) -> usize { + get_columns() + } + + /// Get the number of rows in the current terminal. + pub fn get_rows(&self) -> usize { + get_rows() + } + + /// Create a RAW reader + pub fn create_reader(&self) -> Result> { + RawReader::new(std::io::stdin()) + } + + /// Check if a SIGWINCH signal has been received + pub fn sigwinch(&self) -> bool { + SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) + } + + /// Clear the screen. Used to handle ctrl+l + pub fn clear_screen(&mut self, w: &mut Write) -> Result<()> { + clear_screen(w) + } +} diff --git a/src/tty/windows.rs b/src/tty/windows.rs index f1b0958753..e0e62d04e3 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -11,10 +11,9 @@ use consts::{self, KeyPress}; use ::error; use ::Result; -pub type Handle = winapi::HANDLE; pub type Mode = winapi::DWORD; -pub const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; -pub const STDOUT_FILENO: winapi::DWORD = winapi::STD_OUTPUT_HANDLE; +const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; +const STDOUT_FILENO: winapi::DWORD = winapi::STD_OUTPUT_HANDLE; fn get_std_handle(fd: winapi::DWORD) -> Result { let handle = unsafe { kernel32::GetStdHandle(fd) }; @@ -40,21 +39,17 @@ macro_rules! check { }; } -/// Try to get the number of columns in the current terminal, -/// or assume 80 if it fails. -pub fn get_columns(handle: Handle) -> usize { +fn get_columns(handle: winapi::HANDLE) -> usize { let (cols, _) = get_win_size(handle); cols } -/// Try to get the number of rows in the current terminal, -/// or assume 24 if it fails. -pub fn get_rows(handle: Handle) -> usize { +fn get_rows(handle: winapi::HANDLE) -> usize { let (_, rows) = get_win_size(handle); rows } -fn get_win_size(handle: Handle) -> (usize, usize) { +fn get_win_size(handle: winapi::HANDLE) -> (usize, usize) { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { 0 => (80, 24), @@ -62,11 +57,6 @@ fn get_win_size(handle: Handle) -> (usize, usize) { } } -/// Checking for an unsupported TERM in windows is a no-op -pub fn is_unsupported_term() -> bool { - false -} - fn get_console_mode(handle: winapi::HANDLE) -> Result { let mut original_mode = 0; check!(kernel32::GetConsoleMode(handle, &mut original_mode)); @@ -74,7 +64,7 @@ fn get_console_mode(handle: winapi::HANDLE) -> Result { } /// Return whether or not STDIN, STDOUT or STDERR is a TTY -pub fn is_a_tty(fd: winapi::DWORD) -> bool { +fn is_a_tty(fd: winapi::DWORD) -> bool { let handle = get_std_handle(fd); match handle { Ok(handle) => { @@ -85,7 +75,6 @@ pub fn is_a_tty(fd: winapi::DWORD) -> bool { } } -/// Enable raw mode for the TERM pub fn enable_raw_mode() -> Result { let handle = try!(get_std_handle(STDIN_FILENO)); let original_mode = try!(get_console_mode(handle)); @@ -102,20 +91,14 @@ pub fn enable_raw_mode() -> Result { Ok(original_mode) } -/// Disable Raw mode for the term pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { let handle = try!(get_std_handle(STDIN_FILENO)); check!(kernel32::SetConsoleMode(handle, original_mode)); Ok(()) } -pub fn stdout_handle() -> Result { - let handle = try!(get_std_handle(STDOUT_FILENO)); - Ok(handle) -} - /// Clear the screen. Used to handle ctrl+l -pub fn clear_screen(_: &mut Write, handle: Handle) -> Result<()> { +fn clear_screen(_: &mut Write, handle: winapi::HANDLE) -> Result<()> { let mut info = unsafe { mem::zeroed() }; check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); let coord = winapi::COORD { X: 0, Y: 0 }; @@ -236,8 +219,95 @@ impl Iterator for RawReader { } } -pub static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; +static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; + +pub type Terminal = Console; + +#[derive(Debug)] +pub struct Console { + stdin_isatty: bool, + stdout_handle: winapi::HANDLE, +} + +impl Console { + pub fn new() -> Console { + use std::ptr; + let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); + Console { + stdin_isatty: is_a_tty(STDIN_FILENO), + stdout_handle: stdout_handle, + } + } + + /// Checking for an unsupported TERM in windows is a no-op + pub fn is_unsupported(&self) -> bool { + false + } + + pub fn is_stdin_tty(&self) -> bool { + self.stdin_isatty + } -pub fn install_sigwinch_handler() { + // pub fn install_sigwinch_handler(&mut self) { // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT + // } + + pub fn load_capabilities(&mut self) -> Result<()> { + Ok(()) + } + + /// Enable raw mode for the TERM + pub fn enable_raw_mode(&mut self) -> Result { + enable_raw_mode() + } + + /// Disable Raw mode for the term + pub fn disable_raw_mode(&mut self, mode: Mode) -> Result<()> { + disable_raw_mode(mode) + } + + /// Try to get the number of columns in the current terminal, + /// or assume 80 if it fails. + pub fn get_columns(&self) -> usize { + get_columns(self.stdout_handle) + } + + /// Try to get the number of rows in the current terminal, + /// or assume 24 if it fails. + pub fn get_rows(&self) -> usize { + get_rows(self.stdout_handle) + } + + pub fn create_reader(&self) -> Result> { + RawReader::new(io::stdin()) // FIXME + } + + pub fn sigwinch(&self) -> bool { + SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) + } + + pub fn clear_screen(&mut self, w: &mut Write) -> Result<()> { + clear_screen(w, self.stdout_handle) + } + + pub fn get_console_screen_buffer_info(&self) -> Result { + let mut info = unsafe { mem::zeroed() }; + check!(kernel32::GetConsoleScreenBufferInfo(self.stdout_handle, &mut info)); + Ok(info) + } + + pub fn set_console_cursor_position(&mut self, pos: winapi::COORD) -> Result<()> { + check!(kernel32::SetConsoleCursorPosition(self.stdout_handle, pos)); + Ok(()) + } + + pub fn fill_console_output_character(&mut self, length: winapi::DWORD, pos: winapi::COORD) -> Result<()> { + let mut _count = 0; + check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle, + ' ' as winapi::CHAR, + length, + pos, + &mut _count)); + Ok(()) + } } From 037e0674564a8ed368ac5e1d1a39084a30217091 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 9 Sep 2016 20:52:44 +0200 Subject: [PATCH 0142/1201] Fix Travis script --- .travis.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 00eff38e20..ea884271bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,3 @@ rust: script: - cargo build --verbose - cargo test --verbose - - cargo doc -after_success: | - [ $TRAVIS_BRANCH = master ] && - [ $TRAVIS_PULL_REQUEST = false ] && - bash deploy-docs.sh -env: - global: - secure: CEgJhYJN0LBGdrhBfeFywxPLKJLnjgAhu2H1A7Gl8r7PGhSlvMjLs1CgLluD83pUxrtxAxLxT/I3bJeUhPI5fbxwxfXO7V48yYqivAx11f0FCnvkBFRcxFCysZLazgEFpttDaxwySC69CL+uwoP93F4lO/YKulyUqiEbDdJsZdM= From 8eaa6c9e0abc22ec0df221e696a9da3513a9df00 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 11 Sep 2016 15:42:36 +0200 Subject: [PATCH 0143/1201] Load TermInfo --- Cargo.toml | 1 + examples/example.rs | 2 +- rustfmt.toml | 4 ++- src/error.rs | 15 ++++++++++ src/lib.rs | 68 ++++++++++++++++++++++++--------------------- src/tty/unix.rs | 50 ++++++++++++++++++++------------- src/tty/windows.rs | 37 ++++++++++-------------- 7 files changed, 100 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3580e38803..e49e3c0f19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ unicode-width = "0.1.3" [target.'cfg(unix)'.dependencies] nix = "0.5.0" +term = "0.4" [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/examples/example.rs b/examples/example.rs index 8d49697fa6..ff7f9b625b 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -19,7 +19,7 @@ fn main() { .completion_type(CompletionType::List) .build(); let c = FilenameCompleter::new(); - let mut rl = Editor::new(config); + let mut rl = Editor::new(config).expect("Cannot create line editor"); rl.set_completer(Some(c)); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); diff --git a/rustfmt.toml b/rustfmt.toml index 44148a2d3c..6496add0b7 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,3 @@ -reorder_imports = true +reorder_imports = false +normalise_comments = false +write_mode = "Overwrite" \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 3df6e70e3f..1a4d92940b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,8 @@ use std::error; use std::fmt; #[cfg(unix)] use nix; +#[cfg(unix)] +use term; /// The error type for Rustyline errors that can arise from /// I/O related errors or Errno when using the nix-rust library @@ -23,6 +25,8 @@ pub enum ReadlineError { /// Unix Error from syscall #[cfg(unix)] Errno(nix::Error), + #[cfg(unix)] + TermError(term::Error), #[cfg(windows)] WindowResize, #[cfg(windows)] @@ -39,6 +43,8 @@ impl fmt::Display for ReadlineError { ReadlineError::Char(ref err) => err.fmt(f), #[cfg(unix)] ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()), + #[cfg(unix)] + ReadlineError::TermError(ref err) => err.fmt(f), #[cfg(windows)] ReadlineError::WindowResize => write!(f, "WindowResize"), #[cfg(windows)] @@ -57,6 +63,8 @@ impl error::Error for ReadlineError { ReadlineError::Char(ref err) => err.description(), #[cfg(unix)] ReadlineError::Errno(ref err) => err.errno().desc(), + #[cfg(unix)] + ReadlineError::TermError(ref err) => err.description(), #[cfg(windows)] ReadlineError::WindowResize => "WindowResize", #[cfg(windows)] @@ -85,6 +93,13 @@ impl From for ReadlineError { } } +#[cfg(unix)] +impl From for ReadlineError { + fn from(err: term::Error) -> ReadlineError { + ReadlineError::TermError(err) + } +} + #[cfg(windows)] impl From for ReadlineError { fn from(err: char::DecodeUtf16Error) -> ReadlineError { diff --git a/src/lib.rs b/src/lib.rs index 9971fae235..a9cc263f6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! //! ``` //! let config = rustyline::Config::default(); -//! let mut rl = rustyline::Editor::<()>::new(config); +//! let mut rl = rustyline::Editor::<()>::new(config).expect("Cannot create line editor"); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -22,6 +22,8 @@ extern crate libc; #[cfg(unix)] extern crate nix; +#[cfg(unix)] +extern crate term; extern crate unicode_width; #[cfg(windows)] extern crate winapi; @@ -38,12 +40,10 @@ pub mod config; mod tty; -use std::cell::RefCell; use std::fmt; use std::io::{self, Read, Write}; use std::mem; use std::path::Path; -use std::rc::Rc; use std::result; #[cfg(unix)] use nix::sys::signal; @@ -70,7 +70,7 @@ struct State<'out, 'prompt> { old_rows: usize, // Number of rows used so far (from start of prompt to end of input) history_index: usize, // The history index we are currently editing snapshot: LineBuffer, // Current edited line before history browsing/completion - term: Rc>, // terminal + term: Terminal, // terminal } #[derive(Copy, Clone, Debug, Default)] @@ -81,12 +81,12 @@ struct Position { impl<'out, 'prompt> State<'out, 'prompt> { fn new(out: &'out mut Write, - term: Rc>, + term: Terminal, prompt: &'prompt str, history_index: usize) -> State<'out, 'prompt> { let capacity = MAX_LINE; - let cols = term.borrow().get_columns(); + let cols = term.get_columns(); let prompt_size = calculate_position(prompt, Position::default(), cols); State { out: out, @@ -179,16 +179,15 @@ impl<'out, 'prompt> State<'out, 'prompt> { // calculate the desired position of the cursor let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols); - let mut term = self.term.borrow_mut(); // position at the start of the prompt, clear to end of previous input - let mut info = try!(term.get_console_screen_buffer_info()); + let mut info = try!(self.term.get_console_screen_buffer_info()); info.dwCursorPosition.X = 0; info.dwCursorPosition.Y -= self.cursor.row as i16; - try!(term.set_console_cursor_position(info.dwCursorPosition)); + try!(self.term.set_console_cursor_position(info.dwCursorPosition)); let mut _count = 0; - try!(term.fill_console_output_character( - (info.dwSize.X * (self.old_rows as i16 +1)) as u32, - info.dwCursorPosition)); + try!(self.term + .fill_console_output_character((info.dwSize.X * (self.old_rows as i16 + 1)) as u32, + info.dwCursorPosition)); let mut ab = String::new(); // display the prompt ab.push_str(prompt); // TODO handle ansi escape code (SetConsoleTextAttribute) @@ -197,10 +196,10 @@ impl<'out, 'prompt> State<'out, 'prompt> { try!(write_and_flush(self.out, ab.as_bytes())); // position the cursor - let mut info = try!(term.get_console_screen_buffer_info()); + let mut info = try!(self.term.get_console_screen_buffer_info()); info.dwCursorPosition.X = cursor.col as i16; info.dwCursorPosition.Y -= (end_pos.row - cursor.row) as i16; - try!(term.set_console_cursor_position(info.dwCursorPosition)); + try!(self.term.set_console_cursor_position(info.dwCursorPosition)); self.cursor = cursor; self.old_rows = end_pos.row; @@ -209,7 +208,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { } fn update_columns(&mut self) { - self.cols = self.term.borrow().get_columns(); + self.cols = self.term.get_columns(); } } @@ -644,7 +643,7 @@ fn page_completions(rdr: &mut tty::RawReader, .unwrap() + min_col_pad); let num_cols = s.cols / max_width; - let mut pause_row = s.term.borrow().get_rows() - 1; + let mut pause_row = s.term.get_rows() - 1; let num_rows = (candidates.len() + num_cols - 1) / num_cols; let mut ab = String::new(); for row in 0..num_rows { @@ -663,7 +662,7 @@ fn page_completions(rdr: &mut tty::RawReader, KeyPress::Char('y') | KeyPress::Char('Y') | KeyPress::Char(' ') => { - pause_row += s.term.borrow().get_rows() - 1; + pause_row += s.term.get_rows() - 1; } KeyPress::Enter => { pause_row += 1; @@ -785,14 +784,17 @@ fn readline_edit(prompt: &str, let mut stdout = io::stdout(); editor.kill_ring.reset(); - let mut s = State::new(&mut stdout, editor.term.clone(), prompt, editor.history.len()); + let mut s = State::new(&mut stdout, + editor.term.clone(), + prompt, + editor.history.len()); try!(s.refresh_line()); - let mut rdr = try!(s.term.borrow().create_reader()); + let mut rdr = try!(s.term.create_reader()); loop { let rk = rdr.next_key(true); - if rk.is_err() && s.term.borrow().sigwinch() { + if rk.is_err() && s.term.sigwinch() { s.update_columns(); try!(s.refresh_line()); continue; @@ -881,7 +883,7 @@ fn readline_edit(prompt: &str, } KeyPress::Ctrl('L') => { // Clear the screen leaving the current line at the top of the screen. - try!(s.term.borrow_mut().clear_screen(&mut s.out)); + try!(s.term.clear_screen(&mut s.out)); try!(s.refresh_line()) } KeyPress::Ctrl('N') | @@ -928,9 +930,9 @@ fn readline_edit(prompt: &str, } #[cfg(unix)] KeyPress::Ctrl('Z') => { - try!(s.term.borrow_mut().disable_raw_mode(original_mode)); + try!(tty::disable_raw_mode(original_mode)); try!(signal::raise(signal::SIGSTOP)); - try!(s.term.borrow_mut().enable_raw_mode()); // TODO original_mode may have changed + try!(tty::enable_raw_mode()); // TODO original_mode may have changed try!(s.refresh_line()) } // TODO CTRL-_ // undo @@ -1047,7 +1049,7 @@ fn readline_direct() -> Result { /// Line editor pub struct Editor { - term: Rc>, + term: Terminal, history: History, completer: Option, kill_ring: KillRing, @@ -1055,26 +1057,26 @@ pub struct Editor { } impl Editor { - pub fn new(config: Config) -> Editor { - let term = Rc::new(RefCell::new(Terminal::new())); - Editor { + pub fn new(config: Config) -> Result> { + let term = try!(Terminal::new()); + Ok(Editor { term: term, history: History::new(config), completer: None, kill_ring: KillRing::new(60), config: config, - } + }) } /// This method will read a line from STDIN and will display a `prompt` pub fn readline(&mut self, prompt: &str) -> Result { - if self.term.borrow().is_unsupported() { + if self.term.is_unsupported() { // Write prompt and flush it to stdout let mut stdout = io::stdout(); try!(write_and_flush(&mut stdout, prompt.as_bytes())); readline_direct() - } else if !self.term.borrow().is_stdin_tty() { + } else if !self.term.is_stdin_tty() { // Not a tty: read from file / pipe. readline_direct() } else { @@ -1121,6 +1123,7 @@ impl fmt::Debug for Editor { #[cfg(all(unix,test))] mod test { use std::cell::RefCell; + use std::collections::HashMap; use std::io::Write; use std::rc::Rc; use line_buffer::LineBuffer; @@ -1136,7 +1139,7 @@ mod test { pos: usize, cols: usize) -> State<'out, 'static> { - let term = Rc::new(RefCell::new(Terminal::new())); + let term = Terminal::new().unwrap(); State { out: out, prompt: "", @@ -1204,7 +1207,8 @@ mod test { let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); let input = b"\n"; - let mut rdr = RawReader::new(&input[..]).unwrap(); + let terminfo_keys = Rc::new(RefCell::new(HashMap::new())); + let mut rdr = RawReader::new(&input[..], terminfo_keys).unwrap(); let completer = SimpleCompleter; let key = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); diff --git a/src/tty/unix.rs b/src/tty/unix.rs index bf84dbb771..b4f0ac84ba 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,5 +1,8 @@ use std; +use std::cell::RefCell; +use std::collections::HashMap; use std::io::{Chars, Read, Write}; +use std::rc::Rc; use std::sync; use std::sync::atomic; use libc; @@ -84,6 +87,7 @@ fn is_a_tty(fd: libc::c_int) -> bool { unsafe { libc::isatty(fd) != 0 } } +/// Enable RAW mode for the terminal. pub fn enable_raw_mode() -> Result { use nix::errno::Errno::ENOTTY; use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, @@ -107,6 +111,7 @@ pub fn enable_raw_mode() -> Result { Ok(original_mode) } +/// Disable RAW mode for the terminal. pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &original_mode)); Ok(()) @@ -121,11 +126,17 @@ fn clear_screen(w: &mut Write) -> Result<()> { /// Console input reader pub struct RawReader { chars: Chars, + terminfo_keys: Rc, String>>>, } impl RawReader { - pub fn new(stdin: R) -> Result> { - Ok(RawReader { chars: stdin.chars() }) + pub fn new(stdin: R, + terminfo_keys: Rc, String>>>) + -> Result> { + Ok(RawReader { + chars: stdin.chars(), + terminfo_keys: terminfo_keys, + }) } // As there is no read timeout to properly handle single ESC key, @@ -230,22 +241,25 @@ extern "C" fn sigwinch_handler(_: signal::SigNum) { pub type Terminal = PosixTerminal; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct PosixTerminal { unsupported: bool, stdin_isatty: bool, + terminfo_keys: Rc, String>>>, } impl PosixTerminal { - pub fn new() -> PosixTerminal { - let term = PosixTerminal { + pub fn new() -> Result { + let mut term = PosixTerminal { unsupported: is_unsupported_term(), stdin_isatty: is_a_tty(STDIN_FILENO), + terminfo_keys: Rc::new(RefCell::new(HashMap::new())), }; if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) { install_sigwinch_handler(); + try!(term.load_capabilities()); } - term + Ok(term) } // Init checks: @@ -262,21 +276,17 @@ impl PosixTerminal { // Init if terminal-style mode: - // pub fn load_capabilities(&mut self) -> Result<()> { - // Ok(()) - // } - - // Interactive loop: - - /// Enable RAW mode for the terminal. - pub fn enable_raw_mode(&mut self) -> Result { - enable_raw_mode() + fn load_capabilities(&mut self) -> Result<()> { + use term::terminfo::TermInfo; + let term_info = try!(TermInfo::from_env()); + let mut terminfo_keys = self.terminfo_keys.borrow_mut(); + for (key, val) in term_info.strings.into_iter() { + terminfo_keys.insert(val.clone(), key.clone()); + } + Ok(()) } - /// Disable RAW mode for the terminal. - pub fn disable_raw_mode(&mut self, mode: Mode) -> Result<()> { - disable_raw_mode(mode) - } + // Interactive loop: /// Get the number of columns in the current terminal. pub fn get_columns(&self) -> usize { @@ -290,7 +300,7 @@ impl PosixTerminal { /// Create a RAW reader pub fn create_reader(&self) -> Result> { - RawReader::new(std::io::stdin()) + RawReader::new(std::io::stdin(), self.terminfo_keys.clone()) } /// Check if a SIGWINCH signal has been received diff --git a/src/tty/windows.rs b/src/tty/windows.rs index e0e62d04e3..5f83d34b9f 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -75,6 +75,7 @@ fn is_a_tty(fd: winapi::DWORD) -> bool { } } +/// Enable raw mode for the TERM pub fn enable_raw_mode() -> Result { let handle = try!(get_std_handle(STDIN_FILENO)); let original_mode = try!(get_console_mode(handle)); @@ -91,6 +92,7 @@ pub fn enable_raw_mode() -> Result { Ok(original_mode) } +/// Disable Raw mode for the term pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { let handle = try!(get_std_handle(STDIN_FILENO)); check!(kernel32::SetConsoleMode(handle, original_mode)); @@ -223,20 +225,20 @@ static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; pub type Terminal = Console; -#[derive(Debug)] +#[derive(Clone,Debug)] pub struct Console { stdin_isatty: bool, stdout_handle: winapi::HANDLE, } impl Console { - pub fn new() -> Console { + pub fn new() -> Result { use std::ptr; let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); - Console { + Ok(Console { stdin_isatty: is_a_tty(STDIN_FILENO), stdout_handle: stdout_handle, - } + }) } /// Checking for an unsupported TERM in windows is a no-op @@ -252,20 +254,6 @@ impl Console { // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT // } - pub fn load_capabilities(&mut self) -> Result<()> { - Ok(()) - } - - /// Enable raw mode for the TERM - pub fn enable_raw_mode(&mut self) -> Result { - enable_raw_mode() - } - - /// Disable Raw mode for the term - pub fn disable_raw_mode(&mut self, mode: Mode) -> Result<()> { - disable_raw_mode(mode) - } - /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. pub fn get_columns(&self) -> usize { @@ -301,13 +289,16 @@ impl Console { Ok(()) } - pub fn fill_console_output_character(&mut self, length: winapi::DWORD, pos: winapi::COORD) -> Result<()> { + pub fn fill_console_output_character(&mut self, + length: winapi::DWORD, + pos: winapi::COORD) + -> Result<()> { let mut _count = 0; check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle, - ' ' as winapi::CHAR, - length, - pos, - &mut _count)); + ' ' as winapi::CHAR, + length, + pos, + &mut _count)); Ok(()) } } From e5949c54da8f0065ea0aba104c118efbfcee42c0 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 11 Sep 2016 15:44:56 +0200 Subject: [PATCH 0144/1201] Introduce search Direction and killring Mode enums --- src/history.rs | 64 ++++++++++++++++++++++++++++-------------------- src/kill_ring.rs | 57 ++++++++++++++++++++++-------------------- src/lib.rs | 22 ++++++++--------- 3 files changed, 79 insertions(+), 64 deletions(-) diff --git a/src/history.rs b/src/history.rs index 5d40d5e38d..aa5f22f2dc 100644 --- a/src/history.rs +++ b/src/history.rs @@ -7,6 +7,12 @@ use std::path::Path; use super::Result; use config::{Config, HistoryDuplicates}; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Direction { + Forward, + Reverse, +} + pub struct History { entries: VecDeque, max_len: usize, @@ -118,20 +124,23 @@ impl History { /// Return the absolute index of the nearest history entry that matches `term`. /// Return None if no entry contains `term` between [start, len -1] for forward search /// or between [0, start] for reverse search. - pub fn search(&self, term: &str, start: usize, reverse: bool) -> Option { + pub fn search(&self, term: &str, start: usize, dir: Direction) -> Option { if term.is_empty() || start >= self.len() { return None; } - if reverse { - let index = self.entries - .iter() - .rev() - .skip(self.entries.len() - 1 - start) - .position(|entry| entry.contains(term)); - index.and_then(|index| Some(start - index)) - } else { - let index = self.entries.iter().skip(start).position(|entry| entry.contains(term)); - index.and_then(|index| Some(index + start)) + match dir { + Direction::Reverse => { + let index = self.entries + .iter() + .rev() + .skip(self.entries.len() - 1 - start) + .position(|entry| entry.contains(term)); + index.and_then(|index| Some(start - index)) + } + Direction::Forward => { + let index = self.entries.iter().skip(start).position(|entry| entry.contains(term)); + index.and_then(|index| Some(index + start)) + } } } } @@ -141,9 +150,10 @@ mod tests { extern crate tempdir; use std::path::Path; use config::Config; + use super::{Direction, History}; - fn init() -> super::History { - let mut history = super::History::new(Config::default()); + fn init() -> History { + let mut history = History::new(Config::default()); assert!(history.add("line1")); assert!(history.add("line2")); assert!(history.add("line3")); @@ -153,7 +163,7 @@ mod tests { #[test] fn new() { let config = Config::default(); - let history = super::History::new(config); + let history = History::new(config); assert_eq!(config.max_history_size(), history.max_len); assert_eq!(0, history.entries.len()); } @@ -163,7 +173,7 @@ mod tests { let config = Config::builder() .history_ignore_space(true) .build(); - let mut history = super::History::new(config); + let mut history = History::new(config); assert!(history.add("line1")); assert!(history.add("line2")); assert!(!history.add("line2")); @@ -193,24 +203,24 @@ mod tests { #[test] fn search() { let history = init(); - assert_eq!(None, history.search("", 0, false)); - assert_eq!(None, history.search("none", 0, false)); - assert_eq!(None, history.search("line", 3, false)); + assert_eq!(None, history.search("", 0, Direction::Forward)); + assert_eq!(None, history.search("none", 0, Direction::Forward)); + assert_eq!(None, history.search("line", 3, Direction::Forward)); - assert_eq!(Some(0), history.search("line", 0, false)); - assert_eq!(Some(1), history.search("line", 1, false)); - assert_eq!(Some(2), history.search("line3", 1, false)); + assert_eq!(Some(0), history.search("line", 0, Direction::Forward)); + assert_eq!(Some(1), history.search("line", 1, Direction::Forward)); + assert_eq!(Some(2), history.search("line3", 1, Direction::Forward)); } #[test] fn reverse_search() { let history = init(); - assert_eq!(None, history.search("", 2, true)); - assert_eq!(None, history.search("none", 2, true)); - assert_eq!(None, history.search("line", 3, true)); + assert_eq!(None, history.search("", 2, Direction::Reverse)); + assert_eq!(None, history.search("none", 2, Direction::Reverse)); + assert_eq!(None, history.search("line", 3, Direction::Reverse)); - assert_eq!(Some(2), history.search("line", 2, true)); - assert_eq!(Some(1), history.search("line", 1, true)); - assert_eq!(Some(0), history.search("line1", 1, true)); + assert_eq!(Some(2), history.search("line", 2, Direction::Reverse)); + assert_eq!(Some(1), history.search("line", 1, Direction::Reverse)); + assert_eq!(Some(0), history.search("line1", 1, Direction::Reverse)); } } diff --git a/src/kill_ring.rs b/src/kill_ring.rs index f897597c65..7575c3025b 100644 --- a/src/kill_ring.rs +++ b/src/kill_ring.rs @@ -7,6 +7,12 @@ enum Action { Other, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Mode { + Append, + Prepend, +} + pub struct KillRing { slots: Vec, index: usize, @@ -29,20 +35,19 @@ impl KillRing { } /// Add `text` to the kill-ring. - pub fn kill(&mut self, text: &str, forward: bool) { + pub fn kill(&mut self, text: &str, dir: Mode) { match self.last_action { Action::Kill => { if self.slots.capacity() == 0 { // disabled return; } - if forward { - // append - self.slots[self.index].push_str(text); - } else { - // prepend - self.slots[self.index] = String::from(text) + &self.slots[self.index]; - } + match dir { + Mode::Append => self.slots[self.index].push_str(text), + Mode::Prepend => { + self.slots[self.index] = String::from(text) + &self.slots[self.index] + } + }; } _ => { self.last_action = Action::Kill; @@ -99,12 +104,12 @@ impl KillRing { #[cfg(test)] mod tests { - use super::{Action, KillRing}; + use super::{Action, Mode, KillRing}; #[test] fn disabled() { let mut kill_ring = KillRing::new(0); - kill_ring.kill("text", true); + kill_ring.kill("text", Mode::Append); assert!(kill_ring.slots.is_empty()); assert_eq!(0, kill_ring.index); assert_eq!(Action::Kill, kill_ring.last_action); @@ -116,7 +121,7 @@ mod tests { #[test] fn one_kill() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); + kill_ring.kill("word1", Mode::Append); assert_eq!(0, kill_ring.index); assert_eq!(1, kill_ring.slots.len()); assert_eq!("word1", kill_ring.slots[0]); @@ -124,10 +129,10 @@ mod tests { } #[test] - fn kill_kill_forward() { + fn kill_kill_Append() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); - kill_ring.kill(" word2", true); + kill_ring.kill("word1", Mode::Append); + kill_ring.kill(" word2", Mode::Append); assert_eq!(0, kill_ring.index); assert_eq!(1, kill_ring.slots.len()); assert_eq!("word1 word2", kill_ring.slots[0]); @@ -137,8 +142,8 @@ mod tests { #[test] fn kill_kill_backward() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", false); - kill_ring.kill("word2 ", false); + kill_ring.kill("word1", Mode::Prepend); + kill_ring.kill("word2 ", Mode::Prepend); assert_eq!(0, kill_ring.index); assert_eq!(1, kill_ring.slots.len()); assert_eq!("word2 word1", kill_ring.slots[0]); @@ -148,9 +153,9 @@ mod tests { #[test] fn kill_other_kill() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); + kill_ring.kill("word1", Mode::Append); kill_ring.reset(); - kill_ring.kill("word2", true); + kill_ring.kill("word2", Mode::Append); assert_eq!(1, kill_ring.index); assert_eq!(2, kill_ring.slots.len()); assert_eq!("word1", kill_ring.slots[0]); @@ -161,13 +166,13 @@ mod tests { #[test] fn many_kill() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); + kill_ring.kill("word1", Mode::Append); kill_ring.reset(); - kill_ring.kill("word2", true); + kill_ring.kill("word2", Mode::Append); kill_ring.reset(); - kill_ring.kill("word3", true); + kill_ring.kill("word3", Mode::Append); kill_ring.reset(); - kill_ring.kill("word4", true); + kill_ring.kill("word4", Mode::Append); assert_eq!(1, kill_ring.index); assert_eq!(2, kill_ring.slots.len()); assert_eq!("word3", kill_ring.slots[0]); @@ -178,9 +183,9 @@ mod tests { #[test] fn yank() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); + kill_ring.kill("word1", Mode::Append); kill_ring.reset(); - kill_ring.kill("word2", true); + kill_ring.kill("word2", Mode::Append); assert_eq!(Some(&"word2".to_string()), kill_ring.yank()); assert_eq!(Action::Yank(5), kill_ring.last_action); @@ -191,9 +196,9 @@ mod tests { #[test] fn yank_pop() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); + kill_ring.kill("word1", Mode::Append); kill_ring.reset(); - kill_ring.kill("longword2", true); + kill_ring.kill("longword2", Mode::Append); assert_eq!(None, kill_ring.yank_pop()); kill_ring.yank(); diff --git a/src/lib.rs b/src/lib.rs index a9cc263f6b..c4f25994fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,9 +51,9 @@ use tty::Terminal; use completion::{Completer, longest_common_prefix}; use consts::KeyPress; -use history::History; +use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; -use kill_ring::KillRing; +use kill_ring::{Mode, KillRing}; pub use config::{CompletionType, Config, HistoryDuplicates}; /// The error type for I/O and Linux Syscalls (Errno) @@ -707,7 +707,7 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, let mut search_buf = String::new(); let mut history_idx = history.len() - 1; - let mut reverse = true; + let mut direction = Direction::Reverse; let mut success = true; let mut key; @@ -731,7 +731,7 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, continue; } KeyPress::Ctrl('R') => { - reverse = true; + direction = Direction::Reverse; if history_idx > 0 { history_idx -= 1; } else { @@ -740,7 +740,7 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, } } KeyPress::Ctrl('S') => { - reverse = false; + direction = Direction::Forward; if history_idx < history.len() - 1 { history_idx += 1; } else { @@ -757,7 +757,7 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, _ => break, } } - success = match history.search(&search_buf, history_idx, reverse) { + success = match history.search(&search_buf, history_idx, direction) { Some(idx) => { history_idx = idx; let entry = history.get(idx).unwrap(); @@ -878,7 +878,7 @@ fn readline_edit(prompt: &str, KeyPress::Ctrl('K') => { // Kill the text from point to the end of the line. if let Some(text) = try!(edit_kill_line(&mut s)) { - editor.kill_ring.kill(&text, true) + editor.kill_ring.kill(&text, Mode::Append) } } KeyPress::Ctrl('L') => { @@ -906,7 +906,7 @@ fn readline_edit(prompt: &str, KeyPress::Ctrl('U') => { // Kill backward from point to the beginning of the line. if let Some(text) = try!(edit_discard_line(&mut s)) { - editor.kill_ring.kill(&text, false) + editor.kill_ring.kill(&text, Mode::Prepend) } } #[cfg(unix)] @@ -919,7 +919,7 @@ fn readline_edit(prompt: &str, KeyPress::Ctrl('W') => { // Kill the word behind point, using white space as a word boundary if let Some(text) = try!(edit_delete_prev_word(&mut s, char::is_whitespace)) { - editor.kill_ring.kill(&text, false) + editor.kill_ring.kill(&text, Mode::Prepend) } } KeyPress::Ctrl('Y') => { @@ -949,7 +949,7 @@ fn readline_edit(prompt: &str, // Kill from the cursor to the start of the current word, or, if between words, to the start of the previous word. if let Some(text) = try!(edit_delete_prev_word(&mut s, |ch| !ch.is_alphanumeric())) { - editor.kill_ring.kill(&text, false) + editor.kill_ring.kill(&text, Mode::Prepend) } } KeyPress::Meta('<') => { @@ -975,7 +975,7 @@ fn readline_edit(prompt: &str, KeyPress::Meta('D') => { // kill one word forward if let Some(text) = try!(edit_delete_word(&mut s)) { - editor.kill_ring.kill(&text, true) + editor.kill_ring.kill(&text, Mode::Append) } } KeyPress::Meta('F') => { From 5b498c98a3663f61073380c030d241c2f60af821 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 11 Sep 2016 15:44:56 +0200 Subject: [PATCH 0145/1201] Introduce search Direction and killring Mode enums --- src/history.rs | 64 ++++++++++++++++++++++++++++-------------------- src/kill_ring.rs | 59 ++++++++++++++++++++++++-------------------- src/lib.rs | 22 ++++++++--------- 3 files changed, 80 insertions(+), 65 deletions(-) diff --git a/src/history.rs b/src/history.rs index f58ff9a9b3..1ac87cb05c 100644 --- a/src/history.rs +++ b/src/history.rs @@ -6,6 +6,12 @@ use std::fs::File; use super::Result; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Direction { + Forward, + Reverse, +} + pub struct History { entries: VecDeque, max_len: usize, @@ -129,20 +135,23 @@ impl History { /// Return the absolute index of the nearest history entry that matches `term`. /// Return None if no entry contains `term` between [start, len -1] for forward search /// or between [0, start] for reverse search. - pub fn search(&self, term: &str, start: usize, reverse: bool) -> Option { + pub fn search(&self, term: &str, start: usize, dir: Direction) -> Option { if term.is_empty() || start >= self.len() { return None; } - if reverse { - let index = self.entries - .iter() - .rev() - .skip(self.entries.len() - 1 - start) - .position(|entry| entry.contains(term)); - index.and_then(|index| Some(start - index)) - } else { - let index = self.entries.iter().skip(start).position(|entry| entry.contains(term)); - index.and_then(|index| Some(index + start)) + match dir { + Direction::Reverse => { + let index = self.entries + .iter() + .rev() + .skip(self.entries.len() - 1 - start) + .position(|entry| entry.contains(term)); + index.and_then(|index| Some(start - index)) + } + Direction::Forward => { + let index = self.entries.iter().skip(start).position(|entry| entry.contains(term)); + index.and_then(|index| Some(index + start)) + } } } } @@ -157,9 +166,10 @@ impl Default for History { mod tests { extern crate tempdir; use std::path::Path; + use super::{Direction, History}; - fn init() -> super::History { - let mut history = super::History::new(); + fn init() -> History { + let mut history = History::new(); assert!(history.add("line1")); assert!(history.add("line2")); assert!(history.add("line3")); @@ -168,14 +178,14 @@ mod tests { #[test] fn new() { - let history = super::History::new(); + let history = History::new(); assert_eq!(super::DEFAULT_HISTORY_MAX_LEN, history.max_len); assert_eq!(0, history.entries.len()); } #[test] fn add() { - let mut history = super::History::new(); + let mut history = History::new(); history.ignore_space(true); assert!(history.add("line1")); assert!(history.add("line2")); @@ -206,24 +216,24 @@ mod tests { #[test] fn search() { let history = init(); - assert_eq!(None, history.search("", 0, false)); - assert_eq!(None, history.search("none", 0, false)); - assert_eq!(None, history.search("line", 3, false)); + assert_eq!(None, history.search("", 0, Direction::Forward)); + assert_eq!(None, history.search("none", 0, Direction::Forward)); + assert_eq!(None, history.search("line", 3, Direction::Forward)); - assert_eq!(Some(0), history.search("line", 0, false)); - assert_eq!(Some(1), history.search("line", 1, false)); - assert_eq!(Some(2), history.search("line3", 1, false)); + assert_eq!(Some(0), history.search("line", 0, Direction::Forward)); + assert_eq!(Some(1), history.search("line", 1, Direction::Forward)); + assert_eq!(Some(2), history.search("line3", 1, Direction::Forward)); } #[test] fn reverse_search() { let history = init(); - assert_eq!(None, history.search("", 2, true)); - assert_eq!(None, history.search("none", 2, true)); - assert_eq!(None, history.search("line", 3, true)); + assert_eq!(None, history.search("", 2, Direction::Reverse)); + assert_eq!(None, history.search("none", 2, Direction::Reverse)); + assert_eq!(None, history.search("line", 3, Direction::Reverse)); - assert_eq!(Some(2), history.search("line", 2, true)); - assert_eq!(Some(1), history.search("line", 1, true)); - assert_eq!(Some(0), history.search("line1", 1, true)); + assert_eq!(Some(2), history.search("line", 2, Direction::Reverse)); + assert_eq!(Some(1), history.search("line", 1, Direction::Reverse)); + assert_eq!(Some(0), history.search("line1", 1, Direction::Reverse)); } } diff --git a/src/kill_ring.rs b/src/kill_ring.rs index f897597c65..cbbdbfbe72 100644 --- a/src/kill_ring.rs +++ b/src/kill_ring.rs @@ -7,6 +7,12 @@ enum Action { Other, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Mode { + Append, + Prepend, +} + pub struct KillRing { slots: Vec, index: usize, @@ -29,20 +35,19 @@ impl KillRing { } /// Add `text` to the kill-ring. - pub fn kill(&mut self, text: &str, forward: bool) { + pub fn kill(&mut self, text: &str, dir: Mode) { match self.last_action { Action::Kill => { if self.slots.capacity() == 0 { // disabled return; } - if forward { - // append - self.slots[self.index].push_str(text); - } else { - // prepend - self.slots[self.index] = String::from(text) + &self.slots[self.index]; - } + match dir { + Mode::Append => self.slots[self.index].push_str(text), + Mode::Prepend => { + self.slots[self.index] = String::from(text) + &self.slots[self.index] + } + }; } _ => { self.last_action = Action::Kill; @@ -99,12 +104,12 @@ impl KillRing { #[cfg(test)] mod tests { - use super::{Action, KillRing}; + use super::{Action, Mode, KillRing}; #[test] fn disabled() { let mut kill_ring = KillRing::new(0); - kill_ring.kill("text", true); + kill_ring.kill("text", Mode::Append); assert!(kill_ring.slots.is_empty()); assert_eq!(0, kill_ring.index); assert_eq!(Action::Kill, kill_ring.last_action); @@ -116,7 +121,7 @@ mod tests { #[test] fn one_kill() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); + kill_ring.kill("word1", Mode::Append); assert_eq!(0, kill_ring.index); assert_eq!(1, kill_ring.slots.len()); assert_eq!("word1", kill_ring.slots[0]); @@ -124,10 +129,10 @@ mod tests { } #[test] - fn kill_kill_forward() { + fn kill_append() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); - kill_ring.kill(" word2", true); + kill_ring.kill("word1", Mode::Append); + kill_ring.kill(" word2", Mode::Append); assert_eq!(0, kill_ring.index); assert_eq!(1, kill_ring.slots.len()); assert_eq!("word1 word2", kill_ring.slots[0]); @@ -135,10 +140,10 @@ mod tests { } #[test] - fn kill_kill_backward() { + fn kill_backward() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", false); - kill_ring.kill("word2 ", false); + kill_ring.kill("word1", Mode::Prepend); + kill_ring.kill("word2 ", Mode::Prepend); assert_eq!(0, kill_ring.index); assert_eq!(1, kill_ring.slots.len()); assert_eq!("word2 word1", kill_ring.slots[0]); @@ -148,9 +153,9 @@ mod tests { #[test] fn kill_other_kill() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); + kill_ring.kill("word1", Mode::Append); kill_ring.reset(); - kill_ring.kill("word2", true); + kill_ring.kill("word2", Mode::Append); assert_eq!(1, kill_ring.index); assert_eq!(2, kill_ring.slots.len()); assert_eq!("word1", kill_ring.slots[0]); @@ -161,13 +166,13 @@ mod tests { #[test] fn many_kill() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); + kill_ring.kill("word1", Mode::Append); kill_ring.reset(); - kill_ring.kill("word2", true); + kill_ring.kill("word2", Mode::Append); kill_ring.reset(); - kill_ring.kill("word3", true); + kill_ring.kill("word3", Mode::Append); kill_ring.reset(); - kill_ring.kill("word4", true); + kill_ring.kill("word4", Mode::Append); assert_eq!(1, kill_ring.index); assert_eq!(2, kill_ring.slots.len()); assert_eq!("word3", kill_ring.slots[0]); @@ -178,9 +183,9 @@ mod tests { #[test] fn yank() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); + kill_ring.kill("word1", Mode::Append); kill_ring.reset(); - kill_ring.kill("word2", true); + kill_ring.kill("word2", Mode::Append); assert_eq!(Some(&"word2".to_string()), kill_ring.yank()); assert_eq!(Action::Yank(5), kill_ring.last_action); @@ -191,9 +196,9 @@ mod tests { #[test] fn yank_pop() { let mut kill_ring = KillRing::new(2); - kill_ring.kill("word1", true); + kill_ring.kill("word1", Mode::Append); kill_ring.reset(); - kill_ring.kill("longword2", true); + kill_ring.kill("longword2", Mode::Append); assert_eq!(None, kill_ring.yank_pop()); kill_ring.yank(); diff --git a/src/lib.rs b/src/lib.rs index 27391ec27b..fa98978a5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,9 +50,9 @@ use nix::sys::signal; use encode_unicode::CharExt; use completion::Completer; use consts::KeyPress; -use history::History; +use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; -use kill_ring::KillRing; +use kill_ring::{Mode, KillRing}; /// The error type for I/O and Linux Syscalls (Errno) pub type Result = result::Result; @@ -590,7 +590,7 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, let mut search_buf = String::new(); let mut history_idx = history.len() - 1; - let mut reverse = true; + let mut direction = Direction::Reverse; let mut success = true; let mut key; @@ -614,7 +614,7 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, continue; } KeyPress::Ctrl('R') => { - reverse = true; + direction = Direction::Reverse; if history_idx > 0 { history_idx -= 1; } else { @@ -623,7 +623,7 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, } } KeyPress::Ctrl('S') => { - reverse = false; + direction = Direction::Forward; if history_idx < history.len() - 1 { history_idx += 1; } else { @@ -640,7 +640,7 @@ fn reverse_incremental_search(rdr: &mut tty::RawReader, _ => break, } } - success = match history.search(&search_buf, history_idx, reverse) { + success = match history.search(&search_buf, history_idx, direction) { Some(idx) => { history_idx = idx; let entry = history.get(idx).unwrap(); @@ -759,7 +759,7 @@ fn readline_edit(prompt: &str, KeyPress::Ctrl('K') => { // Kill the text from point to the end of the line. if let Some(text) = try!(edit_kill_line(&mut s)) { - kill_ring.kill(&text, true) + kill_ring.kill(&text, Mode::Append) } } KeyPress::Ctrl('L') => { @@ -787,7 +787,7 @@ fn readline_edit(prompt: &str, KeyPress::Ctrl('U') => { // Kill backward from point to the beginning of the line. if let Some(text) = try!(edit_discard_line(&mut s)) { - kill_ring.kill(&text, false) + kill_ring.kill(&text, Mode::Prepend) } } #[cfg(unix)] @@ -800,7 +800,7 @@ fn readline_edit(prompt: &str, KeyPress::Ctrl('W') => { // Kill the word behind point, using white space as a word boundary if let Some(text) = try!(edit_delete_prev_word(&mut s, char::is_whitespace)) { - kill_ring.kill(&text, false) + kill_ring.kill(&text, Mode::Prepend) } } KeyPress::Ctrl('Y') => { @@ -830,7 +830,7 @@ fn readline_edit(prompt: &str, // Kill from the cursor to the start of the current word, or, if between words, to the start of the previous word. if let Some(text) = try!(edit_delete_prev_word(&mut s, |ch| !ch.is_alphanumeric())) { - kill_ring.kill(&text, false) + kill_ring.kill(&text, Mode::Prepend) } } KeyPress::Meta('<') => { @@ -856,7 +856,7 @@ fn readline_edit(prompt: &str, KeyPress::Meta('D') => { // kill one word forward if let Some(text) = try!(edit_delete_word(&mut s)) { - kill_ring.kill(&text, true) + kill_ring.kill(&text, Mode::Append) } } KeyPress::Meta('F') => { From 2dcd5da3006e1f1b0848cebbb20f719b0fa8c727 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 18 Sep 2016 11:52:09 +0200 Subject: [PATCH 0146/1201] Revert terminfo --- Cargo.toml | 1 - examples/example.rs | 2 +- src/consts.rs | 8 +++++--- src/error.rs | 15 -------------- src/kill_ring.rs | 2 +- src/lib.rs | 20 +++++++----------- src/tty/unix.rs | 50 +++++++++++++++------------------------------ src/tty/windows.rs | 6 +++--- 8 files changed, 34 insertions(+), 70 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e49e3c0f19..3580e38803 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ unicode-width = "0.1.3" [target.'cfg(unix)'.dependencies] nix = "0.5.0" -term = "0.4" [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/examples/example.rs b/examples/example.rs index ff7f9b625b..8d49697fa6 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -19,7 +19,7 @@ fn main() { .completion_type(CompletionType::List) .build(); let c = FilenameCompleter::new(); - let mut rl = Editor::new(config).expect("Cannot create line editor"); + let mut rl = Editor::new(config); rl.set_completer(Some(c)); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); diff --git a/src/consts.rs b/src/consts.rs index 375afe632c..31cbead410 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -14,6 +14,8 @@ pub enum KeyPress { Left, Meta(char), Null, + PageDown, + PageUp, Right, Tab, // Ctrl('I') Up, @@ -33,12 +35,12 @@ pub fn char_to_key_press(c: char) -> KeyPress { '\x05' => KeyPress::Ctrl('E'), '\x06' => KeyPress::Ctrl('F'), '\x07' => KeyPress::Ctrl('G'), - '\x08' => KeyPress::Backspace, + '\x08' => KeyPress::Backspace, // '\b' '\x09' => KeyPress::Tab, - '\x0a' => KeyPress::Ctrl('J'), + '\x0a' => KeyPress::Ctrl('J'), // '\n' (10) '\x0b' => KeyPress::Ctrl('K'), '\x0c' => KeyPress::Ctrl('L'), - '\x0d' => KeyPress::Enter, + '\x0d' => KeyPress::Enter, // '\r' (13) '\x0e' => KeyPress::Ctrl('N'), '\x10' => KeyPress::Ctrl('P'), '\x12' => KeyPress::Ctrl('R'), diff --git a/src/error.rs b/src/error.rs index 1a4d92940b..3df6e70e3f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,8 +6,6 @@ use std::error; use std::fmt; #[cfg(unix)] use nix; -#[cfg(unix)] -use term; /// The error type for Rustyline errors that can arise from /// I/O related errors or Errno when using the nix-rust library @@ -25,8 +23,6 @@ pub enum ReadlineError { /// Unix Error from syscall #[cfg(unix)] Errno(nix::Error), - #[cfg(unix)] - TermError(term::Error), #[cfg(windows)] WindowResize, #[cfg(windows)] @@ -43,8 +39,6 @@ impl fmt::Display for ReadlineError { ReadlineError::Char(ref err) => err.fmt(f), #[cfg(unix)] ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()), - #[cfg(unix)] - ReadlineError::TermError(ref err) => err.fmt(f), #[cfg(windows)] ReadlineError::WindowResize => write!(f, "WindowResize"), #[cfg(windows)] @@ -63,8 +57,6 @@ impl error::Error for ReadlineError { ReadlineError::Char(ref err) => err.description(), #[cfg(unix)] ReadlineError::Errno(ref err) => err.errno().desc(), - #[cfg(unix)] - ReadlineError::TermError(ref err) => err.description(), #[cfg(windows)] ReadlineError::WindowResize => "WindowResize", #[cfg(windows)] @@ -93,13 +85,6 @@ impl From for ReadlineError { } } -#[cfg(unix)] -impl From for ReadlineError { - fn from(err: term::Error) -> ReadlineError { - ReadlineError::TermError(err) - } -} - #[cfg(windows)] impl From for ReadlineError { fn from(err: char::DecodeUtf16Error) -> ReadlineError { diff --git a/src/kill_ring.rs b/src/kill_ring.rs index 7575c3025b..972466ea5c 100644 --- a/src/kill_ring.rs +++ b/src/kill_ring.rs @@ -129,7 +129,7 @@ mod tests { } #[test] - fn kill_kill_Append() { + fn kill_kill_append() { let mut kill_ring = KillRing::new(2); kill_ring.kill("word1", Mode::Append); kill_ring.kill(" word2", Mode::Append); diff --git a/src/lib.rs b/src/lib.rs index c4f25994fa..21b2e2a63c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! //! ``` //! let config = rustyline::Config::default(); -//! let mut rl = rustyline::Editor::<()>::new(config).expect("Cannot create line editor"); +//! let mut rl = rustyline::Editor::<()>::new(config); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -22,8 +22,6 @@ extern crate libc; #[cfg(unix)] extern crate nix; -#[cfg(unix)] -extern crate term; extern crate unicode_width; #[cfg(windows)] extern crate winapi; @@ -1057,15 +1055,15 @@ pub struct Editor { } impl Editor { - pub fn new(config: Config) -> Result> { - let term = try!(Terminal::new()); - Ok(Editor { + pub fn new(config: Config) -> Editor { + let term = Terminal::new(); + Editor { term: term, history: History::new(config), completer: None, kill_ring: KillRing::new(60), config: config, - }) + } } /// This method will read a line from STDIN and will display a `prompt` @@ -1122,10 +1120,7 @@ impl fmt::Debug for Editor { #[cfg(all(unix,test))] mod test { - use std::cell::RefCell; - use std::collections::HashMap; use std::io::Write; - use std::rc::Rc; use line_buffer::LineBuffer; use history::History; use completion::Completer; @@ -1139,7 +1134,7 @@ mod test { pos: usize, cols: usize) -> State<'out, 'static> { - let term = Terminal::new().unwrap(); + let term = Terminal::new(); State { out: out, prompt: "", @@ -1207,8 +1202,7 @@ mod test { let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); let input = b"\n"; - let terminfo_keys = Rc::new(RefCell::new(HashMap::new())); - let mut rdr = RawReader::new(&input[..], terminfo_keys).unwrap(); + let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; let key = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); diff --git a/src/tty/unix.rs b/src/tty/unix.rs index b4f0ac84ba..53ca5c6bfa 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,8 +1,5 @@ use std; -use std::cell::RefCell; -use std::collections::HashMap; use std::io::{Chars, Read, Write}; -use std::rc::Rc; use std::sync; use std::sync::atomic; use libc; @@ -126,17 +123,11 @@ fn clear_screen(w: &mut Write) -> Result<()> { /// Console input reader pub struct RawReader { chars: Chars, - terminfo_keys: Rc, String>>>, } impl RawReader { - pub fn new(stdin: R, - terminfo_keys: Rc, String>>>) - -> Result> { - Ok(RawReader { - chars: stdin.chars(), - terminfo_keys: terminfo_keys, - }) + pub fn new(stdin: R) -> Result> { + Ok(RawReader { chars: stdin.chars() }) } // As there is no read timeout to properly handle single ESC key, @@ -170,9 +161,13 @@ impl RawReader { let seq3 = try!(self.next_char()); if seq3 == '~' { match seq2 { + // '1' => Ok(KeyPress::Home), '3' => Ok(KeyPress::Delete), - // TODO '1' // Home - // TODO '4' // End + // '4' => Ok(KeyPress::End), + '5' => Ok(KeyPress::PageUp), + '6' => Ok(KeyPress::PageDown), + '7' => Ok(KeyPress::Home), + '8' => Ok(KeyPress::End), _ => Ok(KeyPress::UnknownEscSeq), } } else { @@ -180,7 +175,7 @@ impl RawReader { } } else { match seq2 { - 'A' => Ok(KeyPress::Up), + 'A' => Ok(KeyPress::Up), // ANSI 'B' => Ok(KeyPress::Down), 'C' => Ok(KeyPress::Right), 'D' => Ok(KeyPress::Left), @@ -193,6 +188,10 @@ impl RawReader { // ESC O sequences. let seq2 = try!(self.next_char()); match seq2 { + 'A' => Ok(KeyPress::Up), + 'B' => Ok(KeyPress::Down), + 'C' => Ok(KeyPress::Right), + 'D' => Ok(KeyPress::Left), 'F' => Ok(KeyPress::End), 'H' => Ok(KeyPress::Home), _ => Ok(KeyPress::UnknownEscSeq), @@ -245,21 +244,18 @@ pub type Terminal = PosixTerminal; pub struct PosixTerminal { unsupported: bool, stdin_isatty: bool, - terminfo_keys: Rc, String>>>, } impl PosixTerminal { - pub fn new() -> Result { - let mut term = PosixTerminal { + pub fn new() -> PosixTerminal { + let term = PosixTerminal { unsupported: is_unsupported_term(), stdin_isatty: is_a_tty(STDIN_FILENO), - terminfo_keys: Rc::new(RefCell::new(HashMap::new())), }; if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) { install_sigwinch_handler(); - try!(term.load_capabilities()); } - Ok(term) + term } // Init checks: @@ -274,18 +270,6 @@ impl PosixTerminal { self.stdin_isatty } - // Init if terminal-style mode: - - fn load_capabilities(&mut self) -> Result<()> { - use term::terminfo::TermInfo; - let term_info = try!(TermInfo::from_env()); - let mut terminfo_keys = self.terminfo_keys.borrow_mut(); - for (key, val) in term_info.strings.into_iter() { - terminfo_keys.insert(val.clone(), key.clone()); - } - Ok(()) - } - // Interactive loop: /// Get the number of columns in the current terminal. @@ -300,7 +284,7 @@ impl PosixTerminal { /// Create a RAW reader pub fn create_reader(&self) -> Result> { - RawReader::new(std::io::stdin(), self.terminfo_keys.clone()) + RawReader::new(std::io::stdin()) } /// Check if a SIGWINCH signal has been received diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 5f83d34b9f..310ed12ca9 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -232,13 +232,13 @@ pub struct Console { } impl Console { - pub fn new() -> Result { + pub fn new() -> Console { use std::ptr; let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); - Ok(Console { + Console { stdin_isatty: is_a_tty(STDIN_FILENO), stdout_handle: stdout_handle, - }) + } } /// Checking for an unsupported TERM in windows is a no-op From 7efa96a57b1ffd2990b621a0439e355b1c6ebf2d Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 9 Sep 2016 20:47:03 +0200 Subject: [PATCH 0147/1201] Refactor before loading terminfo capabilities. --- src/lib.rs | 87 ++++++++++++++------------------ src/tty/mod.rs | 1 - src/tty/unix.rs | 102 ++++++++++++++++++++++++++++++------- src/tty/windows.rs | 122 +++++++++++++++++++++++++++++++++++---------- 4 files changed, 219 insertions(+), 93 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 214aff1de6..851beaba90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,17 +37,18 @@ pub mod line_buffer; mod char_iter; pub mod config; -#[macro_use] mod tty; +use std::cell::RefCell; use std::fmt; use std::io::{self, Read, Write}; use std::mem; use std::path::Path; +use std::rc::Rc; use std::result; -use std::sync::atomic; #[cfg(unix)] use nix::sys::signal; +use tty::Terminal; use encode_unicode::CharExt; use completion::{Completer, longest_common_prefix}; @@ -71,7 +72,7 @@ struct State<'out, 'prompt> { old_rows: usize, // Number of rows used so far (from start of prompt to end of input) history_index: usize, // The history index we are currently editing snapshot: LineBuffer, // Current edited line before history browsing/completion - output_handle: tty::Handle, // output handle (for windows) + term: Rc>, // terminal } #[derive(Copy, Clone, Debug, Default)] @@ -82,12 +83,12 @@ struct Position { impl<'out, 'prompt> State<'out, 'prompt> { fn new(out: &'out mut Write, - output_handle: tty::Handle, + term: Rc>, prompt: &'prompt str, history_index: usize) -> State<'out, 'prompt> { let capacity = MAX_LINE; - let cols = tty::get_columns(output_handle); + let cols = term.borrow().get_columns(); let prompt_size = calculate_position(prompt, Position::default(), cols); State { out: out, @@ -99,7 +100,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { old_rows: prompt_size.row, history_index: history_index, snapshot: LineBuffer::with_capacity(capacity), - output_handle: output_handle, + term: term, } } @@ -175,24 +176,21 @@ impl<'out, 'prompt> State<'out, 'prompt> { #[cfg(windows)] fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> { - let handle = self.output_handle; // calculate the position of the end of the input line let end_pos = calculate_position(&self.line, prompt_size, self.cols); // calculate the desired position of the cursor let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols); + let mut term = self.term.borrow_mut(); // position at the start of the prompt, clear to end of previous input - let mut info = unsafe { mem::zeroed() }; - check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); + let mut info = try!(term.get_console_screen_buffer_info()); info.dwCursorPosition.X = 0; info.dwCursorPosition.Y -= self.cursor.row as i16; - check!(kernel32::SetConsoleCursorPosition(handle, info.dwCursorPosition)); + try!(term.set_console_cursor_position(info.dwCursorPosition)); let mut _count = 0; - check!(kernel32::FillConsoleOutputCharacterA(handle, - ' ' as winapi::CHAR, - (info.dwSize.X * (self.old_rows as i16 +1)) as winapi::DWORD, - info.dwCursorPosition, - &mut _count)); + try!(term.fill_console_output_character( + (info.dwSize.X * (self.old_rows as i16 +1)) as u32, + info.dwCursorPosition)); let mut ab = String::new(); // display the prompt ab.push_str(prompt); // TODO handle ansi escape code (SetConsoleTextAttribute) @@ -201,10 +199,10 @@ impl<'out, 'prompt> State<'out, 'prompt> { try!(write_and_flush(self.out, ab.as_bytes())); // position the cursor - check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); + let mut info = try!(term.get_console_screen_buffer_info()); info.dwCursorPosition.X = cursor.col as i16; info.dwCursorPosition.Y -= (end_pos.row - cursor.row) as i16; - check!(kernel32::SetConsoleCursorPosition(handle, info.dwCursorPosition)); + try!(term.set_console_cursor_position(info.dwCursorPosition)); self.cursor = cursor; self.old_rows = end_pos.row; @@ -213,7 +211,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { } fn update_columns(&mut self) { - self.cols = tty::get_columns(self.output_handle); + self.cols = self.term.borrow().get_columns(); } } @@ -646,7 +644,7 @@ fn page_completions(rdr: &mut tty::RawReader, .unwrap() + min_col_pad); let num_cols = s.cols / max_width; - let mut pause_row = tty::get_rows(s.output_handle) - 1; + let mut pause_row = s.term.borrow().get_rows() - 1; let num_rows = (candidates.len() + num_cols - 1) / num_cols; let mut ab = String::new(); for row in 0..num_rows { @@ -665,7 +663,7 @@ fn page_completions(rdr: &mut tty::RawReader, KeyPress::Char('y') | KeyPress::Char('Y') | KeyPress::Char(' ') => { - pause_row += tty::get_rows(s.output_handle) - 1; + pause_row += s.term.borrow().get_rows() - 1; } KeyPress::Enter => { pause_row += 1; @@ -785,17 +783,16 @@ fn readline_edit(prompt: &str, let completer = editor.completer.as_ref().map(|c| c as &Completer); let mut stdout = io::stdout(); - let stdout_handle = try!(tty::stdout_handle()); editor.kill_ring.reset(); - let mut s = State::new(&mut stdout, stdout_handle, prompt, editor.history.len()); + let mut s = State::new(&mut stdout, editor.term.clone(), prompt, editor.history.len()); try!(s.refresh_line()); - let mut rdr = try!(tty::RawReader::new(io::stdin())); + let mut rdr = try!(s.term.borrow().create_reader()); loop { let rk = rdr.next_key(true); - if rk.is_err() && tty::SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) { + if rk.is_err() && s.term.borrow().sigwinch() { s.update_columns(); try!(s.refresh_line()); continue; @@ -884,7 +881,7 @@ fn readline_edit(prompt: &str, } KeyPress::Ctrl('L') => { // Clear the screen leaving the current line at the top of the screen. - try!(tty::clear_screen(&mut s.out, s.output_handle)); + try!(s.term.borrow_mut().clear_screen(&mut s.out)); try!(s.refresh_line()) } KeyPress::Ctrl('N') | @@ -931,9 +928,9 @@ fn readline_edit(prompt: &str, } #[cfg(unix)] KeyPress::Ctrl('Z') => { - try!(tty::disable_raw_mode(original_mode)); + try!(s.term.borrow_mut().disable_raw_mode(original_mode)); try!(signal::raise(signal::SIGSTOP)); - try!(tty::enable_raw_mode()); // TODO original_mode may have changed + try!(s.term.borrow_mut().enable_raw_mode()); // TODO original_mode may have changed try!(s.refresh_line()) } // TODO CTRL-_ // undo @@ -1050,9 +1047,7 @@ fn readline_direct() -> Result { /// Line editor pub struct Editor { - unsupported_term: bool, - stdin_isatty: bool, - stdout_isatty: bool, + term: Rc>, history: History, completer: Option, kill_ring: KillRing, @@ -1061,30 +1056,25 @@ pub struct Editor { impl Editor { pub fn new(config: Config) -> Editor { - let editor = Editor { - unsupported_term: tty::is_unsupported_term(), - stdin_isatty: tty::is_a_tty(tty::STDIN_FILENO), - stdout_isatty: tty::is_a_tty(tty::STDOUT_FILENO), + let term = Rc::new(RefCell::new(Terminal::new())); + Editor { + term: term, history: History::new(config), completer: None, kill_ring: KillRing::new(60), config: config, - }; - if !editor.unsupported_term && editor.stdin_isatty && editor.stdout_isatty { - tty::install_sigwinch_handler(); } - editor } /// This method will read a line from STDIN and will display a `prompt` pub fn readline(&mut self, prompt: &str) -> Result { - if self.unsupported_term { + if self.term.borrow().is_unsupported() { // Write prompt and flush it to stdout let mut stdout = io::stdout(); try!(write_and_flush(&mut stdout, prompt.as_bytes())); readline_direct() - } else if !self.stdin_isatty { + } else if !self.term.borrow().is_stdin_tty() { // Not a tty: read from file / pipe. readline_direct() } else { @@ -1121,33 +1111,32 @@ impl Editor { impl fmt::Debug for Editor { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("State") - .field("unsupported_term", &self.unsupported_term) - .field("stdin_isatty", &self.stdin_isatty) + f.debug_struct("Editor") + .field("term", &self.term) + .field("config", &self.config) .finish() } } #[cfg(all(unix,test))] mod test { + use std::cell::RefCell; use std::io::Write; + use std::rc::Rc; use line_buffer::LineBuffer; use history::History; use completion::Completer; use config::Config; use {Position, State}; use super::Result; - use tty::Handle; - - fn default_handle() -> Handle { - () - } + use tty::Terminal; fn init_state<'out>(out: &'out mut Write, line: &str, pos: usize, cols: usize) -> State<'out, 'static> { + let term = Rc::new(RefCell::new(Terminal::new())); State { out: out, prompt: "", @@ -1158,7 +1147,7 @@ mod test { old_rows: 0, history_index: 0, snapshot: LineBuffer::with_capacity(100), - output_handle: default_handle(), + term: term, } } diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 22c6793842..82e2ed670c 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -2,7 +2,6 @@ // If on Windows platform import Windows TTY module // and re-export into mod.rs scope -#[macro_use] #[cfg(windows)] mod windows; #[cfg(windows)] diff --git a/src/tty/unix.rs b/src/tty/unix.rs index ff1d0b31d8..a08029a519 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -12,10 +12,9 @@ use consts::{self, KeyPress}; use ::Result; use ::error; -pub type Handle = (); pub type Mode = termios::Termios; -pub const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; -pub const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; +const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; +const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; /// Unsupported Terminals that don't support RAW mode static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"]; @@ -31,14 +30,14 @@ const TIOCGWINSZ: libc::c_int = 0x5413; /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. -pub fn get_columns(_: Handle) -> usize { +fn get_columns() -> usize { let (cols, _) = get_win_size(); cols } /// Try to get the number of rows in the current terminal, /// or assume 24 if it fails. -pub fn get_rows(_: Handle) -> usize { +fn get_rows() -> usize { let (_, rows) = get_win_size(); rows } @@ -66,7 +65,7 @@ fn get_win_size() -> (usize, usize) { /// Check TERM environment variable to see if current term is in our /// unsupported list -pub fn is_unsupported_term() -> bool { +fn is_unsupported_term() -> bool { use std::ascii::AsciiExt; match std::env::var("TERM") { Ok(term) => { @@ -82,11 +81,10 @@ pub fn is_unsupported_term() -> bool { /// Return whether or not STDIN, STDOUT or STDERR is a TTY -pub fn is_a_tty(fd: libc::c_int) -> bool { +fn is_a_tty(fd: libc::c_int) -> bool { unsafe { libc::isatty(fd) != 0 } } -/// Enable raw mode for the TERM pub fn enable_raw_mode() -> Result { use nix::errno::Errno::ENOTTY; use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, @@ -110,18 +108,12 @@ pub fn enable_raw_mode() -> Result { Ok(original_mode) } -/// Disable Raw mode for the term pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &original_mode)); Ok(()) } -pub fn stdout_handle() -> Result { - Ok(()) -} - -/// Clear the screen. Used to handle ctrl+l -pub fn clear_screen(w: &mut Write, _: Handle) -> Result<()> { +fn clear_screen(w: &mut Write) -> Result<()> { try!(w.write_all(b"\x1b[H\x1b[2J")); try!(w.flush()); Ok(()) @@ -222,9 +214,9 @@ impl RawReader { } static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT; -pub static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; +static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; -pub fn install_sigwinch_handler() { +fn install_sigwinch_handler() { SIGWINCH_ONCE.call_once(|| unsafe { let sigwinch = signal::SigAction::new(signal::SigHandler::Handler(sigwinch_handler), signal::SaFlag::empty(), @@ -236,3 +228,79 @@ pub fn install_sigwinch_handler() { extern "C" fn sigwinch_handler(_: signal::SigNum) { SIGWINCH.store(true, atomic::Ordering::SeqCst); } + +pub type Terminal = PosixTerminal; + +#[derive(Debug)] +pub struct PosixTerminal { + unsupported: bool, + stdin_isatty: bool, +} + +impl PosixTerminal { + pub fn new() -> PosixTerminal { + let term = PosixTerminal { + unsupported: is_unsupported_term(), + stdin_isatty: is_a_tty(STDIN_FILENO), + }; + if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) { + install_sigwinch_handler(); + } + term + } + + // Init checks: + + /// Check if current terminal can provide a rich line-editing user interface. + pub fn is_unsupported(&self) -> bool { + self.unsupported + } + + /// check if stdin is connected to a terminal. + pub fn is_stdin_tty(&self) -> bool { + self.stdin_isatty + } + + // Init if terminal-style mode: + + // pub fn load_capabilities(&mut self) -> Result<()> { + // Ok(()) + // } + + // Interactive loop: + + /// Enable RAW mode for the terminal. + pub fn enable_raw_mode(&mut self) -> Result { + enable_raw_mode() + } + + /// Disable RAW mode for the terminal. + pub fn disable_raw_mode(&mut self, mode: Mode) -> Result<()> { + disable_raw_mode(mode) + } + + /// Get the number of columns in the current terminal. + pub fn get_columns(&self) -> usize { + get_columns() + } + + /// Get the number of rows in the current terminal. + pub fn get_rows(&self) -> usize { + get_rows() + } + + /// Create a RAW reader + pub fn create_reader(&self) -> Result> { + RawReader::new(std::io::stdin()) + } + + /// Check if a SIGWINCH signal has been received + pub fn sigwinch(&self) -> bool { + SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) + } + + /// Clear the screen. Used to handle ctrl+l + pub fn clear_screen(&mut self, w: &mut Write) -> Result<()> { + clear_screen(w) + } +} diff --git a/src/tty/windows.rs b/src/tty/windows.rs index f1b0958753..e0e62d04e3 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -11,10 +11,9 @@ use consts::{self, KeyPress}; use ::error; use ::Result; -pub type Handle = winapi::HANDLE; pub type Mode = winapi::DWORD; -pub const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; -pub const STDOUT_FILENO: winapi::DWORD = winapi::STD_OUTPUT_HANDLE; +const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; +const STDOUT_FILENO: winapi::DWORD = winapi::STD_OUTPUT_HANDLE; fn get_std_handle(fd: winapi::DWORD) -> Result { let handle = unsafe { kernel32::GetStdHandle(fd) }; @@ -40,21 +39,17 @@ macro_rules! check { }; } -/// Try to get the number of columns in the current terminal, -/// or assume 80 if it fails. -pub fn get_columns(handle: Handle) -> usize { +fn get_columns(handle: winapi::HANDLE) -> usize { let (cols, _) = get_win_size(handle); cols } -/// Try to get the number of rows in the current terminal, -/// or assume 24 if it fails. -pub fn get_rows(handle: Handle) -> usize { +fn get_rows(handle: winapi::HANDLE) -> usize { let (_, rows) = get_win_size(handle); rows } -fn get_win_size(handle: Handle) -> (usize, usize) { +fn get_win_size(handle: winapi::HANDLE) -> (usize, usize) { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { 0 => (80, 24), @@ -62,11 +57,6 @@ fn get_win_size(handle: Handle) -> (usize, usize) { } } -/// Checking for an unsupported TERM in windows is a no-op -pub fn is_unsupported_term() -> bool { - false -} - fn get_console_mode(handle: winapi::HANDLE) -> Result { let mut original_mode = 0; check!(kernel32::GetConsoleMode(handle, &mut original_mode)); @@ -74,7 +64,7 @@ fn get_console_mode(handle: winapi::HANDLE) -> Result { } /// Return whether or not STDIN, STDOUT or STDERR is a TTY -pub fn is_a_tty(fd: winapi::DWORD) -> bool { +fn is_a_tty(fd: winapi::DWORD) -> bool { let handle = get_std_handle(fd); match handle { Ok(handle) => { @@ -85,7 +75,6 @@ pub fn is_a_tty(fd: winapi::DWORD) -> bool { } } -/// Enable raw mode for the TERM pub fn enable_raw_mode() -> Result { let handle = try!(get_std_handle(STDIN_FILENO)); let original_mode = try!(get_console_mode(handle)); @@ -102,20 +91,14 @@ pub fn enable_raw_mode() -> Result { Ok(original_mode) } -/// Disable Raw mode for the term pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { let handle = try!(get_std_handle(STDIN_FILENO)); check!(kernel32::SetConsoleMode(handle, original_mode)); Ok(()) } -pub fn stdout_handle() -> Result { - let handle = try!(get_std_handle(STDOUT_FILENO)); - Ok(handle) -} - /// Clear the screen. Used to handle ctrl+l -pub fn clear_screen(_: &mut Write, handle: Handle) -> Result<()> { +fn clear_screen(_: &mut Write, handle: winapi::HANDLE) -> Result<()> { let mut info = unsafe { mem::zeroed() }; check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); let coord = winapi::COORD { X: 0, Y: 0 }; @@ -236,8 +219,95 @@ impl Iterator for RawReader { } } -pub static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; +static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; + +pub type Terminal = Console; + +#[derive(Debug)] +pub struct Console { + stdin_isatty: bool, + stdout_handle: winapi::HANDLE, +} + +impl Console { + pub fn new() -> Console { + use std::ptr; + let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); + Console { + stdin_isatty: is_a_tty(STDIN_FILENO), + stdout_handle: stdout_handle, + } + } + + /// Checking for an unsupported TERM in windows is a no-op + pub fn is_unsupported(&self) -> bool { + false + } + + pub fn is_stdin_tty(&self) -> bool { + self.stdin_isatty + } -pub fn install_sigwinch_handler() { + // pub fn install_sigwinch_handler(&mut self) { // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT + // } + + pub fn load_capabilities(&mut self) -> Result<()> { + Ok(()) + } + + /// Enable raw mode for the TERM + pub fn enable_raw_mode(&mut self) -> Result { + enable_raw_mode() + } + + /// Disable Raw mode for the term + pub fn disable_raw_mode(&mut self, mode: Mode) -> Result<()> { + disable_raw_mode(mode) + } + + /// Try to get the number of columns in the current terminal, + /// or assume 80 if it fails. + pub fn get_columns(&self) -> usize { + get_columns(self.stdout_handle) + } + + /// Try to get the number of rows in the current terminal, + /// or assume 24 if it fails. + pub fn get_rows(&self) -> usize { + get_rows(self.stdout_handle) + } + + pub fn create_reader(&self) -> Result> { + RawReader::new(io::stdin()) // FIXME + } + + pub fn sigwinch(&self) -> bool { + SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) + } + + pub fn clear_screen(&mut self, w: &mut Write) -> Result<()> { + clear_screen(w, self.stdout_handle) + } + + pub fn get_console_screen_buffer_info(&self) -> Result { + let mut info = unsafe { mem::zeroed() }; + check!(kernel32::GetConsoleScreenBufferInfo(self.stdout_handle, &mut info)); + Ok(info) + } + + pub fn set_console_cursor_position(&mut self, pos: winapi::COORD) -> Result<()> { + check!(kernel32::SetConsoleCursorPosition(self.stdout_handle, pos)); + Ok(()) + } + + pub fn fill_console_output_character(&mut self, length: winapi::DWORD, pos: winapi::COORD) -> Result<()> { + let mut _count = 0; + check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle, + ' ' as winapi::CHAR, + length, + pos, + &mut _count)); + Ok(()) + } } From 462bc0898d7ee085574abacbe9c8e5b8d478818b Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 11 Sep 2016 15:42:36 +0200 Subject: [PATCH 0148/1201] Load TermInfo --- Cargo.toml | 1 + examples/example.rs | 2 +- rustfmt.toml | 4 ++- src/error.rs | 15 ++++++++++ src/lib.rs | 68 ++++++++++++++++++++++++--------------------- src/tty/unix.rs | 50 ++++++++++++++++++++------------- src/tty/windows.rs | 37 ++++++++++-------------- 7 files changed, 100 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9b09d189bd..9b35ed8a63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ encode_unicode = "0.1.3" [target.'cfg(unix)'.dependencies] nix = "0.5.0" +term = "0.4" [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/examples/example.rs b/examples/example.rs index 8d49697fa6..ff7f9b625b 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -19,7 +19,7 @@ fn main() { .completion_type(CompletionType::List) .build(); let c = FilenameCompleter::new(); - let mut rl = Editor::new(config); + let mut rl = Editor::new(config).expect("Cannot create line editor"); rl.set_completer(Some(c)); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); diff --git a/rustfmt.toml b/rustfmt.toml index 44148a2d3c..6496add0b7 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,3 @@ -reorder_imports = true +reorder_imports = false +normalise_comments = false +write_mode = "Overwrite" \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 009ec65f1d..e22323a60c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,8 @@ use std::error; use std::fmt; #[cfg(unix)] use nix; +#[cfg(unix)] +use term; #[cfg(unix)] use char_iter; @@ -26,6 +28,8 @@ pub enum ReadlineError { /// Unix Error from syscall #[cfg(unix)] Errno(nix::Error), + #[cfg(unix)] + TermError(term::Error), #[cfg(windows)] WindowResize, #[cfg(windows)] @@ -42,6 +46,8 @@ impl fmt::Display for ReadlineError { ReadlineError::Char(ref err) => err.fmt(f), #[cfg(unix)] ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()), + #[cfg(unix)] + ReadlineError::TermError(ref err) => err.fmt(f), #[cfg(windows)] ReadlineError::WindowResize => write!(f, "WindowResize"), #[cfg(windows)] @@ -60,6 +66,8 @@ impl error::Error for ReadlineError { ReadlineError::Char(ref err) => err.description(), #[cfg(unix)] ReadlineError::Errno(ref err) => err.errno().desc(), + #[cfg(unix)] + ReadlineError::TermError(ref err) => err.description(), #[cfg(windows)] ReadlineError::WindowResize => "WindowResize", #[cfg(windows)] @@ -88,6 +96,13 @@ impl From for ReadlineError { } } +#[cfg(unix)] +impl From for ReadlineError { + fn from(err: term::Error) -> ReadlineError { + ReadlineError::TermError(err) + } +} + #[cfg(windows)] impl From for ReadlineError { fn from(err: char::DecodeUtf16Error) -> ReadlineError { diff --git a/src/lib.rs b/src/lib.rs index 851beaba90..54dab5676c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! //! ``` //! let config = rustyline::Config::default(); -//! let mut rl = rustyline::Editor::<()>::new(config); +//! let mut rl = rustyline::Editor::<()>::new(config).expect("Cannot create line editor"); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -20,6 +20,8 @@ extern crate libc; #[cfg(unix)] extern crate nix; +#[cfg(unix)] +extern crate term; extern crate unicode_width; extern crate encode_unicode; #[cfg(windows)] @@ -39,12 +41,10 @@ pub mod config; mod tty; -use std::cell::RefCell; use std::fmt; use std::io::{self, Read, Write}; use std::mem; use std::path::Path; -use std::rc::Rc; use std::result; #[cfg(unix)] use nix::sys::signal; @@ -72,7 +72,7 @@ struct State<'out, 'prompt> { old_rows: usize, // Number of rows used so far (from start of prompt to end of input) history_index: usize, // The history index we are currently editing snapshot: LineBuffer, // Current edited line before history browsing/completion - term: Rc>, // terminal + term: Terminal, // terminal } #[derive(Copy, Clone, Debug, Default)] @@ -83,12 +83,12 @@ struct Position { impl<'out, 'prompt> State<'out, 'prompt> { fn new(out: &'out mut Write, - term: Rc>, + term: Terminal, prompt: &'prompt str, history_index: usize) -> State<'out, 'prompt> { let capacity = MAX_LINE; - let cols = term.borrow().get_columns(); + let cols = term.get_columns(); let prompt_size = calculate_position(prompt, Position::default(), cols); State { out: out, @@ -181,16 +181,15 @@ impl<'out, 'prompt> State<'out, 'prompt> { // calculate the desired position of the cursor let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols); - let mut term = self.term.borrow_mut(); // position at the start of the prompt, clear to end of previous input - let mut info = try!(term.get_console_screen_buffer_info()); + let mut info = try!(self.term.get_console_screen_buffer_info()); info.dwCursorPosition.X = 0; info.dwCursorPosition.Y -= self.cursor.row as i16; - try!(term.set_console_cursor_position(info.dwCursorPosition)); + try!(self.term.set_console_cursor_position(info.dwCursorPosition)); let mut _count = 0; - try!(term.fill_console_output_character( - (info.dwSize.X * (self.old_rows as i16 +1)) as u32, - info.dwCursorPosition)); + try!(self.term + .fill_console_output_character((info.dwSize.X * (self.old_rows as i16 + 1)) as u32, + info.dwCursorPosition)); let mut ab = String::new(); // display the prompt ab.push_str(prompt); // TODO handle ansi escape code (SetConsoleTextAttribute) @@ -199,10 +198,10 @@ impl<'out, 'prompt> State<'out, 'prompt> { try!(write_and_flush(self.out, ab.as_bytes())); // position the cursor - let mut info = try!(term.get_console_screen_buffer_info()); + let mut info = try!(self.term.get_console_screen_buffer_info()); info.dwCursorPosition.X = cursor.col as i16; info.dwCursorPosition.Y -= (end_pos.row - cursor.row) as i16; - try!(term.set_console_cursor_position(info.dwCursorPosition)); + try!(self.term.set_console_cursor_position(info.dwCursorPosition)); self.cursor = cursor; self.old_rows = end_pos.row; @@ -211,7 +210,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { } fn update_columns(&mut self) { - self.cols = self.term.borrow().get_columns(); + self.cols = self.term.get_columns(); } } @@ -644,7 +643,7 @@ fn page_completions(rdr: &mut tty::RawReader, .unwrap() + min_col_pad); let num_cols = s.cols / max_width; - let mut pause_row = s.term.borrow().get_rows() - 1; + let mut pause_row = s.term.get_rows() - 1; let num_rows = (candidates.len() + num_cols - 1) / num_cols; let mut ab = String::new(); for row in 0..num_rows { @@ -663,7 +662,7 @@ fn page_completions(rdr: &mut tty::RawReader, KeyPress::Char('y') | KeyPress::Char('Y') | KeyPress::Char(' ') => { - pause_row += s.term.borrow().get_rows() - 1; + pause_row += s.term.get_rows() - 1; } KeyPress::Enter => { pause_row += 1; @@ -785,14 +784,17 @@ fn readline_edit(prompt: &str, let mut stdout = io::stdout(); editor.kill_ring.reset(); - let mut s = State::new(&mut stdout, editor.term.clone(), prompt, editor.history.len()); + let mut s = State::new(&mut stdout, + editor.term.clone(), + prompt, + editor.history.len()); try!(s.refresh_line()); - let mut rdr = try!(s.term.borrow().create_reader()); + let mut rdr = try!(s.term.create_reader()); loop { let rk = rdr.next_key(true); - if rk.is_err() && s.term.borrow().sigwinch() { + if rk.is_err() && s.term.sigwinch() { s.update_columns(); try!(s.refresh_line()); continue; @@ -881,7 +883,7 @@ fn readline_edit(prompt: &str, } KeyPress::Ctrl('L') => { // Clear the screen leaving the current line at the top of the screen. - try!(s.term.borrow_mut().clear_screen(&mut s.out)); + try!(s.term.clear_screen(&mut s.out)); try!(s.refresh_line()) } KeyPress::Ctrl('N') | @@ -928,9 +930,9 @@ fn readline_edit(prompt: &str, } #[cfg(unix)] KeyPress::Ctrl('Z') => { - try!(s.term.borrow_mut().disable_raw_mode(original_mode)); + try!(tty::disable_raw_mode(original_mode)); try!(signal::raise(signal::SIGSTOP)); - try!(s.term.borrow_mut().enable_raw_mode()); // TODO original_mode may have changed + try!(tty::enable_raw_mode()); // TODO original_mode may have changed try!(s.refresh_line()) } // TODO CTRL-_ // undo @@ -1047,7 +1049,7 @@ fn readline_direct() -> Result { /// Line editor pub struct Editor { - term: Rc>, + term: Terminal, history: History, completer: Option, kill_ring: KillRing, @@ -1055,26 +1057,26 @@ pub struct Editor { } impl Editor { - pub fn new(config: Config) -> Editor { - let term = Rc::new(RefCell::new(Terminal::new())); - Editor { + pub fn new(config: Config) -> Result> { + let term = try!(Terminal::new()); + Ok(Editor { term: term, history: History::new(config), completer: None, kill_ring: KillRing::new(60), config: config, - } + }) } /// This method will read a line from STDIN and will display a `prompt` pub fn readline(&mut self, prompt: &str) -> Result { - if self.term.borrow().is_unsupported() { + if self.term.is_unsupported() { // Write prompt and flush it to stdout let mut stdout = io::stdout(); try!(write_and_flush(&mut stdout, prompt.as_bytes())); readline_direct() - } else if !self.term.borrow().is_stdin_tty() { + } else if !self.term.is_stdin_tty() { // Not a tty: read from file / pipe. readline_direct() } else { @@ -1121,6 +1123,7 @@ impl fmt::Debug for Editor { #[cfg(all(unix,test))] mod test { use std::cell::RefCell; + use std::collections::HashMap; use std::io::Write; use std::rc::Rc; use line_buffer::LineBuffer; @@ -1136,7 +1139,7 @@ mod test { pos: usize, cols: usize) -> State<'out, 'static> { - let term = Rc::new(RefCell::new(Terminal::new())); + let term = Terminal::new().unwrap(); State { out: out, prompt: "", @@ -1204,7 +1207,8 @@ mod test { let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); let input = b"\n"; - let mut rdr = RawReader::new(&input[..]).unwrap(); + let terminfo_keys = Rc::new(RefCell::new(HashMap::new())); + let mut rdr = RawReader::new(&input[..], terminfo_keys).unwrap(); let completer = SimpleCompleter; let key = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); diff --git a/src/tty/unix.rs b/src/tty/unix.rs index a08029a519..66fd21ddc5 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,5 +1,8 @@ use std; +use std::cell::RefCell; +use std::collections::HashMap; use std::io::{Read, Write}; +use std::rc::Rc; use std::sync; use std::sync::atomic; use libc; @@ -85,6 +88,7 @@ fn is_a_tty(fd: libc::c_int) -> bool { unsafe { libc::isatty(fd) != 0 } } +/// Enable RAW mode for the terminal. pub fn enable_raw_mode() -> Result { use nix::errno::Errno::ENOTTY; use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, @@ -108,6 +112,7 @@ pub fn enable_raw_mode() -> Result { Ok(original_mode) } +/// Disable RAW mode for the terminal. pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &original_mode)); Ok(()) @@ -122,11 +127,17 @@ fn clear_screen(w: &mut Write) -> Result<()> { /// Console input reader pub struct RawReader { chars: char_iter::Chars, + terminfo_keys: Rc, String>>>, } impl RawReader { - pub fn new(stdin: R) -> Result> { - Ok(RawReader { chars: char_iter::chars(stdin) }) + pub fn new(stdin: R, + terminfo_keys: Rc, String>>>) + -> Result> { + Ok(RawReader { + chars: char_iter::chars(stdin), + terminfo_keys: terminfo_keys, + }) } // As there is no read timeout to properly handle single ESC key, @@ -231,22 +242,25 @@ extern "C" fn sigwinch_handler(_: signal::SigNum) { pub type Terminal = PosixTerminal; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct PosixTerminal { unsupported: bool, stdin_isatty: bool, + terminfo_keys: Rc, String>>>, } impl PosixTerminal { - pub fn new() -> PosixTerminal { - let term = PosixTerminal { + pub fn new() -> Result { + let mut term = PosixTerminal { unsupported: is_unsupported_term(), stdin_isatty: is_a_tty(STDIN_FILENO), + terminfo_keys: Rc::new(RefCell::new(HashMap::new())), }; if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) { install_sigwinch_handler(); + try!(term.load_capabilities()); } - term + Ok(term) } // Init checks: @@ -263,21 +277,17 @@ impl PosixTerminal { // Init if terminal-style mode: - // pub fn load_capabilities(&mut self) -> Result<()> { - // Ok(()) - // } - - // Interactive loop: - - /// Enable RAW mode for the terminal. - pub fn enable_raw_mode(&mut self) -> Result { - enable_raw_mode() + fn load_capabilities(&mut self) -> Result<()> { + use term::terminfo::TermInfo; + let term_info = try!(TermInfo::from_env()); + let mut terminfo_keys = self.terminfo_keys.borrow_mut(); + for (key, val) in term_info.strings.into_iter() { + terminfo_keys.insert(val.clone(), key.clone()); + } + Ok(()) } - /// Disable RAW mode for the terminal. - pub fn disable_raw_mode(&mut self, mode: Mode) -> Result<()> { - disable_raw_mode(mode) - } + // Interactive loop: /// Get the number of columns in the current terminal. pub fn get_columns(&self) -> usize { @@ -291,7 +301,7 @@ impl PosixTerminal { /// Create a RAW reader pub fn create_reader(&self) -> Result> { - RawReader::new(std::io::stdin()) + RawReader::new(std::io::stdin(), self.terminfo_keys.clone()) } /// Check if a SIGWINCH signal has been received diff --git a/src/tty/windows.rs b/src/tty/windows.rs index e0e62d04e3..5f83d34b9f 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -75,6 +75,7 @@ fn is_a_tty(fd: winapi::DWORD) -> bool { } } +/// Enable raw mode for the TERM pub fn enable_raw_mode() -> Result { let handle = try!(get_std_handle(STDIN_FILENO)); let original_mode = try!(get_console_mode(handle)); @@ -91,6 +92,7 @@ pub fn enable_raw_mode() -> Result { Ok(original_mode) } +/// Disable Raw mode for the term pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { let handle = try!(get_std_handle(STDIN_FILENO)); check!(kernel32::SetConsoleMode(handle, original_mode)); @@ -223,20 +225,20 @@ static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; pub type Terminal = Console; -#[derive(Debug)] +#[derive(Clone,Debug)] pub struct Console { stdin_isatty: bool, stdout_handle: winapi::HANDLE, } impl Console { - pub fn new() -> Console { + pub fn new() -> Result { use std::ptr; let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); - Console { + Ok(Console { stdin_isatty: is_a_tty(STDIN_FILENO), stdout_handle: stdout_handle, - } + }) } /// Checking for an unsupported TERM in windows is a no-op @@ -252,20 +254,6 @@ impl Console { // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT // } - pub fn load_capabilities(&mut self) -> Result<()> { - Ok(()) - } - - /// Enable raw mode for the TERM - pub fn enable_raw_mode(&mut self) -> Result { - enable_raw_mode() - } - - /// Disable Raw mode for the term - pub fn disable_raw_mode(&mut self, mode: Mode) -> Result<()> { - disable_raw_mode(mode) - } - /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. pub fn get_columns(&self) -> usize { @@ -301,13 +289,16 @@ impl Console { Ok(()) } - pub fn fill_console_output_character(&mut self, length: winapi::DWORD, pos: winapi::COORD) -> Result<()> { + pub fn fill_console_output_character(&mut self, + length: winapi::DWORD, + pos: winapi::COORD) + -> Result<()> { let mut _count = 0; check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle, - ' ' as winapi::CHAR, - length, - pos, - &mut _count)); + ' ' as winapi::CHAR, + length, + pos, + &mut _count)); Ok(()) } } From 6edb1b28773e0aa4c43f1bc8f8df9fa3d59cc4e3 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 18 Sep 2016 11:52:09 +0200 Subject: [PATCH 0149/1201] Revert terminfo --- Cargo.toml | 1 - examples/example.rs | 2 +- src/consts.rs | 8 +++++--- src/error.rs | 15 -------------- src/lib.rs | 20 +++++++----------- src/tty/unix.rs | 50 +++++++++++++++------------------------------ src/tty/windows.rs | 6 +++--- 7 files changed, 33 insertions(+), 69 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9b35ed8a63..9b09d189bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ encode_unicode = "0.1.3" [target.'cfg(unix)'.dependencies] nix = "0.5.0" -term = "0.4" [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/examples/example.rs b/examples/example.rs index ff7f9b625b..8d49697fa6 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -19,7 +19,7 @@ fn main() { .completion_type(CompletionType::List) .build(); let c = FilenameCompleter::new(); - let mut rl = Editor::new(config).expect("Cannot create line editor"); + let mut rl = Editor::new(config); rl.set_completer(Some(c)); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); diff --git a/src/consts.rs b/src/consts.rs index 375afe632c..31cbead410 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -14,6 +14,8 @@ pub enum KeyPress { Left, Meta(char), Null, + PageDown, + PageUp, Right, Tab, // Ctrl('I') Up, @@ -33,12 +35,12 @@ pub fn char_to_key_press(c: char) -> KeyPress { '\x05' => KeyPress::Ctrl('E'), '\x06' => KeyPress::Ctrl('F'), '\x07' => KeyPress::Ctrl('G'), - '\x08' => KeyPress::Backspace, + '\x08' => KeyPress::Backspace, // '\b' '\x09' => KeyPress::Tab, - '\x0a' => KeyPress::Ctrl('J'), + '\x0a' => KeyPress::Ctrl('J'), // '\n' (10) '\x0b' => KeyPress::Ctrl('K'), '\x0c' => KeyPress::Ctrl('L'), - '\x0d' => KeyPress::Enter, + '\x0d' => KeyPress::Enter, // '\r' (13) '\x0e' => KeyPress::Ctrl('N'), '\x10' => KeyPress::Ctrl('P'), '\x12' => KeyPress::Ctrl('R'), diff --git a/src/error.rs b/src/error.rs index e22323a60c..009ec65f1d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,8 +6,6 @@ use std::error; use std::fmt; #[cfg(unix)] use nix; -#[cfg(unix)] -use term; #[cfg(unix)] use char_iter; @@ -28,8 +26,6 @@ pub enum ReadlineError { /// Unix Error from syscall #[cfg(unix)] Errno(nix::Error), - #[cfg(unix)] - TermError(term::Error), #[cfg(windows)] WindowResize, #[cfg(windows)] @@ -46,8 +42,6 @@ impl fmt::Display for ReadlineError { ReadlineError::Char(ref err) => err.fmt(f), #[cfg(unix)] ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()), - #[cfg(unix)] - ReadlineError::TermError(ref err) => err.fmt(f), #[cfg(windows)] ReadlineError::WindowResize => write!(f, "WindowResize"), #[cfg(windows)] @@ -66,8 +60,6 @@ impl error::Error for ReadlineError { ReadlineError::Char(ref err) => err.description(), #[cfg(unix)] ReadlineError::Errno(ref err) => err.errno().desc(), - #[cfg(unix)] - ReadlineError::TermError(ref err) => err.description(), #[cfg(windows)] ReadlineError::WindowResize => "WindowResize", #[cfg(windows)] @@ -96,13 +88,6 @@ impl From for ReadlineError { } } -#[cfg(unix)] -impl From for ReadlineError { - fn from(err: term::Error) -> ReadlineError { - ReadlineError::TermError(err) - } -} - #[cfg(windows)] impl From for ReadlineError { fn from(err: char::DecodeUtf16Error) -> ReadlineError { diff --git a/src/lib.rs b/src/lib.rs index 54dab5676c..1b0202d83e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! //! ``` //! let config = rustyline::Config::default(); -//! let mut rl = rustyline::Editor::<()>::new(config).expect("Cannot create line editor"); +//! let mut rl = rustyline::Editor::<()>::new(config); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -20,8 +20,6 @@ extern crate libc; #[cfg(unix)] extern crate nix; -#[cfg(unix)] -extern crate term; extern crate unicode_width; extern crate encode_unicode; #[cfg(windows)] @@ -1057,15 +1055,15 @@ pub struct Editor { } impl Editor { - pub fn new(config: Config) -> Result> { - let term = try!(Terminal::new()); - Ok(Editor { + pub fn new(config: Config) -> Editor { + let term = Terminal::new(); + Editor { term: term, history: History::new(config), completer: None, kill_ring: KillRing::new(60), config: config, - }) + } } /// This method will read a line from STDIN and will display a `prompt` @@ -1122,10 +1120,7 @@ impl fmt::Debug for Editor { #[cfg(all(unix,test))] mod test { - use std::cell::RefCell; - use std::collections::HashMap; use std::io::Write; - use std::rc::Rc; use line_buffer::LineBuffer; use history::History; use completion::Completer; @@ -1139,7 +1134,7 @@ mod test { pos: usize, cols: usize) -> State<'out, 'static> { - let term = Terminal::new().unwrap(); + let term = Terminal::new(); State { out: out, prompt: "", @@ -1207,8 +1202,7 @@ mod test { let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); let input = b"\n"; - let terminfo_keys = Rc::new(RefCell::new(HashMap::new())); - let mut rdr = RawReader::new(&input[..], terminfo_keys).unwrap(); + let mut rdr = RawReader::new(&input[..]).unwrap(); let completer = SimpleCompleter; let key = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 66fd21ddc5..bbc57b54cb 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,8 +1,5 @@ use std; -use std::cell::RefCell; -use std::collections::HashMap; use std::io::{Read, Write}; -use std::rc::Rc; use std::sync; use std::sync::atomic; use libc; @@ -127,17 +124,11 @@ fn clear_screen(w: &mut Write) -> Result<()> { /// Console input reader pub struct RawReader { chars: char_iter::Chars, - terminfo_keys: Rc, String>>>, } impl RawReader { - pub fn new(stdin: R, - terminfo_keys: Rc, String>>>) - -> Result> { - Ok(RawReader { - chars: char_iter::chars(stdin), - terminfo_keys: terminfo_keys, - }) + pub fn new(stdin: R) -> Result> { + Ok(RawReader { chars: char_iter::chars(stdin) }) } // As there is no read timeout to properly handle single ESC key, @@ -171,9 +162,13 @@ impl RawReader { let seq3 = try!(self.next_char()); if seq3 == '~' { match seq2 { + // '1' => Ok(KeyPress::Home), '3' => Ok(KeyPress::Delete), - // TODO '1' // Home - // TODO '4' // End + // '4' => Ok(KeyPress::End), + '5' => Ok(KeyPress::PageUp), + '6' => Ok(KeyPress::PageDown), + '7' => Ok(KeyPress::Home), + '8' => Ok(KeyPress::End), _ => Ok(KeyPress::UnknownEscSeq), } } else { @@ -181,7 +176,7 @@ impl RawReader { } } else { match seq2 { - 'A' => Ok(KeyPress::Up), + 'A' => Ok(KeyPress::Up), // ANSI 'B' => Ok(KeyPress::Down), 'C' => Ok(KeyPress::Right), 'D' => Ok(KeyPress::Left), @@ -194,6 +189,10 @@ impl RawReader { // ESC O sequences. let seq2 = try!(self.next_char()); match seq2 { + 'A' => Ok(KeyPress::Up), + 'B' => Ok(KeyPress::Down), + 'C' => Ok(KeyPress::Right), + 'D' => Ok(KeyPress::Left), 'F' => Ok(KeyPress::End), 'H' => Ok(KeyPress::Home), _ => Ok(KeyPress::UnknownEscSeq), @@ -246,21 +245,18 @@ pub type Terminal = PosixTerminal; pub struct PosixTerminal { unsupported: bool, stdin_isatty: bool, - terminfo_keys: Rc, String>>>, } impl PosixTerminal { - pub fn new() -> Result { - let mut term = PosixTerminal { + pub fn new() -> PosixTerminal { + let term = PosixTerminal { unsupported: is_unsupported_term(), stdin_isatty: is_a_tty(STDIN_FILENO), - terminfo_keys: Rc::new(RefCell::new(HashMap::new())), }; if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) { install_sigwinch_handler(); - try!(term.load_capabilities()); } - Ok(term) + term } // Init checks: @@ -275,18 +271,6 @@ impl PosixTerminal { self.stdin_isatty } - // Init if terminal-style mode: - - fn load_capabilities(&mut self) -> Result<()> { - use term::terminfo::TermInfo; - let term_info = try!(TermInfo::from_env()); - let mut terminfo_keys = self.terminfo_keys.borrow_mut(); - for (key, val) in term_info.strings.into_iter() { - terminfo_keys.insert(val.clone(), key.clone()); - } - Ok(()) - } - // Interactive loop: /// Get the number of columns in the current terminal. @@ -301,7 +285,7 @@ impl PosixTerminal { /// Create a RAW reader pub fn create_reader(&self) -> Result> { - RawReader::new(std::io::stdin(), self.terminfo_keys.clone()) + RawReader::new(std::io::stdin()) } /// Check if a SIGWINCH signal has been received diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 5f83d34b9f..310ed12ca9 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -232,13 +232,13 @@ pub struct Console { } impl Console { - pub fn new() -> Result { + pub fn new() -> Console { use std::ptr; let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); - Ok(Console { + Console { stdin_isatty: is_a_tty(STDIN_FILENO), stdout_handle: stdout_handle, - }) + } } /// Checking for an unsupported TERM in windows is a no-op From a2699d43ae07f5c530b708dd7366afc95f8f08f4 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 18 Sep 2016 13:29:15 +0200 Subject: [PATCH 0150/1201] Upgrade to nix version 0.7 --- Cargo.toml | 2 +- src/tty/unix.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3580e38803..7154333472 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ libc = "0.2.7" unicode-width = "0.1.3" [target.'cfg(unix)'.dependencies] -nix = "0.5.0" +nix = "0.7" [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 53ca5c6bfa..4ad439f6ea 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -228,13 +228,13 @@ static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; fn install_sigwinch_handler() { SIGWINCH_ONCE.call_once(|| unsafe { let sigwinch = signal::SigAction::new(signal::SigHandler::Handler(sigwinch_handler), - signal::SaFlag::empty(), + signal::SaFlags::empty(), signal::SigSet::empty()); let _ = signal::sigaction(signal::SIGWINCH, &sigwinch); }); } -extern "C" fn sigwinch_handler(_: signal::SigNum) { +extern "C" fn sigwinch_handler(_: libc::c_int) { SIGWINCH.store(true, atomic::Ordering::SeqCst); } From d9522b3b0811869e9af6fdae2323b5ff37901e62 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 2 Oct 2016 08:50:35 +0200 Subject: [PATCH 0151/1201] Fix nightly build --- src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 21b2e2a63c..c92e14a905 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,7 @@ struct State<'out, 'prompt> { history_index: usize, // The history index we are currently editing snapshot: LineBuffer, // Current edited line before history browsing/completion term: Terminal, // terminal + byte_buffer: [u8; 4] } #[derive(Copy, Clone, Debug, Default)] @@ -97,6 +98,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { history_index: history_index, snapshot: LineBuffer::with_capacity(capacity), term: term, + byte_buffer: [0; 4], } } @@ -298,8 +300,8 @@ fn edit_insert(s: &mut State, ch: char) -> Result<()> { // Avoid a full update of the line in the trivial case. let cursor = calculate_position(&s.line[..s.line.pos()], s.prompt_size, s.cols); s.cursor = cursor; - let bits = ch.encode_utf8(); - let bits = bits.as_slice(); + let bits = ch.encode_utf8(&mut s.byte_buffer); + let bits = bits.as_bytes(); write_and_flush(s.out, bits) } else { s.refresh_line() From b537aee8334861a99195aac7be5eb3d03582a61d Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 2 Oct 2016 09:10:13 +0200 Subject: [PATCH 0152/1201] Oops --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c92e14a905..f6d6c588c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,7 @@ struct State<'out, 'prompt> { history_index: usize, // The history index we are currently editing snapshot: LineBuffer, // Current edited line before history browsing/completion term: Terminal, // terminal - byte_buffer: [u8; 4] + byte_buffer: [u8; 4], } #[derive(Copy, Clone, Debug, Default)] @@ -1148,6 +1148,7 @@ mod test { history_index: 0, snapshot: LineBuffer::with_capacity(100), term: term, + byte_buffer: [0; 4], } } From d8832713c65b2629ec23b29ee5881527647b5a63 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 2 Oct 2016 09:29:20 +0200 Subject: [PATCH 0153/1201] Fix #80 --- src/history.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/history.rs b/src/history.rs index aa5f22f2dc..c54d7ef96e 100644 --- a/src/history.rs +++ b/src/history.rs @@ -104,6 +104,9 @@ impl History { } /// Load the history from the specified file. + /// + /// # Failure + /// Will return `Err` if path does not already exist. pub fn load + ?Sized>(&mut self, path: &P) -> Result<()> { use std::io::{BufRead, BufReader}; From 28f68c4dcd2716c941159ec6f8f5ff13eee1687c Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 2 Oct 2016 09:29:20 +0200 Subject: [PATCH 0154/1201] Fix #80 --- src/history.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/history.rs b/src/history.rs index 59f3126262..212d2990af 100644 --- a/src/history.rs +++ b/src/history.rs @@ -96,6 +96,9 @@ impl History { } /// Load the history from the specified file. + /// + /// # Failure + /// Will return `Err` if path does not already exist. pub fn load + ?Sized>(&mut self, path: &P) -> Result<()> { use std::io::{BufRead, BufReader}; From 150bdb226e335dea580417af7d2e73a0f00dd854 Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Tue, 4 Oct 2016 06:08:46 -0400 Subject: [PATCH 0155/1201] Reving Rust Version for testing and Updating README for master docs --- .travis.yml | 2 +- README.md | 3 ++- appveyor.yml | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f30b67cbda..a328ee24eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: rust rust: - - 1.11.0 + - 1.12.0 - beta - nightly script: diff --git a/README.md b/README.md index a151f9fdeb..88b9831cb4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ Readline implementation in Rust that is based on [Antirez' Linenoise](https://github.com/antirez/linenoise) -[Documentation](https://docs.rs/rustyline) +[Documentation (Releases)](https://docs.rs/rustyline) +[Documentation (Master)](https://kkawakam.github.io/rustyline/rustyline/) **Supported Platforms** * Linux diff --git a/appveyor.yml b/appveyor.yml index 98dde4258e..4aae8469f4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ environment: matrix: - - TARGET: 1.11.0-x86_64-pc-windows-msvc - - TARGET: 1.11.0-x86_64-pc-windows-gnu + - TARGET: 1.12.0-x86_64-pc-windows-msvc + - TARGET: 1.12.0-x86_64-pc-windows-gnu - TARGET: beta-x86_64-pc-windows-msvc - TARGET: beta-x86_64-pc-windows-gnu install: From e5b20d3011d72bc694babfd1b360a05b2732f007 Mon Sep 17 00:00:00 2001 From: Katsu Kawakami Date: Tue, 4 Oct 2016 06:11:03 -0400 Subject: [PATCH 0156/1201] Fix spacing --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 88b9831cb4..fcf591ccb1 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Readline implementation in Rust that is based on [Antirez' Linenoise](https://github.com/antirez/linenoise) [Documentation (Releases)](https://docs.rs/rustyline) + [Documentation (Master)](https://kkawakam.github.io/rustyline/rustyline/) **Supported Platforms** From 7369fd668d1def78b7db1dd66a8d3f60f63619b3 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 8 Oct 2016 07:37:48 +0200 Subject: [PATCH 0157/1201] Draft for #82 --- src/lib.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index f6d6c588c4..dffc614c3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1109,6 +1109,25 @@ impl Editor { pub fn set_completer(&mut self, completer: Option) { self.completer = completer; } + + /// ``` + /// let config = rustyline::Config::default(); + /// let mut rl = rustyline::Editor::<()>::new(config); + /// for readline in rl.iter("> ") { + /// match readline { + /// Ok(line) => { + /// println!("Line: {}", line); + /// }, + /// Err(err) => { + /// println!("Error: {:?}", err); + /// break + /// } + /// } + /// } + /// ``` + pub fn iter<'a>(&'a mut self, prompt: &'a str) -> Iter { + Iter { editor: self, prompt: prompt } + } } impl fmt::Debug for Editor { @@ -1120,6 +1139,27 @@ impl fmt::Debug for Editor { } } +pub struct Iter<'a, C: Completer> where C: 'a { + editor: &'a mut Editor, + prompt: &'a str, +} + +impl<'a, C: Completer> Iterator for Iter<'a, C> { + type Item = Result; + + fn next(&mut self) -> Option> { + let readline = self.editor.readline(self.prompt); + match readline { + Ok(l) => { + self.editor.add_history_entry(&l); // TODO Validate + Some(Ok(l)) + }, + Err(error::ReadlineError::Eof) => None, + e @ Err(_) => Some(e), + } + } +} + #[cfg(all(unix,test))] mod test { use std::io::Write; From 7633912a3539a383d6a9ffe1f11c22d09a7953d7 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 8 Oct 2016 07:37:48 +0200 Subject: [PATCH 0158/1201] Draft for #82 --- src/lib.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 1b33d30c28..8f4d9b5780 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1107,6 +1107,25 @@ impl Editor { pub fn set_completer(&mut self, completer: Option) { self.completer = completer; } + + /// ``` + /// let config = rustyline::Config::default(); + /// let mut rl = rustyline::Editor::<()>::new(config); + /// for readline in rl.iter("> ") { + /// match readline { + /// Ok(line) => { + /// println!("Line: {}", line); + /// }, + /// Err(err) => { + /// println!("Error: {:?}", err); + /// break + /// } + /// } + /// } + /// ``` + pub fn iter<'a>(&'a mut self, prompt: &'a str) -> Iter { + Iter { editor: self, prompt: prompt } + } } impl fmt::Debug for Editor { @@ -1118,6 +1137,27 @@ impl fmt::Debug for Editor { } } +pub struct Iter<'a, C: Completer> where C: 'a { + editor: &'a mut Editor, + prompt: &'a str, +} + +impl<'a, C: Completer> Iterator for Iter<'a, C> { + type Item = Result; + + fn next(&mut self) -> Option> { + let readline = self.editor.readline(self.prompt); + match readline { + Ok(l) => { + self.editor.add_history_entry(&l); // TODO Validate + Some(Ok(l)) + }, + Err(error::ReadlineError::Eof) => None, + e @ Err(_) => Some(e), + } + } +} + #[cfg(all(unix,test))] mod test { use std::io::Write; From 5d55c9e0e07155e741bd28d0bb54b17c88a7a294 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 12 Oct 2016 19:35:41 +0200 Subject: [PATCH 0159/1201] Update todo list Completion list is not correctly paged by default. And add a link to linefeed library. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a151f9fdeb..ca77ab1e92 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,6 @@ Meta-BackSpace | Kill from the start of the current word, or, if between words, ## ToDo - - Show completion list - Undos - Read input with timeout to properly handle single ESC key - expose an API callable from C @@ -135,6 +134,7 @@ $ wineconsole --backend=curses target/x86_64-pc-windows-gnu/debug/examples/examp ## Similar projects - [copperline](https://github.com/srijs/rust-copperline) (Rust) + - [linefeed](https://github.com/murarth/linefeed) (Rust) - [liner](https://github.com/MovingtoMars/liner) (Rust) - [linenoise-ng](https://github.com/arangodb/linenoise-ng) (C++) - [liner](https://github.com/peterh/liner) (Go) From 56f27cf86087021e5b57cc089681e9bfdbfd0d23 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 16 Oct 2016 16:17:26 +0200 Subject: [PATCH 0160/1201] Make History iterable --- src/history.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/history.rs b/src/history.rs index c54d7ef96e..cbaf26cb76 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,7 +1,10 @@ //! History API use std::collections::VecDeque; +use std::collections::vec_deque; use std::fs::File; +use std::iter::DoubleEndedIterator; +use std::ops::Index; use std::path::Path; use super::Result; @@ -13,6 +16,7 @@ pub enum Direction { Reverse, } +/// Current state of the history. pub struct History { entries: VecDeque, max_len: usize, @@ -35,6 +39,11 @@ impl History { self.entries.get(index) } + /// Return the last history entry (i.e. previous command) + pub fn last(&self) -> Option<&String> { + self.entries.back() + } + /// Add a new entry in the history. pub fn add(&mut self, line: &str) -> bool { if self.max_len == 0 { @@ -146,6 +155,49 @@ impl History { } } } + + /// Return a forward iterator. + pub fn iter(&self) -> Iter { + Iter(self.entries.iter()) + } +} + +impl Index for History { + type Output = String; + + fn index(&self, index: usize) -> &String { + &self.entries[index] + } +} + +impl<'a> IntoIterator for &'a History { + type Item = &'a String; + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Iter<'a> { + self.iter() + } +} + +/// History iterator. +pub struct Iter<'a>(vec_deque::Iter<'a, String>); + +impl<'a> Iterator for Iter<'a> { + type Item = &'a String; + + fn next(&mut self) -> Option<&'a String> { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl<'a> DoubleEndedIterator for Iter<'a> { + fn next_back(&mut self) -> Option<&'a String> { + self.0.next_back() + } } #[cfg(test)] @@ -189,7 +241,7 @@ mod tests { let mut history = init(); history.set_max_len(1); assert_eq!(1, history.entries.len()); - assert_eq!(Some(&"line3".to_string()), history.entries.back()); + assert_eq!(Some(&"line3".to_string()), history.last()); } #[test] From 9577bfca91a1df1b02921c76483160acc1184a9c Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 16 Oct 2016 16:17:26 +0200 Subject: [PATCH 0161/1201] Make History iterable --- src/history.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/history.rs b/src/history.rs index 94ed8dc8b5..e09751f220 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,7 +1,10 @@ //! History API use std::collections::VecDeque; +use std::collections::vec_deque; use std::fs::File; +use std::iter::DoubleEndedIterator; +use std::ops::Index; use std::path::Path; use super::Result; @@ -13,6 +16,7 @@ pub enum Direction { Reverse, } +/// Current state of the history. pub struct History { entries: VecDeque, max_len: usize, @@ -35,6 +39,11 @@ impl History { self.entries.get(index) } + /// Return the last history entry (i.e. previous command) + pub fn last(&self) -> Option<&String> { + self.entries.back() + } + /// Add a new entry in the history. pub fn add(&mut self, line: &str) -> bool { if self.max_len == 0 { @@ -144,6 +153,49 @@ impl History { } } } + + /// Return a forward iterator. + pub fn iter(&self) -> Iter { + Iter(self.entries.iter()) + } +} + +impl Index for History { + type Output = String; + + fn index(&self, index: usize) -> &String { + &self.entries[index] + } +} + +impl<'a> IntoIterator for &'a History { + type Item = &'a String; + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Iter<'a> { + self.iter() + } +} + +/// History iterator. +pub struct Iter<'a>(vec_deque::Iter<'a, String>); + +impl<'a> Iterator for Iter<'a> { + type Item = &'a String; + + fn next(&mut self) -> Option<&'a String> { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl<'a> DoubleEndedIterator for Iter<'a> { + fn next_back(&mut self) -> Option<&'a String> { + self.0.next_back() + } } #[cfg(test)] @@ -187,7 +239,7 @@ mod tests { let mut history = init(); history.set_max_len(1); assert_eq!(1, history.entries.len()); - assert_eq!(Some(&"line3".to_string()), history.entries.back()); + assert_eq!(Some(&"line3".to_string()), history.last()); } #[test] From 1009a26c1c5d1a42326e8232e9abe1030d155cb1 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 17 Oct 2016 19:55:42 +0200 Subject: [PATCH 0162/1201] Fix #77 --- examples/example.rs | 2 +- src/history.rs | 10 +++++----- src/lib.rs | 15 ++++++++++----- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 8d49697fa6..e87444db03 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -28,7 +28,7 @@ fn main() { let readline = rl.readline(PROMPT); match readline { Ok(line) => { - rl.add_history_entry(&line); + rl.add_history_entry(line.as_ref()); println!("Line: {}", line); }, Err(ReadlineError::Interrupted) => { diff --git a/src/history.rs b/src/history.rs index cbaf26cb76..12f6f5d8d1 100644 --- a/src/history.rs +++ b/src/history.rs @@ -45,17 +45,17 @@ impl History { } /// Add a new entry in the history. - pub fn add(&mut self, line: &str) -> bool { + pub fn add + Into>(&mut self, line: S) -> bool { if self.max_len == 0 { return false; } - if line.is_empty() || - (self.ignore_space && line.chars().next().map_or(true, |c| c.is_whitespace())) { + if line.as_ref().is_empty() || + (self.ignore_space && line.as_ref().chars().next().map_or(true, |c| c.is_whitespace())) { return false; } if self.ignore_dups { if let Some(s) = self.entries.back() { - if s == line { + if s == line.as_ref() { return false; } } @@ -63,7 +63,7 @@ impl History { if self.entries.len() == self.max_len { self.entries.pop_front(); } - self.entries.push_back(String::from(line)); + self.entries.push_back(line.into()); true } diff --git a/src/lib.rs b/src/lib.rs index dffc614c3e..b17e84a6d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1093,7 +1093,7 @@ impl Editor { self.history.save(path) } /// Add a new entry in the history. - pub fn add_history_entry(&mut self, line: &str) -> bool { + pub fn add_history_entry + Into>(&mut self, line: S) -> bool { self.history.add(line) } /// Clear history. @@ -1126,7 +1126,10 @@ impl Editor { /// } /// ``` pub fn iter<'a>(&'a mut self, prompt: &'a str) -> Iter { - Iter { editor: self, prompt: prompt } + Iter { + editor: self, + prompt: prompt, + } } } @@ -1139,7 +1142,9 @@ impl fmt::Debug for Editor { } } -pub struct Iter<'a, C: Completer> where C: 'a { +pub struct Iter<'a, C: Completer> + where C: 'a +{ editor: &'a mut Editor, prompt: &'a str, } @@ -1151,9 +1156,9 @@ impl<'a, C: Completer> Iterator for Iter<'a, C> { let readline = self.editor.readline(self.prompt); match readline { Ok(l) => { - self.editor.add_history_entry(&l); // TODO Validate + self.editor.add_history_entry(l.as_ref()); // TODO Validate Some(Ok(l)) - }, + } Err(error::ReadlineError::Eof) => None, e @ Err(_) => Some(e), } From 198350c75a5e389a844e5e626678cff3bfa9959a Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 17 Oct 2016 19:55:42 +0200 Subject: [PATCH 0163/1201] Fix #77 --- examples/example.rs | 2 +- src/history.rs | 10 +++++----- src/lib.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 8d49697fa6..e87444db03 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -28,7 +28,7 @@ fn main() { let readline = rl.readline(PROMPT); match readline { Ok(line) => { - rl.add_history_entry(&line); + rl.add_history_entry(line.as_ref()); println!("Line: {}", line); }, Err(ReadlineError::Interrupted) => { diff --git a/src/history.rs b/src/history.rs index 94ed8dc8b5..927b3dca80 100644 --- a/src/history.rs +++ b/src/history.rs @@ -36,17 +36,17 @@ impl History { } /// Add a new entry in the history. - pub fn add(&mut self, line: &str) -> bool { + pub fn add + Into>(&mut self, line: S) -> bool { if self.max_len == 0 { return false; } - if line.is_empty() || - (self.ignore_space && line.chars().next().map_or(true, |c| c.is_whitespace())) { + if line.as_ref().is_empty() || + (self.ignore_space && line.as_ref().chars().next().map_or(true, |c| c.is_whitespace())) { return false; } if self.ignore_dups { if let Some(s) = self.entries.back() { - if s == line { + if s == line.as_ref() { return false; } } @@ -54,7 +54,7 @@ impl History { if self.entries.len() == self.max_len { self.entries.pop_front(); } - self.entries.push_back(String::from(line)); + self.entries.push_back(line.into()); true } diff --git a/src/lib.rs b/src/lib.rs index 1b33d30c28..dd23501690 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1091,7 +1091,7 @@ impl Editor { self.history.save(path) } /// Add a new entry in the history. - pub fn add_history_entry(&mut self, line: &str) -> bool { + pub fn add_history_entry + Into>(&mut self, line: S) -> bool { self.history.add(line) } /// Clear history. From e5c923f13ba0278bdd58fad521c67360849c341a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 23 Oct 2016 10:57:35 +0200 Subject: [PATCH 0164/1201] Introduce RawReader trait --- src/completion.rs | 4 ++-- src/lib.rs | 56 ++++++++++++++++++++++++++++------------------ src/tty/mod.rs | 8 +++++++ src/tty/unix.rs | 56 +++++++++++++++++++++++++--------------------- src/tty/windows.rs | 35 +++++++++++++++-------------- 5 files changed, 92 insertions(+), 67 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 2cc59cb1a6..868a4fbad2 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -237,8 +237,8 @@ pub fn longest_common_prefix(candidates: &[String]) -> Option<&str> { } let mut longest_common_prefix = 0; 'o: loop { - for i in 0..candidates.len() - 1 { - let b1 = candidates[i].as_bytes(); + for (i, c1) in candidates.iter().enumerate().take(candidates.len() - 1) { + let b1 = c1.as_bytes(); let b2 = candidates[i + 1].as_bytes(); if b1.len() <= longest_common_prefix || b2.len() <= longest_common_prefix || b1[longest_common_prefix] != b2[longest_common_prefix] { diff --git a/src/lib.rs b/src/lib.rs index b17e84a6d2..9d978fcdaa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,13 +39,13 @@ pub mod config; mod tty; use std::fmt; -use std::io::{self, Read, Write}; +use std::io::{self, Write}; use std::mem; use std::path::Path; use std::result; #[cfg(unix)] use nix::sys::signal; -use tty::Terminal; +use tty::{RawReader, Terminal}; use completion::{Completer, longest_common_prefix}; use consts::KeyPress; @@ -527,11 +527,11 @@ fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { } /// Completes the line/word -fn complete_line(rdr: &mut tty::RawReader, - s: &mut State, - completer: &Completer, - config: &Config) - -> Result> { +fn complete_line(rdr: &mut R, + s: &mut State, + completer: &Completer, + config: &Config) + -> Result> { // get a list of completions let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); // if no completions, we are done @@ -628,10 +628,10 @@ fn complete_line(rdr: &mut tty::RawReader, } } -fn page_completions(rdr: &mut tty::RawReader, - s: &mut State, - candidates: &[String]) - -> Result> { +fn page_completions(rdr: &mut R, + s: &mut State, + candidates: &[String]) + -> Result> { use std::cmp; use unicode_width::UnicodeWidthStr; @@ -695,10 +695,10 @@ fn page_completions(rdr: &mut tty::RawReader, } /// Incremental search -fn reverse_incremental_search(rdr: &mut tty::RawReader, - s: &mut State, - history: &History) - -> Result> { +fn reverse_incremental_search(rdr: &mut R, + s: &mut State, + history: &History) + -> Result> { if history.is_empty() { return Ok(None); } @@ -1168,13 +1168,16 @@ impl<'a, C: Completer> Iterator for Iter<'a, C> { #[cfg(all(unix,test))] mod test { use std::io::Write; + use std::slice::Iter; use line_buffer::LineBuffer; use history::History; use completion::Completer; use config::Config; + use consts::KeyPress; + use error::ReadlineError; use {Position, State}; use super::Result; - use tty::Terminal; + use tty::{RawReader, Terminal}; fn init_state<'out>(out: &'out mut Write, line: &str, @@ -1242,18 +1245,27 @@ mod test { } } + impl<'a> RawReader for Iter<'a, KeyPress> { + fn next_key(&mut self, _: bool) -> Result { + match self.next() { + Some(key) => Ok(*key), + None => Err(ReadlineError::Eof), + } + } + fn next_char(&mut self) -> Result { + unimplemented!(); + } + } + #[test] fn complete_line() { - use consts::KeyPress; - use tty::RawReader; - let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); - let input = b"\n"; - let mut rdr = RawReader::new(&input[..]).unwrap(); + let keys = &[KeyPress::Enter]; + let mut rdr = keys.iter(); let completer = SimpleCompleter; let key = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); - assert_eq!(Some(KeyPress::Ctrl('J')), key); + assert_eq!(Some(KeyPress::Enter), key); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); } diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 4f81220287..870a8eaca5 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -1,4 +1,12 @@ //! This module implements and describes common TTY methods & traits +use ::Result; +use consts::KeyPress; + +pub trait RawReader: Sized { + fn next_key(&mut self, esc_seq: bool) -> Result; + #[cfg(unix)] + fn next_char(&mut self) -> Result; +} // If on Windows platform import Windows TTY module // and re-export into mod.rs scope diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 4ad439f6ea..5fc58a36ba 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -10,6 +10,7 @@ use nix::sys::termios; use consts::{self, KeyPress}; use ::Result; use ::error; +use super::RawReader; pub type Mode = termios::Termios; const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; @@ -121,33 +122,13 @@ fn clear_screen(w: &mut Write) -> Result<()> { } /// Console input reader -pub struct RawReader { +pub struct PosixRawReader { chars: Chars, } -impl RawReader { - pub fn new(stdin: R) -> Result> { - Ok(RawReader { chars: stdin.chars() }) - } - - // As there is no read timeout to properly handle single ESC key, - // we make possible to deactivate escape sequence processing. - pub fn next_key(&mut self, esc_seq: bool) -> Result { - let c = try!(self.next_char()); - - let mut key = consts::char_to_key_press(c); - if esc_seq && key == KeyPress::Esc { - // escape sequence - key = try!(self.escape_sequence()); - } - Ok(key) - } - - pub fn next_char(&mut self) -> Result { - match self.chars.next() { - Some(c) => Ok(try!(c)), - None => Err(error::ReadlineError::Eof), - } +impl PosixRawReader { + pub fn new(stdin: R) -> Result> { + Ok(PosixRawReader { chars: stdin.chars() }) } fn escape_sequence(&mut self) -> Result { @@ -222,6 +203,29 @@ impl RawReader { } } +impl RawReader for PosixRawReader { + // As there is no read timeout to properly handle single ESC key, + // we make possible to deactivate escape sequence processing. + fn next_key(&mut self, esc_seq: bool) -> Result { + let c = try!(self.next_char()); + + let mut key = consts::char_to_key_press(c); + if esc_seq && key == KeyPress::Esc { + // escape sequence + key = try!(self.escape_sequence()); + } + Ok(key) + } + + fn next_char(&mut self) -> Result { + match self.chars.next() { + Some(c) => Ok(try!(c)), + None => Err(error::ReadlineError::Eof), + } + } +} + + static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT; static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; @@ -283,8 +287,8 @@ impl PosixTerminal { } /// Create a RAW reader - pub fn create_reader(&self) -> Result> { - RawReader::new(std::io::stdin()) + pub fn create_reader(&self) -> Result> { + PosixRawReader::new(std::io::stdin()) } /// Check if a SIGWINCH signal has been received diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 310ed12ca9..7d1b13dc4f 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,6 +1,5 @@ use std::io; -use std::io::{Read, Write}; -use std::marker::PhantomData; +use std::io::Write; use std::mem; use std::sync::atomic; @@ -10,6 +9,7 @@ use winapi; use consts::{self, KeyPress}; use ::error; use ::Result; +use super::RawReader; pub type Mode = winapi::DWORD; const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; @@ -100,9 +100,7 @@ pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { } /// Clear the screen. Used to handle ctrl+l -fn clear_screen(_: &mut Write, handle: winapi::HANDLE) -> Result<()> { - let mut info = unsafe { mem::zeroed() }; - check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); +fn clear_screen(info: winapi::CONSOLE_SCREEN_BUFFER_INFO, handle: winapi::HANDLE) -> Result<()> { let coord = winapi::COORD { X: 0, Y: 0 }; check!(kernel32::SetConsoleCursorPosition(handle, coord)); let mut _count = 0; @@ -116,23 +114,23 @@ fn clear_screen(_: &mut Write, handle: winapi::HANDLE) -> Result<()> { } /// Console input reader -pub struct RawReader { +pub struct ConsoleRawReader { handle: winapi::HANDLE, buf: Option, - phantom: PhantomData, } -impl RawReader { - pub fn new(_: R) -> Result> { +impl ConsoleRawReader { + pub fn new() -> Result { let handle = try!(get_std_handle(STDIN_FILENO)); - Ok(RawReader { + Ok(ConsoleRawReader { handle: handle, buf: None, - phantom: PhantomData, }) } +} - pub fn next_key(&mut self, _: bool) -> Result { +impl RawReader for ConsoleRawReader { + fn next_key(&mut self, _: bool) -> Result { use std::char::decode_utf16; // use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; use winapi::{LEFT_ALT_PRESSED, RIGHT_ALT_PRESSED}; @@ -178,6 +176,8 @@ impl RawReader { winapi::VK_DELETE => return Ok(KeyPress::Delete), winapi::VK_HOME => return Ok(KeyPress::Home), winapi::VK_END => return Ok(KeyPress::End), + winapi::VK_PRIOR => return Ok(KeyPress::PageUp), + winapi::VK_NEXT => return Ok(KeyPress::PageDown), _ => continue, }; } else if utf16 == 27 { @@ -211,7 +211,7 @@ impl RawReader { } } -impl Iterator for RawReader { +impl Iterator for ConsoleRawReader { type Item = u16; fn next(&mut self) -> Option { @@ -266,16 +266,17 @@ impl Console { get_rows(self.stdout_handle) } - pub fn create_reader(&self) -> Result> { - RawReader::new(io::stdin()) // FIXME + pub fn create_reader(&self) -> Result { + ConsoleRawReader::new() } pub fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } - pub fn clear_screen(&mut self, w: &mut Write) -> Result<()> { - clear_screen(w, self.stdout_handle) + pub fn clear_screen(&mut self, _: &mut Write) -> Result<()> { + let info = try!(self.get_console_screen_buffer_info()); + clear_screen(info, self.stdout_handle) } pub fn get_console_screen_buffer_info(&self) -> Result { From 70c4376da428a70bfbd1303105c30d000eb12821 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 23 Oct 2016 10:57:35 +0200 Subject: [PATCH 0165/1201] Introduce RawReader trait --- src/completion.rs | 4 ++-- src/lib.rs | 56 ++++++++++++++++++++++++++++------------------ src/tty/mod.rs | 8 +++++++ src/tty/unix.rs | 56 +++++++++++++++++++++++++--------------------- src/tty/windows.rs | 35 +++++++++++++++-------------- 5 files changed, 92 insertions(+), 67 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 2cc59cb1a6..868a4fbad2 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -237,8 +237,8 @@ pub fn longest_common_prefix(candidates: &[String]) -> Option<&str> { } let mut longest_common_prefix = 0; 'o: loop { - for i in 0..candidates.len() - 1 { - let b1 = candidates[i].as_bytes(); + for (i, c1) in candidates.iter().enumerate().take(candidates.len() - 1) { + let b1 = c1.as_bytes(); let b2 = candidates[i + 1].as_bytes(); if b1.len() <= longest_common_prefix || b2.len() <= longest_common_prefix || b1[longest_common_prefix] != b2[longest_common_prefix] { diff --git a/src/lib.rs b/src/lib.rs index 1b33d30c28..33c5e3abf3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,13 +40,13 @@ pub mod config; mod tty; use std::fmt; -use std::io::{self, Read, Write}; +use std::io::{self, Write}; use std::mem; use std::path::Path; use std::result; #[cfg(unix)] use nix::sys::signal; -use tty::Terminal; +use tty::{RawReader, Terminal}; use encode_unicode::CharExt; use completion::{Completer, longest_common_prefix}; @@ -525,11 +525,11 @@ fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> { } /// Completes the line/word -fn complete_line(rdr: &mut tty::RawReader, - s: &mut State, - completer: &Completer, - config: &Config) - -> Result> { +fn complete_line(rdr: &mut R, + s: &mut State, + completer: &Completer, + config: &Config) + -> Result> { // get a list of completions let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); // if no completions, we are done @@ -626,10 +626,10 @@ fn complete_line(rdr: &mut tty::RawReader, } } -fn page_completions(rdr: &mut tty::RawReader, - s: &mut State, - candidates: &[String]) - -> Result> { +fn page_completions(rdr: &mut R, + s: &mut State, + candidates: &[String]) + -> Result> { use std::cmp; use unicode_width::UnicodeWidthStr; @@ -693,10 +693,10 @@ fn page_completions(rdr: &mut tty::RawReader, } /// Incremental search -fn reverse_incremental_search(rdr: &mut tty::RawReader, - s: &mut State, - history: &History) - -> Result> { +fn reverse_incremental_search(rdr: &mut R, + s: &mut State, + history: &History) + -> Result> { if history.is_empty() { return Ok(None); } @@ -1121,13 +1121,16 @@ impl fmt::Debug for Editor { #[cfg(all(unix,test))] mod test { use std::io::Write; + use std::slice::Iter; use line_buffer::LineBuffer; use history::History; use completion::Completer; use config::Config; + use consts::KeyPress; + use error::ReadlineError; use {Position, State}; use super::Result; - use tty::Terminal; + use tty::{RawReader, Terminal}; fn init_state<'out>(out: &'out mut Write, line: &str, @@ -1194,18 +1197,27 @@ mod test { } } + impl<'a> RawReader for Iter<'a, KeyPress> { + fn next_key(&mut self, _: bool) -> Result { + match self.next() { + Some(key) => Ok(*key), + None => Err(ReadlineError::Eof), + } + } + fn next_char(&mut self) -> Result { + unimplemented!(); + } + } + #[test] fn complete_line() { - use consts::KeyPress; - use tty::RawReader; - let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); - let input = b"\n"; - let mut rdr = RawReader::new(&input[..]).unwrap(); + let keys = &[KeyPress::Enter]; + let mut rdr = keys.iter(); let completer = SimpleCompleter; let key = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); - assert_eq!(Some(KeyPress::Ctrl('J')), key); + assert_eq!(Some(KeyPress::Enter), key); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); } diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 82e2ed670c..8f93bd6ecd 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -1,4 +1,12 @@ //! This module implements and describes common TTY methods & traits +use ::Result; +use consts::KeyPress; + +pub trait RawReader: Sized { + fn next_key(&mut self, esc_seq: bool) -> Result; + #[cfg(unix)] + fn next_char(&mut self) -> Result; +} // If on Windows platform import Windows TTY module // and re-export into mod.rs scope diff --git a/src/tty/unix.rs b/src/tty/unix.rs index bbc57b54cb..403fcb6acf 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -11,6 +11,7 @@ use char_iter; use consts::{self, KeyPress}; use ::Result; use ::error; +use super::RawReader; pub type Mode = termios::Termios; const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; @@ -122,33 +123,13 @@ fn clear_screen(w: &mut Write) -> Result<()> { } /// Console input reader -pub struct RawReader { +pub struct PosixRawReader { chars: char_iter::Chars, } -impl RawReader { - pub fn new(stdin: R) -> Result> { - Ok(RawReader { chars: char_iter::chars(stdin) }) - } - - // As there is no read timeout to properly handle single ESC key, - // we make possible to deactivate escape sequence processing. - pub fn next_key(&mut self, esc_seq: bool) -> Result { - let c = try!(self.next_char()); - - let mut key = consts::char_to_key_press(c); - if esc_seq && key == KeyPress::Esc { - // escape sequence - key = try!(self.escape_sequence()); - } - Ok(key) - } - - pub fn next_char(&mut self) -> Result { - match self.chars.next() { - Some(c) => Ok(try!(c)), - None => Err(error::ReadlineError::Eof), - } +impl PosixRawReader { + pub fn new(stdin: R) -> Result> { + Ok(PosixRawReader { chars: char_iter::chars(stdin) }) } fn escape_sequence(&mut self) -> Result { @@ -223,6 +204,29 @@ impl RawReader { } } +impl RawReader for PosixRawReader { + // As there is no read timeout to properly handle single ESC key, + // we make possible to deactivate escape sequence processing. + fn next_key(&mut self, esc_seq: bool) -> Result { + let c = try!(self.next_char()); + + let mut key = consts::char_to_key_press(c); + if esc_seq && key == KeyPress::Esc { + // escape sequence + key = try!(self.escape_sequence()); + } + Ok(key) + } + + fn next_char(&mut self) -> Result { + match self.chars.next() { + Some(c) => Ok(try!(c)), + None => Err(error::ReadlineError::Eof), + } + } +} + + static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT; static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; @@ -284,8 +288,8 @@ impl PosixTerminal { } /// Create a RAW reader - pub fn create_reader(&self) -> Result> { - RawReader::new(std::io::stdin()) + pub fn create_reader(&self) -> Result> { + PosixRawReader::new(std::io::stdin()) } /// Check if a SIGWINCH signal has been received diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 310ed12ca9..7d1b13dc4f 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,6 +1,5 @@ use std::io; -use std::io::{Read, Write}; -use std::marker::PhantomData; +use std::io::Write; use std::mem; use std::sync::atomic; @@ -10,6 +9,7 @@ use winapi; use consts::{self, KeyPress}; use ::error; use ::Result; +use super::RawReader; pub type Mode = winapi::DWORD; const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; @@ -100,9 +100,7 @@ pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { } /// Clear the screen. Used to handle ctrl+l -fn clear_screen(_: &mut Write, handle: winapi::HANDLE) -> Result<()> { - let mut info = unsafe { mem::zeroed() }; - check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info)); +fn clear_screen(info: winapi::CONSOLE_SCREEN_BUFFER_INFO, handle: winapi::HANDLE) -> Result<()> { let coord = winapi::COORD { X: 0, Y: 0 }; check!(kernel32::SetConsoleCursorPosition(handle, coord)); let mut _count = 0; @@ -116,23 +114,23 @@ fn clear_screen(_: &mut Write, handle: winapi::HANDLE) -> Result<()> { } /// Console input reader -pub struct RawReader { +pub struct ConsoleRawReader { handle: winapi::HANDLE, buf: Option, - phantom: PhantomData, } -impl RawReader { - pub fn new(_: R) -> Result> { +impl ConsoleRawReader { + pub fn new() -> Result { let handle = try!(get_std_handle(STDIN_FILENO)); - Ok(RawReader { + Ok(ConsoleRawReader { handle: handle, buf: None, - phantom: PhantomData, }) } +} - pub fn next_key(&mut self, _: bool) -> Result { +impl RawReader for ConsoleRawReader { + fn next_key(&mut self, _: bool) -> Result { use std::char::decode_utf16; // use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; use winapi::{LEFT_ALT_PRESSED, RIGHT_ALT_PRESSED}; @@ -178,6 +176,8 @@ impl RawReader { winapi::VK_DELETE => return Ok(KeyPress::Delete), winapi::VK_HOME => return Ok(KeyPress::Home), winapi::VK_END => return Ok(KeyPress::End), + winapi::VK_PRIOR => return Ok(KeyPress::PageUp), + winapi::VK_NEXT => return Ok(KeyPress::PageDown), _ => continue, }; } else if utf16 == 27 { @@ -211,7 +211,7 @@ impl RawReader { } } -impl Iterator for RawReader { +impl Iterator for ConsoleRawReader { type Item = u16; fn next(&mut self) -> Option { @@ -266,16 +266,17 @@ impl Console { get_rows(self.stdout_handle) } - pub fn create_reader(&self) -> Result> { - RawReader::new(io::stdin()) // FIXME + pub fn create_reader(&self) -> Result { + ConsoleRawReader::new() } pub fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } - pub fn clear_screen(&mut self, w: &mut Write) -> Result<()> { - clear_screen(w, self.stdout_handle) + pub fn clear_screen(&mut self, _: &mut Write) -> Result<()> { + let info = try!(self.get_console_screen_buffer_info()); + clear_screen(info, self.stdout_handle) } pub fn get_console_screen_buffer_info(&self) -> Result { From 07d5248a91ff6cd94a7e621486a59e732fa9e615 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 23 Oct 2016 12:15:49 +0200 Subject: [PATCH 0166/1201] Simplify PosixRawReader --- src/tty/unix.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 5fc58a36ba..b752ad817a 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -122,12 +122,12 @@ fn clear_screen(w: &mut Write) -> Result<()> { } /// Console input reader -pub struct PosixRawReader { - chars: Chars, +pub struct PosixRawReader { + chars: Chars, } -impl PosixRawReader { - pub fn new(stdin: R) -> Result> { +impl PosixRawReader { + pub fn new(stdin: std::io::Stdin) -> Result { Ok(PosixRawReader { chars: stdin.chars() }) } @@ -203,7 +203,7 @@ impl PosixRawReader { } } -impl RawReader for PosixRawReader { +impl RawReader for PosixRawReader { // As there is no read timeout to properly handle single ESC key, // we make possible to deactivate escape sequence processing. fn next_key(&mut self, esc_seq: bool) -> Result { @@ -287,7 +287,7 @@ impl PosixTerminal { } /// Create a RAW reader - pub fn create_reader(&self) -> Result> { + pub fn create_reader(&self) -> Result { PosixRawReader::new(std::io::stdin()) } From 592ac56574f5eeb6b79b6857f35a0d6e056d8c2a Mon Sep 17 00:00:00 2001 From: Takafumi Hirata Date: Tue, 25 Oct 2016 08:22:15 +0900 Subject: [PATCH 0167/1201] fix is_unsupported_term --- src/tty/unix.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index bbc57b54cb..08aa7453e9 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -69,11 +69,12 @@ fn is_unsupported_term() -> bool { use std::ascii::AsciiExt; match std::env::var("TERM") { Ok(term) => { - let mut unsupported = false; for iter in &UNSUPPORTED_TERM { - unsupported = (*iter).eq_ignore_ascii_case(&term) + if (*iter).eq_ignore_ascii_case(&term) { + return true + } } - unsupported + false } Err(_) => false, } @@ -298,3 +299,15 @@ impl PosixTerminal { clear_screen(w) } } + +#[cfg(all(unix,test))] +mod test { + #[test] + fn test_unsupported_term() { + ::std::env::set_var("TERM", "xterm"); + assert_eq!(false, super::is_unsupported_term()); + + ::std::env::set_var("TERM", "dumb"); + assert_eq!(true, super::is_unsupported_term()); + } +} From f4244f118ba99094bd8716c1305404bd572daef5 Mon Sep 17 00:00:00 2001 From: Katsu K Date: Thu, 27 Oct 2016 19:13:00 -0400 Subject: [PATCH 0168/1201] Fix compile time error from bad merge --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2636a481b9..4f2dd57048 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1149,7 +1149,7 @@ impl<'a, C: Completer> Iterator for Iter<'a, C> { let readline = self.editor.readline(self.prompt); match readline { Ok(l) => { - self.editor.add_history_entry(&l); // TODO Validate + self.editor.add_history_entry(l.as_ref()); // TODO Validate Some(Ok(l)) }, Err(error::ReadlineError::Eof) => None, From 260ef1e49b826c0ee88d5a1f2acf46a2096bae16 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 5 Nov 2016 16:48:59 +0100 Subject: [PATCH 0169/1201] Fix clippy warnings --- examples/example.rs | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index e87444db03..63e88281e5 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -21,7 +21,7 @@ fn main() { let c = FilenameCompleter::new(); let mut rl = Editor::new(config); rl.set_completer(Some(c)); - if let Err(_) = rl.load_history("history.txt") { + if rl.load_history("history.txt").is_err() { println!("No previous history."); } loop { diff --git a/src/lib.rs b/src/lib.rs index 9d978fcdaa..bb76c612e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -316,7 +316,7 @@ fn edit_insert(s: &mut State, ch: char) -> Result<()> { // Yank/paste `text` at current position. fn edit_yank(s: &mut State, text: &str) -> Result<()> { - if let Some(_) = s.line.yank(text) { + if s.line.yank(text).is_some() { s.refresh_line() } else { Ok(()) From 86285e8aa913360427bf0f89f894b7bf8967230f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 5 Nov 2016 16:49:33 +0100 Subject: [PATCH 0170/1201] Fix completion (#95) --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index bb76c612e3..cee3748abe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -572,6 +572,9 @@ fn complete_line(rdr: &mut R, return Ok(None); } _ => { + if i == candidates.len() { + s.snapshot(); + } break; } } From 00855be3eab4d5ad79e74f81e55753220801832a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 5 Nov 2016 19:33:29 +0100 Subject: [PATCH 0171/1201] Try to handle single ESC key (#66) The side effect is that manual escape sequences such as ESC-f must be typed quickly... --- src/lib.rs | 14 +++++++------- src/tty/mod.rs | 2 +- src/tty/unix.rs | 21 +++++++++++++++------ src/tty/windows.rs | 2 +- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cee3748abe..1c6a889ec9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -555,7 +555,7 @@ fn complete_line(rdr: &mut R, s.snapshot(); } - key = try!(rdr.next_key(false)); + key = try!(rdr.next_key()); match key { KeyPress::Tab => { i = (i + 1) % (candidates.len() + 1); // Circular @@ -594,7 +594,7 @@ fn complete_line(rdr: &mut R, } } // we can't complete any further, wait for second tab - let mut key = try!(rdr.next_key(false)); + let mut key = try!(rdr.next_key()); // if any character other than tab, pass it to the main loop if key != KeyPress::Tab { return Ok(Some(key)); @@ -612,7 +612,7 @@ fn complete_line(rdr: &mut R, while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && key != KeyPress::Char('n') && key != KeyPress::Char('N') && key != KeyPress::Backspace { - key = try!(rdr.next_key(true)); + key = try!(rdr.next_key()); } show_completions = match key { KeyPress::Char('y') | @@ -659,7 +659,7 @@ fn page_completions(rdr: &mut R, key != KeyPress::Char('Q') && key != KeyPress::Char(' ') && key != KeyPress::Backspace && key != KeyPress::Enter { - key = try!(rdr.next_key(true)); + key = try!(rdr.next_key()); } match key { KeyPress::Char('y') | @@ -723,7 +723,7 @@ fn reverse_incremental_search(rdr: &mut R, }; try!(s.refresh_prompt_and_line(&prompt)); - key = try!(rdr.next_key(true)); + key = try!(rdr.next_key()); if let KeyPress::Char(c) = key { search_buf.push(c); } else { @@ -796,7 +796,7 @@ fn readline_edit(prompt: &str, let mut rdr = try!(s.term.create_reader()); loop { - let rk = rdr.next_key(true); + let rk = rdr.next_key(); if rk.is_err() && s.term.sigwinch() { s.update_columns(); try!(s.refresh_line()); @@ -1249,7 +1249,7 @@ mod test { } impl<'a> RawReader for Iter<'a, KeyPress> { - fn next_key(&mut self, _: bool) -> Result { + fn next_key(&mut self) -> Result { match self.next() { Some(key) => Ok(*key), None => Err(ReadlineError::Eof), diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 870a8eaca5..8020af806f 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -3,7 +3,7 @@ use ::Result; use consts::KeyPress; pub trait RawReader: Sized { - fn next_key(&mut self, esc_seq: bool) -> Result; + fn next_key(&mut self) -> Result; #[cfg(unix)] fn next_char(&mut self) -> Result; } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index b752ad817a..93679d6892 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -4,6 +4,7 @@ use std::sync; use std::sync::atomic; use libc; use nix; +use nix::poll; use nix::sys::signal; use nix::sys::termios; @@ -204,15 +205,23 @@ impl PosixRawReader { } impl RawReader for PosixRawReader { - // As there is no read timeout to properly handle single ESC key, - // we make possible to deactivate escape sequence processing. - fn next_key(&mut self, esc_seq: bool) -> Result { + fn next_key(&mut self) -> Result { let c = try!(self.next_char()); let mut key = consts::char_to_key_press(c); - if esc_seq && key == KeyPress::Esc { - // escape sequence - key = try!(self.escape_sequence()); + if key == KeyPress::Esc { + let mut fds = [poll::PollFd::new(STDIN_FILENO, poll::POLLIN, poll::EventFlags::empty())]; + match poll::poll(&mut fds, 500) { + Ok(n) if n == 0 => { + // single escape + }, + Ok(_) => { + // escape sequence + key = try!(self.escape_sequence()) + }, + // Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e.into()), + } } Ok(key) } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 7d1b13dc4f..25dbe58274 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -130,7 +130,7 @@ impl ConsoleRawReader { } impl RawReader for ConsoleRawReader { - fn next_key(&mut self, _: bool) -> Result { + fn next_key(&mut self) -> Result { use std::char::decode_utf16; // use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; use winapi::{LEFT_ALT_PRESSED, RIGHT_ALT_PRESSED}; From bb6de8a570ee10028cf77ce3db89b390e8436d68 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 5 Nov 2016 19:39:19 +0100 Subject: [PATCH 0172/1201] Rustfmt --- src/tty/unix.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 93679d6892..5ab31309e3 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -210,15 +210,16 @@ impl RawReader for PosixRawReader { let mut key = consts::char_to_key_press(c); if key == KeyPress::Esc { - let mut fds = [poll::PollFd::new(STDIN_FILENO, poll::POLLIN, poll::EventFlags::empty())]; + let mut fds = + [poll::PollFd::new(STDIN_FILENO, poll::POLLIN, poll::EventFlags::empty())]; match poll::poll(&mut fds, 500) { Ok(n) if n == 0 => { // single escape - }, + } Ok(_) => { // escape sequence key = try!(self.escape_sequence()) - }, + } // Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, Err(e) => return Err(e.into()), } From b3674699f080cad299f21cb45dce033c75c247a6 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 6 Nov 2016 09:03:58 +0100 Subject: [PATCH 0173/1201] Revert "Try to handle single ESC key (#66)" This reverts commit 00855be3eab4d5ad79e74f81e55753220801832a. --- src/lib.rs | 14 +++++++------- src/tty/mod.rs | 2 +- src/tty/unix.rs | 31 +++++++++++++++++++------------ src/tty/windows.rs | 2 +- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1c6a889ec9..cee3748abe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -555,7 +555,7 @@ fn complete_line(rdr: &mut R, s.snapshot(); } - key = try!(rdr.next_key()); + key = try!(rdr.next_key(false)); match key { KeyPress::Tab => { i = (i + 1) % (candidates.len() + 1); // Circular @@ -594,7 +594,7 @@ fn complete_line(rdr: &mut R, } } // we can't complete any further, wait for second tab - let mut key = try!(rdr.next_key()); + let mut key = try!(rdr.next_key(false)); // if any character other than tab, pass it to the main loop if key != KeyPress::Tab { return Ok(Some(key)); @@ -612,7 +612,7 @@ fn complete_line(rdr: &mut R, while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && key != KeyPress::Char('n') && key != KeyPress::Char('N') && key != KeyPress::Backspace { - key = try!(rdr.next_key()); + key = try!(rdr.next_key(true)); } show_completions = match key { KeyPress::Char('y') | @@ -659,7 +659,7 @@ fn page_completions(rdr: &mut R, key != KeyPress::Char('Q') && key != KeyPress::Char(' ') && key != KeyPress::Backspace && key != KeyPress::Enter { - key = try!(rdr.next_key()); + key = try!(rdr.next_key(true)); } match key { KeyPress::Char('y') | @@ -723,7 +723,7 @@ fn reverse_incremental_search(rdr: &mut R, }; try!(s.refresh_prompt_and_line(&prompt)); - key = try!(rdr.next_key()); + key = try!(rdr.next_key(true)); if let KeyPress::Char(c) = key { search_buf.push(c); } else { @@ -796,7 +796,7 @@ fn readline_edit(prompt: &str, let mut rdr = try!(s.term.create_reader()); loop { - let rk = rdr.next_key(); + let rk = rdr.next_key(true); if rk.is_err() && s.term.sigwinch() { s.update_columns(); try!(s.refresh_line()); @@ -1249,7 +1249,7 @@ mod test { } impl<'a> RawReader for Iter<'a, KeyPress> { - fn next_key(&mut self) -> Result { + fn next_key(&mut self, _: bool) -> Result { match self.next() { Some(key) => Ok(*key), None => Err(ReadlineError::Eof), diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 8020af806f..870a8eaca5 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -3,7 +3,7 @@ use ::Result; use consts::KeyPress; pub trait RawReader: Sized { - fn next_key(&mut self) -> Result; + fn next_key(&mut self, esc_seq: bool) -> Result; #[cfg(unix)] fn next_char(&mut self) -> Result; } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 5ab31309e3..6c42f9cf33 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -205,23 +205,30 @@ impl PosixRawReader { } impl RawReader for PosixRawReader { - fn next_key(&mut self) -> Result { + // As there is no read timeout to properly handle single ESC key, + // we make possible to deactivate escape sequence processing. + fn next_key(&mut self, esc_seq: bool) -> Result { let c = try!(self.next_char()); let mut key = consts::char_to_key_press(c); if key == KeyPress::Esc { - let mut fds = - [poll::PollFd::new(STDIN_FILENO, poll::POLLIN, poll::EventFlags::empty())]; - match poll::poll(&mut fds, 500) { - Ok(n) if n == 0 => { - // single escape - } - Ok(_) => { - // escape sequence - key = try!(self.escape_sequence()) + if esc_seq { + // escape sequence + key = try!(self.escape_sequence()); + } else { + let mut fds = + [poll::PollFd::new(STDIN_FILENO, poll::POLLIN, poll::EventFlags::empty())]; + match poll::poll(&mut fds, 500) { + Ok(n) if n == 0 => { + // single escape + } + Ok(_) => { + // escape sequence + key = try!(self.escape_sequence()) + } + // Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e.into()), } - // Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, - Err(e) => return Err(e.into()), } } Ok(key) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 25dbe58274..7d1b13dc4f 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -130,7 +130,7 @@ impl ConsoleRawReader { } impl RawReader for ConsoleRawReader { - fn next_key(&mut self) -> Result { + fn next_key(&mut self, _: bool) -> Result { use std::char::decode_utf16; // use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; use winapi::{LEFT_ALT_PRESSED, RIGHT_ALT_PRESSED}; From 41808a68eb27525be819e511ad3d1b9f52c15ec8 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 6 Nov 2016 10:47:39 +0100 Subject: [PATCH 0174/1201] Polling Stdin is useless --- src/tty/unix.rs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 6c42f9cf33..2427848a9a 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -4,7 +4,6 @@ use std::sync; use std::sync::atomic; use libc; use nix; -use nix::poll; use nix::sys::signal; use nix::sys::termios; @@ -208,28 +207,13 @@ impl RawReader for PosixRawReader { // As there is no read timeout to properly handle single ESC key, // we make possible to deactivate escape sequence processing. fn next_key(&mut self, esc_seq: bool) -> Result { + // FIXME Stdin is buffered, so polling after reading an ESC char is useless (next available chars are already buffered)... let c = try!(self.next_char()); let mut key = consts::char_to_key_press(c); - if key == KeyPress::Esc { - if esc_seq { - // escape sequence - key = try!(self.escape_sequence()); - } else { - let mut fds = - [poll::PollFd::new(STDIN_FILENO, poll::POLLIN, poll::EventFlags::empty())]; - match poll::poll(&mut fds, 500) { - Ok(n) if n == 0 => { - // single escape - } - Ok(_) => { - // escape sequence - key = try!(self.escape_sequence()) - } - // Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, - Err(e) => return Err(e.into()), - } - } + if key == KeyPress::Esc && esc_seq { + // escape sequence + key = try!(self.escape_sequence()); } Ok(key) } From af0b3788c2b208548946cac3c0a4dede85e3ce56 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 10:58:42 +0100 Subject: [PATCH 0175/1201] Introduce a test specific module Now we can test the Editor! --- src/consts.rs | 10 +++++ src/lib.rs | 88 ++++++++++++++++++++++++++++++++--------- src/tty/mod.rs | 33 ++++++++++++++-- src/tty/test.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++ src/tty/unix.rs | 31 ++++++++------- src/tty/windows.rs | 13 +++--- 6 files changed, 233 insertions(+), 41 deletions(-) create mode 100644 src/tty/test.rs diff --git a/src/consts.rs b/src/consts.rs index 31cbead410..e0ee405c7b 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -56,3 +56,13 @@ pub fn char_to_key_press(c: char) -> KeyPress { _ => KeyPress::Null, } } + +#[cfg(test)] +mod tests { + use super::{char_to_key_press, KeyPress}; + + #[test] + fn char_to_key() { + assert_eq!(KeyPress::Esc, char_to_key_press('\x1b')); + } +} diff --git a/src/lib.rs b/src/lib.rs index cee3748abe..171b36ed6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,7 @@ use std::path::Path; use std::result; #[cfg(unix)] use nix::sys::signal; -use tty::{RawReader, Terminal}; +use tty::{RawReader, Terminal, Term}; use completion::{Completer, longest_common_prefix}; use consts::KeyPress; @@ -1168,19 +1168,17 @@ impl<'a, C: Completer> Iterator for Iter<'a, C> { } } -#[cfg(all(unix,test))] +#[cfg(test)] mod test { use std::io::Write; - use std::slice::Iter; use line_buffer::LineBuffer; use history::History; use completion::Completer; use config::Config; use consts::KeyPress; - use error::ReadlineError; use {Position, State}; - use super::Result; - use tty::{RawReader, Terminal}; + use super::{Editor, Result}; + use tty::{Terminal, Term}; fn init_state<'out>(out: &'out mut Write, line: &str, @@ -1203,6 +1201,13 @@ mod test { } } + fn init_editor(keys: &[KeyPress]) -> Editor<()> { + let config = Config::default(); + let mut editor = Editor::<()>::new(config); + editor.term.keys.extend(keys.iter().cloned()); + editor + } + #[test] fn edit_history_next() { let mut out = ::std::io::sink(); @@ -1248,18 +1253,6 @@ mod test { } } - impl<'a> RawReader for Iter<'a, KeyPress> { - fn next_key(&mut self, _: bool) -> Result { - match self.next() { - Some(key) => Ok(*key), - None => Err(ReadlineError::Eof), - } - } - fn next_char(&mut self) -> Result { - unimplemented!(); - } - } - #[test] fn complete_line() { let mut out = ::std::io::sink(); @@ -1279,4 +1272,63 @@ mod test { assert_eq!(3, pos.col); assert_eq!(0, pos.row); } + + fn assert_line(keys: &[KeyPress], expected_line: &str) { + let mut editor = init_editor(keys); + let actual_line = editor.readline(&">>").unwrap(); + assert_eq!(expected_line, actual_line); + } + + #[test] + fn delete_key() { + assert_line(&[KeyPress::Char('a'), KeyPress::Delete, KeyPress::Enter], + "a"); + assert_line(&[KeyPress::Char('a'), KeyPress::Left, KeyPress::Delete, KeyPress::Enter], + ""); + } + + #[test] + fn down_key() { + assert_line(&[KeyPress::Down, KeyPress::Enter], ""); + } + + #[test] + fn end_key() { + assert_line(&[KeyPress::End, KeyPress::Enter], ""); + } + + #[test] + fn home_key() { + assert_line(&[KeyPress::Home, KeyPress::Enter], ""); + } + + #[test] + fn left_key() { + assert_line(&[KeyPress::Left, KeyPress::Enter], ""); + } + + #[test] + fn meta_backspace_key() { + assert_line(&[KeyPress::Meta('\x08'), KeyPress::Enter], ""); + } + + #[test] + fn page_down_key() { + assert_line(&[KeyPress::PageDown, KeyPress::Enter], ""); + } + + #[test] + fn page_up_key() { + assert_line(&[KeyPress::PageUp, KeyPress::Enter], ""); + } + + #[test] + fn right_key() { + assert_line(&[KeyPress::Right, KeyPress::Enter], ""); + } + + #[test] + fn up_key() { + assert_line(&[KeyPress::Up, KeyPress::Enter], ""); + } } diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 870a8eaca5..4c51ae221e 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -1,23 +1,48 @@ //! This module implements and describes common TTY methods & traits +use std::io::Write; use ::Result; use consts::KeyPress; pub trait RawReader: Sized { + /// Blocking read of key pressed. fn next_key(&mut self, esc_seq: bool) -> Result; + /// For CTRL-V support #[cfg(unix)] fn next_char(&mut self) -> Result; } +/// Terminal contract +pub trait Term: Clone { + fn new() -> Self; + /// Check if current terminal can provide a rich line-editing user interface. + fn is_unsupported(&self) -> bool; + /// check if stdin is connected to a terminal. + fn is_stdin_tty(&self) -> bool; + /// Get the number of columns in the current terminal. + fn get_columns(&self) -> usize; + /// Get the number of rows in the current terminal. + fn get_rows(&self) -> usize; + /// Check if a SIGWINCH signal has been received + fn sigwinch(&self) -> bool; + /// Clear the screen. Used to handle ctrl+l + fn clear_screen(&mut self, w: &mut Write) -> Result<()>; +} + // If on Windows platform import Windows TTY module // and re-export into mod.rs scope -#[cfg(windows)] +#[cfg(all(windows, not(test)))] mod windows; -#[cfg(windows)] +#[cfg(all(windows, not(test)))] pub use self::windows::*; // If on Unix platform import Unix TTY module // and re-export into mod.rs scope -#[cfg(unix)] +#[cfg(all(unix, not(test)))] mod unix; -#[cfg(unix)] +#[cfg(all(unix, not(test)))] pub use self::unix::*; + +#[cfg(test)] +mod test; +#[cfg(test)] +pub use self::test::*; diff --git a/src/tty/test.rs b/src/tty/test.rs new file mode 100644 index 0000000000..d40f494064 --- /dev/null +++ b/src/tty/test.rs @@ -0,0 +1,99 @@ +//! Tests specific definitions +use std::io::Write; +use std::iter::IntoIterator; +use std::slice::Iter; +use std::vec::IntoIter; + +use consts::KeyPress; +use ::error::ReadlineError; +use ::Result; +use super::{RawReader, Term}; + +pub type Mode = (); + +pub fn enable_raw_mode() -> Result { + Ok(()) +} +pub fn disable_raw_mode(_: Mode) -> Result<()> { + Ok(()) +} + +impl<'a> RawReader for Iter<'a, KeyPress> { + fn next_key(&mut self, _: bool) -> Result { + match self.next() { + Some(key) => Ok(*key), + None => Err(ReadlineError::Eof), + } + } + #[cfg(unix)] + fn next_char(&mut self) -> Result { + unimplemented!(); + } +} + +impl RawReader for IntoIter { + fn next_key(&mut self, _: bool) -> Result { + match self.next() { + Some(key) => Ok(key), + None => Err(ReadlineError::Eof), + } + } + #[cfg(unix)] + fn next_char(&mut self) -> Result { + unimplemented!(); + } +} + +pub type Terminal = DummyTerminal; + +#[derive(Clone,Debug)] +pub struct DummyTerminal { + pub keys: Vec, +} + +impl DummyTerminal { + /// Create a RAW reader + pub fn create_reader(&self) -> Result> { + Ok(self.keys.clone().into_iter()) + } +} + +impl Term for DummyTerminal { + fn new() -> DummyTerminal { + DummyTerminal { keys: Vec::new() } + } + + // Init checks: + + /// Check if current terminal can provide a rich line-editing user interface. + fn is_unsupported(&self) -> bool { + false + } + + /// check if stdin is connected to a terminal. + fn is_stdin_tty(&self) -> bool { + true + } + + // Interactive loop: + + /// Get the number of columns in the current terminal. + fn get_columns(&self) -> usize { + 80 + } + + /// Get the number of rows in the current terminal. + fn get_rows(&self) -> usize { + 24 + } + + /// Check if a SIGWINCH signal has been received + fn sigwinch(&self) -> bool { + false + } + + /// Clear the screen. Used to handle ctrl+l + fn clear_screen(&mut self, _: &mut Write) -> Result<()> { + Ok(()) + } +} diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 2427848a9a..dad14a848d 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,3 +1,4 @@ +//! Unix specific definitions use std; use std::io::{Chars, Read, Write}; use std::sync; @@ -10,7 +11,7 @@ use nix::sys::termios; use consts::{self, KeyPress}; use ::Result; use ::error; -use super::RawReader; +use super::{RawReader, Term}; pub type Mode = termios::Termios; const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; @@ -245,14 +246,21 @@ extern "C" fn sigwinch_handler(_: libc::c_int) { pub type Terminal = PosixTerminal; -#[derive(Clone, Debug)] +#[derive(Clone,Debug)] pub struct PosixTerminal { unsupported: bool, stdin_isatty: bool, } impl PosixTerminal { - pub fn new() -> PosixTerminal { + /// Create a RAW reader + pub fn create_reader(&self) -> Result { + PosixRawReader::new(std::io::stdin()) + } +} + +impl Term for PosixTerminal { + fn new() -> PosixTerminal { let term = PosixTerminal { unsupported: is_unsupported_term(), stdin_isatty: is_a_tty(STDIN_FILENO), @@ -266,39 +274,34 @@ impl PosixTerminal { // Init checks: /// Check if current terminal can provide a rich line-editing user interface. - pub fn is_unsupported(&self) -> bool { + fn is_unsupported(&self) -> bool { self.unsupported } /// check if stdin is connected to a terminal. - pub fn is_stdin_tty(&self) -> bool { + fn is_stdin_tty(&self) -> bool { self.stdin_isatty } // Interactive loop: /// Get the number of columns in the current terminal. - pub fn get_columns(&self) -> usize { + fn get_columns(&self) -> usize { get_columns() } /// Get the number of rows in the current terminal. - pub fn get_rows(&self) -> usize { + fn get_rows(&self) -> usize { get_rows() } - /// Create a RAW reader - pub fn create_reader(&self) -> Result { - PosixRawReader::new(std::io::stdin()) - } - /// Check if a SIGWINCH signal has been received - pub fn sigwinch(&self) -> bool { + fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } /// Clear the screen. Used to handle ctrl+l - pub fn clear_screen(&mut self, w: &mut Write) -> Result<()> { + fn clear_screen(&mut self, w: &mut Write) -> Result<()> { clear_screen(w) } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 7d1b13dc4f..e3a7a7031f 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,3 +1,4 @@ +//! Windows specific definitions use std::io; use std::io::Write; use std::mem; @@ -9,7 +10,7 @@ use winapi; use consts::{self, KeyPress}; use ::error; use ::Result; -use super::RawReader; +use super::{RawReader, Term}; pub type Mode = winapi::DWORD; const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; @@ -232,6 +233,12 @@ pub struct Console { } impl Console { + pub fn create_reader(&self) -> Result { + ConsoleRawReader::new() + } +} + +impl Term for Console { pub fn new() -> Console { use std::ptr; let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); @@ -266,10 +273,6 @@ impl Console { get_rows(self.stdout_handle) } - pub fn create_reader(&self) -> Result { - ConsoleRawReader::new() - } - pub fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } From 9ef96899eddacfd2d7d42525d759567632332a8b Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 11:01:36 +0100 Subject: [PATCH 0176/1201] Replace TCSAFLUSH by TCSADRAIN See https://github.com/antirez/linenoise/issues/75 --- src/tty/unix.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index dad14a848d..1feb474243 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -106,13 +106,13 @@ pub fn enable_raw_mode() -> Result { raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); raw.c_cc[VMIN] = 1; // One character-at-a-time input raw.c_cc[VTIME] = 0; // with blocking read - try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &raw)); + try!(termios::tcsetattr(STDIN_FILENO, termios::TCSADRAIN, &raw)); Ok(original_mode) } /// Disable RAW mode for the terminal. pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { - try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &original_mode)); + try!(termios::tcsetattr(STDIN_FILENO, termios::TCSADRAIN, &original_mode)); Ok(()) } From ad9e2f91b7fee6836c67ff0926a2b265199a65d0 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 11:04:01 +0100 Subject: [PATCH 0177/1201] Misc --- src/tty/windows.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index e3a7a7031f..d8c28e29b8 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -158,6 +158,7 @@ impl RawReader for ConsoleRawReader { key_event.wVirtualKeyCode != winapi::VK_MENU as winapi::WORD { continue; } + // key_event.wRepeatCount seems to be always set to 1 (maybe because we only read one character at a time) // let alt_gr = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED) == // (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); @@ -179,6 +180,7 @@ impl RawReader for ConsoleRawReader { winapi::VK_END => return Ok(KeyPress::End), winapi::VK_PRIOR => return Ok(KeyPress::PageUp), winapi::VK_NEXT => return Ok(KeyPress::PageDown), + // winapi::VK_BACK is correctly handled because the key_event.UnicodeChar is also set. _ => continue, }; } else if utf16 == 27 { From 5c465c7adbd3718240c97a7fd87a99cf6e497a9e Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 11:15:11 +0100 Subject: [PATCH 0178/1201] Try to fix windows build --- src/tty/test.rs | 21 ++++++++++++++++ src/tty/windows.rs | 62 +++++++++++++++++++++++----------------------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/tty/test.rs b/src/tty/test.rs index d40f494064..65d3116bcc 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -4,6 +4,9 @@ use std::iter::IntoIterator; use std::slice::Iter; use std::vec::IntoIter; +#[cfg(windows)] +use winapi; + use consts::KeyPress; use ::error::ReadlineError; use ::Result; @@ -56,6 +59,24 @@ impl DummyTerminal { pub fn create_reader(&self) -> Result> { Ok(self.keys.clone().into_iter()) } + + #[cfg(windows)] + pub fn get_console_screen_buffer_info(&self) -> Result { + Ok(info) + } + + #[cfg(windows)] + pub fn set_console_cursor_position(&mut self, pos: winapi::COORD) -> Result<()> { + Ok(()) + } + + #[cfg(windows)] + pub fn fill_console_output_character(&mut self, + length: winapi::DWORD, + pos: winapi::COORD) + -> Result<()> { + Ok(()) + } } impl Term for DummyTerminal { diff --git a/src/tty/windows.rs b/src/tty/windows.rs index d8c28e29b8..5cf0ad19de 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -238,10 +238,34 @@ impl Console { pub fn create_reader(&self) -> Result { ConsoleRawReader::new() } + + pub fn get_console_screen_buffer_info(&self) -> Result { + let mut info = unsafe { mem::zeroed() }; + check!(kernel32::GetConsoleScreenBufferInfo(self.stdout_handle, &mut info)); + Ok(info) + } + + pub fn set_console_cursor_position(&mut self, pos: winapi::COORD) -> Result<()> { + check!(kernel32::SetConsoleCursorPosition(self.stdout_handle, pos)); + Ok(()) + } + + pub fn fill_console_output_character(&mut self, + length: winapi::DWORD, + pos: winapi::COORD) + -> Result<()> { + let mut _count = 0; + check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle, + ' ' as winapi::CHAR, + length, + pos, + &mut _count)); + Ok(()) + } } impl Term for Console { - pub fn new() -> Console { + fn new() -> Console { use std::ptr; let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); Console { @@ -251,11 +275,11 @@ impl Term for Console { } /// Checking for an unsupported TERM in windows is a no-op - pub fn is_unsupported(&self) -> bool { + fn is_unsupported(&self) -> bool { false } - pub fn is_stdin_tty(&self) -> bool { + fn is_stdin_tty(&self) -> bool { self.stdin_isatty } @@ -265,46 +289,22 @@ impl Term for Console { /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. - pub fn get_columns(&self) -> usize { + fn get_columns(&self) -> usize { get_columns(self.stdout_handle) } /// Try to get the number of rows in the current terminal, /// or assume 24 if it fails. - pub fn get_rows(&self) -> usize { + fn get_rows(&self) -> usize { get_rows(self.stdout_handle) } - pub fn sigwinch(&self) -> bool { + fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } - pub fn clear_screen(&mut self, _: &mut Write) -> Result<()> { + fn clear_screen(&mut self, _: &mut Write) -> Result<()> { let info = try!(self.get_console_screen_buffer_info()); clear_screen(info, self.stdout_handle) } - - pub fn get_console_screen_buffer_info(&self) -> Result { - let mut info = unsafe { mem::zeroed() }; - check!(kernel32::GetConsoleScreenBufferInfo(self.stdout_handle, &mut info)); - Ok(info) - } - - pub fn set_console_cursor_position(&mut self, pos: winapi::COORD) -> Result<()> { - check!(kernel32::SetConsoleCursorPosition(self.stdout_handle, pos)); - Ok(()) - } - - pub fn fill_console_output_character(&mut self, - length: winapi::DWORD, - pos: winapi::COORD) - -> Result<()> { - let mut _count = 0; - check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle, - ' ' as winapi::CHAR, - length, - pos, - &mut _count)); - Ok(()) - } } From f6d1ecee42760797e716d01eb09e9706a4b33ee8 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 11:47:49 +0100 Subject: [PATCH 0179/1201] Fix Windows build --- src/tty/test.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/tty/test.rs b/src/tty/test.rs index 65d3116bcc..41cb960b79 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -62,18 +62,33 @@ impl DummyTerminal { #[cfg(windows)] pub fn get_console_screen_buffer_info(&self) -> Result { + let dw_size = winapi::COORD { X: 80, Y: 24 }; + let dw_cursor_osition = winapi::COORD { X: 0, Y: 0 }; + let sr_window = winapi::SMALL_RECT { + Left: 0, + Top: 0, + Right: 0, + Bottom: 0, + }; + let info = winapi::CONSOLE_SCREEN_BUFFER_INFO { + dwSize: dw_size, + dwCursorPosition: dw_cursor_osition, + wAttributes: 0, + srWindow: sr_window, + dwMaximumWindowSize: dw_size, + }; Ok(info) } #[cfg(windows)] - pub fn set_console_cursor_position(&mut self, pos: winapi::COORD) -> Result<()> { + pub fn set_console_cursor_position(&mut self, _: winapi::COORD) -> Result<()> { Ok(()) } #[cfg(windows)] pub fn fill_console_output_character(&mut self, - length: winapi::DWORD, - pos: winapi::COORD) + _: winapi::DWORD, + _: winapi::COORD) -> Result<()> { Ok(()) } From 67886c98db1f5f6263fa19a43c0ecf02956fac0a Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 16:10:13 +0100 Subject: [PATCH 0180/1201] Try to handle single ESC key (#66) --- src/config.rs | 13 ++++++++++++ src/lib.rs | 21 +++++++++++-------- src/tty/mod.rs | 2 +- src/tty/test.rs | 4 ++-- src/tty/unix.rs | 51 ++++++++++++++++++++++++++++++++++++---------- src/tty/windows.rs | 2 +- 6 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/config.rs b/src/config.rs index 49add02a4e..4d09c79653 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,8 @@ pub struct Config { /// When listing completion alternatives, only display /// one screen of possibilities at a time. completion_prompt_limit: usize, + /// Duration (milliseconds) Rustyline will wait for a character when reading an ambiguous key sequence. + keyseq_timeout: i32, } impl Config { @@ -43,6 +45,10 @@ impl Config { pub fn completion_prompt_limit(&self) -> usize { self.completion_prompt_limit } + + pub fn keyseq_timeout(&self) -> i32 { + self.keyseq_timeout + } } impl Default for Config { @@ -53,6 +59,7 @@ impl Default for Config { history_ignore_space: false, completion_type: CompletionType::Circular, // TODO Validate completion_prompt_limit: 100, + keyseq_timeout: 500, } } } @@ -118,6 +125,12 @@ impl Builder { self } + /// Set `keyseq_timeout` in milliseconds. + pub fn keyseq_timeout(mut self, keyseq_timeout_ms: i32) -> Builder { + self.p.keyseq_timeout = keyseq_timeout_ms; + self + } + pub fn build(self) -> Config { self.p } diff --git a/src/lib.rs b/src/lib.rs index 171b36ed6d..a4053a4994 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -555,7 +555,7 @@ fn complete_line(rdr: &mut R, s.snapshot(); } - key = try!(rdr.next_key(false)); + key = try!(rdr.next_key(config.keyseq_timeout())); match key { KeyPress::Tab => { i = (i + 1) % (candidates.len() + 1); // Circular @@ -594,7 +594,7 @@ fn complete_line(rdr: &mut R, } } // we can't complete any further, wait for second tab - let mut key = try!(rdr.next_key(false)); + let mut key = try!(rdr.next_key(config.keyseq_timeout())); // if any character other than tab, pass it to the main loop if key != KeyPress::Tab { return Ok(Some(key)); @@ -612,7 +612,7 @@ fn complete_line(rdr: &mut R, while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && key != KeyPress::Char('n') && key != KeyPress::Char('N') && key != KeyPress::Backspace { - key = try!(rdr.next_key(true)); + key = try!(rdr.next_key(config.keyseq_timeout())); } show_completions = match key { KeyPress::Char('y') | @@ -621,7 +621,7 @@ fn complete_line(rdr: &mut R, }; } if show_completions { - page_completions(rdr, s, &candidates) + page_completions(rdr, s, config, &candidates) } else { try!(s.refresh_line()); Ok(None) @@ -633,6 +633,7 @@ fn complete_line(rdr: &mut R, fn page_completions(rdr: &mut R, s: &mut State, + config: &Config, candidates: &[String]) -> Result> { use std::cmp; @@ -659,7 +660,7 @@ fn page_completions(rdr: &mut R, key != KeyPress::Char('Q') && key != KeyPress::Char(' ') && key != KeyPress::Backspace && key != KeyPress::Enter { - key = try!(rdr.next_key(true)); + key = try!(rdr.next_key(config.keyseq_timeout())); } match key { KeyPress::Char('y') | @@ -700,7 +701,8 @@ fn page_completions(rdr: &mut R, /// Incremental search fn reverse_incremental_search(rdr: &mut R, s: &mut State, - history: &History) + history: &History, + config: &Config) -> Result> { if history.is_empty() { return Ok(None); @@ -723,7 +725,7 @@ fn reverse_incremental_search(rdr: &mut R, }; try!(s.refresh_prompt_and_line(&prompt)); - key = try!(rdr.next_key(true)); + key = try!(rdr.next_key(config.keyseq_timeout())); if let KeyPress::Char(c) = key { search_buf.push(c); } else { @@ -796,7 +798,7 @@ fn readline_edit(prompt: &str, let mut rdr = try!(s.term.create_reader()); loop { - let rk = rdr.next_key(true); + let rk = rdr.next_key(editor.config.keyseq_timeout()); if rk.is_err() && s.term.sigwinch() { s.update_columns(); try!(s.refresh_line()); @@ -824,7 +826,8 @@ fn readline_edit(prompt: &str, } } else if key == KeyPress::Ctrl('R') { // Search history backward - let next = try!(reverse_incremental_search(&mut rdr, &mut s, &editor.history)); + let next = + try!(reverse_incremental_search(&mut rdr, &mut s, &editor.history, &editor.config)); if next.is_some() { key = next.unwrap(); } else { diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 4c51ae221e..801fba7df1 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -5,7 +5,7 @@ use consts::KeyPress; pub trait RawReader: Sized { /// Blocking read of key pressed. - fn next_key(&mut self, esc_seq: bool) -> Result; + fn next_key(&mut self, timeout_ms: i32) -> Result; /// For CTRL-V support #[cfg(unix)] fn next_char(&mut self) -> Result; diff --git a/src/tty/test.rs b/src/tty/test.rs index 41cb960b79..d39fab8123 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -22,7 +22,7 @@ pub fn disable_raw_mode(_: Mode) -> Result<()> { } impl<'a> RawReader for Iter<'a, KeyPress> { - fn next_key(&mut self, _: bool) -> Result { + fn next_key(&mut self, _: i32) -> Result { match self.next() { Some(key) => Ok(*key), None => Err(ReadlineError::Eof), @@ -35,7 +35,7 @@ impl<'a> RawReader for Iter<'a, KeyPress> { } impl RawReader for IntoIter { - fn next_key(&mut self, _: bool) -> Result { + fn next_key(&mut self, _: i32) -> Result { match self.next() { Some(key) => Ok(key), None => Err(ReadlineError::Eof), diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 1feb474243..eddb9700a2 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,10 +1,11 @@ //! Unix specific definitions use std; -use std::io::{Chars, Read, Write}; +use std::io::{self, Chars, Read, Write}; use std::sync; use std::sync::atomic; use libc; use nix; +use nix::poll; use nix::sys::signal; use nix::sys::termios; @@ -122,13 +123,33 @@ fn clear_screen(w: &mut Write) -> Result<()> { Ok(()) } +// Rust std::io::Stdin is buffered with no way to know if bytes are available. +// So we use low-level stuff instead... +struct StdinRaw {} + +impl Read for StdinRaw { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let res = unsafe { + libc::read(STDIN_FILENO, + buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as libc::size_t) + }; + if res == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(res as usize) + } + } +} + /// Console input reader pub struct PosixRawReader { - chars: Chars, + chars: Chars, } impl PosixRawReader { - pub fn new(stdin: std::io::Stdin) -> Result { + fn new() -> Result { + let stdin = StdinRaw {}; Ok(PosixRawReader { chars: stdin.chars() }) } @@ -205,16 +226,24 @@ impl PosixRawReader { } impl RawReader for PosixRawReader { - // As there is no read timeout to properly handle single ESC key, - // we make possible to deactivate escape sequence processing. - fn next_key(&mut self, esc_seq: bool) -> Result { - // FIXME Stdin is buffered, so polling after reading an ESC char is useless (next available chars are already buffered)... + fn next_key(&mut self, timeout_ms: i32) -> Result { let c = try!(self.next_char()); let mut key = consts::char_to_key_press(c); - if key == KeyPress::Esc && esc_seq { - // escape sequence - key = try!(self.escape_sequence()); + if key == KeyPress::Esc { + let mut fds = + [poll::PollFd::new(STDIN_FILENO, poll::POLLIN, poll::EventFlags::empty())]; + match poll::poll(&mut fds, timeout_ms) { + Ok(n) if n == 0 => { + // single escape + } + Ok(_) => { + // escape sequence + key = try!(self.escape_sequence()) + } + // Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e.into()), + } } Ok(key) } @@ -255,7 +284,7 @@ pub struct PosixTerminal { impl PosixTerminal { /// Create a RAW reader pub fn create_reader(&self) -> Result { - PosixRawReader::new(std::io::stdin()) + PosixRawReader::new() } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 5cf0ad19de..5a7ebf41fc 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -131,7 +131,7 @@ impl ConsoleRawReader { } impl RawReader for ConsoleRawReader { - fn next_key(&mut self, _: bool) -> Result { + fn next_key(&mut self, _: i32) -> Result { use std::char::decode_utf16; // use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; use winapi::{LEFT_ALT_PRESSED, RIGHT_ALT_PRESSED}; From 9ef7bd3916430c3352f391b21d4269ac9122c5ce Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 18:33:27 +0100 Subject: [PATCH 0181/1201] Move create_reader into the Term trait --- src/tty/mod.rs | 4 ++++ src/tty/test.rs | 13 ++++++++----- src/tty/unix.rs | 14 +++++++------- src/tty/windows.rs | 10 ++++++---- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 801fba7df1..1b77010318 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -13,6 +13,8 @@ pub trait RawReader: Sized { /// Terminal contract pub trait Term: Clone { + type Reader: RawReader; + fn new() -> Self; /// Check if current terminal can provide a rich line-editing user interface. fn is_unsupported(&self) -> bool; @@ -24,6 +26,8 @@ pub trait Term: Clone { fn get_rows(&self) -> usize; /// Check if a SIGWINCH signal has been received fn sigwinch(&self) -> bool; + /// Create a RAW reader + fn create_reader(&self) -> Result; /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, w: &mut Write) -> Result<()>; } diff --git a/src/tty/test.rs b/src/tty/test.rs index d39fab8123..24b65001b2 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -55,11 +55,6 @@ pub struct DummyTerminal { } impl DummyTerminal { - /// Create a RAW reader - pub fn create_reader(&self) -> Result> { - Ok(self.keys.clone().into_iter()) - } - #[cfg(windows)] pub fn get_console_screen_buffer_info(&self) -> Result { let dw_size = winapi::COORD { X: 80, Y: 24 }; @@ -95,6 +90,8 @@ impl DummyTerminal { } impl Term for DummyTerminal { + type Reader = IntoIter; + fn new() -> DummyTerminal { DummyTerminal { keys: Vec::new() } } @@ -128,6 +125,12 @@ impl Term for DummyTerminal { false } + /// Create a RAW reader + fn create_reader(&self) -> Result> { + Ok(self.keys.clone().into_iter()) + } + + /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, _: &mut Write) -> Result<()> { Ok(()) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index eddb9700a2..af0c7a6f42 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -281,14 +281,9 @@ pub struct PosixTerminal { stdin_isatty: bool, } -impl PosixTerminal { - /// Create a RAW reader - pub fn create_reader(&self) -> Result { - PosixRawReader::new() - } -} - impl Term for PosixTerminal { + type Reader = PosixRawReader; + fn new() -> PosixTerminal { let term = PosixTerminal { unsupported: is_unsupported_term(), @@ -324,6 +319,11 @@ impl Term for PosixTerminal { get_rows() } + /// Create a RAW reader + fn create_reader(&self) -> Result { + PosixRawReader::new() + } + /// Check if a SIGWINCH signal has been received fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 5a7ebf41fc..ab5bc8d298 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -235,10 +235,6 @@ pub struct Console { } impl Console { - pub fn create_reader(&self) -> Result { - ConsoleRawReader::new() - } - pub fn get_console_screen_buffer_info(&self) -> Result { let mut info = unsafe { mem::zeroed() }; check!(kernel32::GetConsoleScreenBufferInfo(self.stdout_handle, &mut info)); @@ -265,6 +261,8 @@ impl Console { } impl Term for Console { + type Reader = ConsoleRawReader; + fn new() -> Console { use std::ptr; let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); @@ -299,6 +297,10 @@ impl Term for Console { get_rows(self.stdout_handle) } + fn create_reader(&self) -> Result { + ConsoleRawReader::new() + } + fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } From e3ed29ef78d726f4cde89b39fc6a57018fecfa18 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 10:58:42 +0100 Subject: [PATCH 0182/1201] Introduce a test specific module Now we can test the Editor! --- src/consts.rs | 10 +++++ src/lib.rs | 88 ++++++++++++++++++++++++++++++++--------- src/tty/mod.rs | 34 ++++++++++++++-- src/tty/test.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++ src/tty/unix.rs | 43 ++++++++++---------- src/tty/windows.rs | 13 +++--- 6 files changed, 240 insertions(+), 47 deletions(-) create mode 100644 src/tty/test.rs diff --git a/src/consts.rs b/src/consts.rs index 31cbead410..e0ee405c7b 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -56,3 +56,13 @@ pub fn char_to_key_press(c: char) -> KeyPress { _ => KeyPress::Null, } } + +#[cfg(test)] +mod tests { + use super::{char_to_key_press, KeyPress}; + + #[test] + fn char_to_key() { + assert_eq!(KeyPress::Esc, char_to_key_press('\x1b')); + } +} diff --git a/src/lib.rs b/src/lib.rs index 4f2dd57048..c2fdabcf12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,7 @@ use std::path::Path; use std::result; #[cfg(unix)] use nix::sys::signal; -use tty::{RawReader, Terminal}; +use tty::{RawReader, Terminal, Term}; use encode_unicode::CharExt; use completion::{Completer, longest_common_prefix}; @@ -1158,19 +1158,17 @@ impl<'a, C: Completer> Iterator for Iter<'a, C> { } } -#[cfg(all(unix,test))] +#[cfg(test)] mod test { use std::io::Write; - use std::slice::Iter; use line_buffer::LineBuffer; use history::History; use completion::Completer; use config::Config; use consts::KeyPress; - use error::ReadlineError; use {Position, State}; - use super::Result; - use tty::{RawReader, Terminal}; + use super::{Editor, Result}; + use tty::{Terminal, Term}; fn init_state<'out>(out: &'out mut Write, line: &str, @@ -1192,6 +1190,13 @@ mod test { } } + fn init_editor(keys: &[KeyPress]) -> Editor<()> { + let config = Config::default(); + let mut editor = Editor::<()>::new(config); + editor.term.keys.extend(keys.iter().cloned()); + editor + } + #[test] fn edit_history_next() { let mut out = ::std::io::sink(); @@ -1237,18 +1242,6 @@ mod test { } } - impl<'a> RawReader for Iter<'a, KeyPress> { - fn next_key(&mut self, _: bool) -> Result { - match self.next() { - Some(key) => Ok(*key), - None => Err(ReadlineError::Eof), - } - } - fn next_char(&mut self) -> Result { - unimplemented!(); - } - } - #[test] fn complete_line() { let mut out = ::std::io::sink(); @@ -1268,4 +1261,63 @@ mod test { assert_eq!(3, pos.col); assert_eq!(0, pos.row); } + + fn assert_line(keys: &[KeyPress], expected_line: &str) { + let mut editor = init_editor(keys); + let actual_line = editor.readline(&">>").unwrap(); + assert_eq!(expected_line, actual_line); + } + + #[test] + fn delete_key() { + assert_line(&[KeyPress::Char('a'), KeyPress::Delete, KeyPress::Enter], + "a"); + assert_line(&[KeyPress::Char('a'), KeyPress::Left, KeyPress::Delete, KeyPress::Enter], + ""); + } + + #[test] + fn down_key() { + assert_line(&[KeyPress::Down, KeyPress::Enter], ""); + } + + #[test] + fn end_key() { + assert_line(&[KeyPress::End, KeyPress::Enter], ""); + } + + #[test] + fn home_key() { + assert_line(&[KeyPress::Home, KeyPress::Enter], ""); + } + + #[test] + fn left_key() { + assert_line(&[KeyPress::Left, KeyPress::Enter], ""); + } + + #[test] + fn meta_backspace_key() { + assert_line(&[KeyPress::Meta('\x08'), KeyPress::Enter], ""); + } + + #[test] + fn page_down_key() { + assert_line(&[KeyPress::PageDown, KeyPress::Enter], ""); + } + + #[test] + fn page_up_key() { + assert_line(&[KeyPress::PageUp, KeyPress::Enter], ""); + } + + #[test] + fn right_key() { + assert_line(&[KeyPress::Right, KeyPress::Enter], ""); + } + + #[test] + fn up_key() { + assert_line(&[KeyPress::Up, KeyPress::Enter], ""); + } } diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 8f93bd6ecd..4c51ae221e 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -1,22 +1,48 @@ //! This module implements and describes common TTY methods & traits +use std::io::Write; use ::Result; use consts::KeyPress; pub trait RawReader: Sized { + /// Blocking read of key pressed. fn next_key(&mut self, esc_seq: bool) -> Result; + /// For CTRL-V support #[cfg(unix)] fn next_char(&mut self) -> Result; } +/// Terminal contract +pub trait Term: Clone { + fn new() -> Self; + /// Check if current terminal can provide a rich line-editing user interface. + fn is_unsupported(&self) -> bool; + /// check if stdin is connected to a terminal. + fn is_stdin_tty(&self) -> bool; + /// Get the number of columns in the current terminal. + fn get_columns(&self) -> usize; + /// Get the number of rows in the current terminal. + fn get_rows(&self) -> usize; + /// Check if a SIGWINCH signal has been received + fn sigwinch(&self) -> bool; + /// Clear the screen. Used to handle ctrl+l + fn clear_screen(&mut self, w: &mut Write) -> Result<()>; +} + // If on Windows platform import Windows TTY module // and re-export into mod.rs scope -#[cfg(windows)] +#[cfg(all(windows, not(test)))] mod windows; -#[cfg(windows)] +#[cfg(all(windows, not(test)))] pub use self::windows::*; // If on Unix platform import Unix TTY module // and re-export into mod.rs scope -#[cfg(unix)]mod unix; -#[cfg(unix)] +#[cfg(all(unix, not(test)))] +mod unix; +#[cfg(all(unix, not(test)))] pub use self::unix::*; + +#[cfg(test)] +mod test; +#[cfg(test)] +pub use self::test::*; diff --git a/src/tty/test.rs b/src/tty/test.rs new file mode 100644 index 0000000000..d40f494064 --- /dev/null +++ b/src/tty/test.rs @@ -0,0 +1,99 @@ +//! Tests specific definitions +use std::io::Write; +use std::iter::IntoIterator; +use std::slice::Iter; +use std::vec::IntoIter; + +use consts::KeyPress; +use ::error::ReadlineError; +use ::Result; +use super::{RawReader, Term}; + +pub type Mode = (); + +pub fn enable_raw_mode() -> Result { + Ok(()) +} +pub fn disable_raw_mode(_: Mode) -> Result<()> { + Ok(()) +} + +impl<'a> RawReader for Iter<'a, KeyPress> { + fn next_key(&mut self, _: bool) -> Result { + match self.next() { + Some(key) => Ok(*key), + None => Err(ReadlineError::Eof), + } + } + #[cfg(unix)] + fn next_char(&mut self) -> Result { + unimplemented!(); + } +} + +impl RawReader for IntoIter { + fn next_key(&mut self, _: bool) -> Result { + match self.next() { + Some(key) => Ok(key), + None => Err(ReadlineError::Eof), + } + } + #[cfg(unix)] + fn next_char(&mut self) -> Result { + unimplemented!(); + } +} + +pub type Terminal = DummyTerminal; + +#[derive(Clone,Debug)] +pub struct DummyTerminal { + pub keys: Vec, +} + +impl DummyTerminal { + /// Create a RAW reader + pub fn create_reader(&self) -> Result> { + Ok(self.keys.clone().into_iter()) + } +} + +impl Term for DummyTerminal { + fn new() -> DummyTerminal { + DummyTerminal { keys: Vec::new() } + } + + // Init checks: + + /// Check if current terminal can provide a rich line-editing user interface. + fn is_unsupported(&self) -> bool { + false + } + + /// check if stdin is connected to a terminal. + fn is_stdin_tty(&self) -> bool { + true + } + + // Interactive loop: + + /// Get the number of columns in the current terminal. + fn get_columns(&self) -> usize { + 80 + } + + /// Get the number of rows in the current terminal. + fn get_rows(&self) -> usize { + 24 + } + + /// Check if a SIGWINCH signal has been received + fn sigwinch(&self) -> bool { + false + } + + /// Clear the screen. Used to handle ctrl+l + fn clear_screen(&mut self, _: &mut Write) -> Result<()> { + Ok(()) + } +} diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 85b16ddd79..f83bb7b6ea 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,5 +1,6 @@ +//! Unix specific definitions use std; -use std::io::{Read, Write}; +use std::io::Write; use std::sync; use std::sync::atomic; use libc; @@ -11,7 +12,7 @@ use char_iter; use consts::{self, KeyPress}; use ::Result; use ::error; -use super::RawReader; +use super::{RawReader, Term}; pub type Mode = termios::Termios; const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; @@ -124,12 +125,12 @@ fn clear_screen(w: &mut Write) -> Result<()> { } /// Console input reader -pub struct PosixRawReader { - chars: char_iter::Chars, +pub struct PosixRawReader { + chars: char_iter::Chars, } -impl PosixRawReader { - pub fn new(stdin: R) -> Result> { +impl PosixRawReader { + pub fn new(stdin: std::io::Stdin) -> Result { Ok(PosixRawReader { chars: char_iter::chars(stdin) }) } @@ -205,7 +206,7 @@ impl PosixRawReader { } } -impl RawReader for PosixRawReader { +impl RawReader for PosixRawReader { // As there is no read timeout to properly handle single ESC key, // we make possible to deactivate escape sequence processing. fn next_key(&mut self, esc_seq: bool) -> Result { @@ -246,14 +247,21 @@ extern "C" fn sigwinch_handler(_: signal::SigNum) { pub type Terminal = PosixTerminal; -#[derive(Clone, Debug)] +#[derive(Clone,Debug)] pub struct PosixTerminal { unsupported: bool, stdin_isatty: bool, } impl PosixTerminal { - pub fn new() -> PosixTerminal { + /// Create a RAW reader + pub fn create_reader(&self) -> Result { + PosixRawReader::new(std::io::stdin()) + } +} + +impl Term for PosixTerminal { + fn new() -> PosixTerminal { let term = PosixTerminal { unsupported: is_unsupported_term(), stdin_isatty: is_a_tty(STDIN_FILENO), @@ -267,39 +275,34 @@ impl PosixTerminal { // Init checks: /// Check if current terminal can provide a rich line-editing user interface. - pub fn is_unsupported(&self) -> bool { + fn is_unsupported(&self) -> bool { self.unsupported } /// check if stdin is connected to a terminal. - pub fn is_stdin_tty(&self) -> bool { + fn is_stdin_tty(&self) -> bool { self.stdin_isatty } // Interactive loop: /// Get the number of columns in the current terminal. - pub fn get_columns(&self) -> usize { + fn get_columns(&self) -> usize { get_columns() } /// Get the number of rows in the current terminal. - pub fn get_rows(&self) -> usize { + fn get_rows(&self) -> usize { get_rows() } - /// Create a RAW reader - pub fn create_reader(&self) -> Result> { - PosixRawReader::new(std::io::stdin()) - } - /// Check if a SIGWINCH signal has been received - pub fn sigwinch(&self) -> bool { + fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } /// Clear the screen. Used to handle ctrl+l - pub fn clear_screen(&mut self, w: &mut Write) -> Result<()> { + fn clear_screen(&mut self, w: &mut Write) -> Result<()> { clear_screen(w) } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 7d1b13dc4f..e3a7a7031f 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,3 +1,4 @@ +//! Windows specific definitions use std::io; use std::io::Write; use std::mem; @@ -9,7 +10,7 @@ use winapi; use consts::{self, KeyPress}; use ::error; use ::Result; -use super::RawReader; +use super::{RawReader, Term}; pub type Mode = winapi::DWORD; const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; @@ -232,6 +233,12 @@ pub struct Console { } impl Console { + pub fn create_reader(&self) -> Result { + ConsoleRawReader::new() + } +} + +impl Term for Console { pub fn new() -> Console { use std::ptr; let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); @@ -266,10 +273,6 @@ impl Console { get_rows(self.stdout_handle) } - pub fn create_reader(&self) -> Result { - ConsoleRawReader::new() - } - pub fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } From 919d1f510709aec348ef2e5fccab12c911abc9e5 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 23 Oct 2016 12:15:49 +0200 Subject: [PATCH 0183/1201] Simplify PosixRawReader From be606654f0566e89b21dd457b04aed213155087a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 5 Nov 2016 16:48:59 +0100 Subject: [PATCH 0184/1201] Fix clippy warnings --- examples/example.rs | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index e87444db03..63e88281e5 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -21,7 +21,7 @@ fn main() { let c = FilenameCompleter::new(); let mut rl = Editor::new(config); rl.set_completer(Some(c)); - if let Err(_) = rl.load_history("history.txt") { + if rl.load_history("history.txt").is_err() { println!("No previous history."); } loop { diff --git a/src/lib.rs b/src/lib.rs index c2fdabcf12..2bdbe52523 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -314,7 +314,7 @@ fn edit_insert(s: &mut State, ch: char) -> Result<()> { // Yank/paste `text` at current position. fn edit_yank(s: &mut State, text: &str) -> Result<()> { - if let Some(_) = s.line.yank(text) { + if s.line.yank(text).is_some() { s.refresh_line() } else { Ok(()) From 326256d8979add01298516b0bb91599f290d3bc4 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 11:15:11 +0100 Subject: [PATCH 0185/1201] Try to fix windows build --- src/tty/test.rs | 21 ++++++++++++++++ src/tty/windows.rs | 62 +++++++++++++++++++++++----------------------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/tty/test.rs b/src/tty/test.rs index d40f494064..65d3116bcc 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -4,6 +4,9 @@ use std::iter::IntoIterator; use std::slice::Iter; use std::vec::IntoIter; +#[cfg(windows)] +use winapi; + use consts::KeyPress; use ::error::ReadlineError; use ::Result; @@ -56,6 +59,24 @@ impl DummyTerminal { pub fn create_reader(&self) -> Result> { Ok(self.keys.clone().into_iter()) } + + #[cfg(windows)] + pub fn get_console_screen_buffer_info(&self) -> Result { + Ok(info) + } + + #[cfg(windows)] + pub fn set_console_cursor_position(&mut self, pos: winapi::COORD) -> Result<()> { + Ok(()) + } + + #[cfg(windows)] + pub fn fill_console_output_character(&mut self, + length: winapi::DWORD, + pos: winapi::COORD) + -> Result<()> { + Ok(()) + } } impl Term for DummyTerminal { diff --git a/src/tty/windows.rs b/src/tty/windows.rs index e3a7a7031f..0e42a7abb8 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -236,10 +236,34 @@ impl Console { pub fn create_reader(&self) -> Result { ConsoleRawReader::new() } + + pub fn get_console_screen_buffer_info(&self) -> Result { + let mut info = unsafe { mem::zeroed() }; + check!(kernel32::GetConsoleScreenBufferInfo(self.stdout_handle, &mut info)); + Ok(info) + } + + pub fn set_console_cursor_position(&mut self, pos: winapi::COORD) -> Result<()> { + check!(kernel32::SetConsoleCursorPosition(self.stdout_handle, pos)); + Ok(()) + } + + pub fn fill_console_output_character(&mut self, + length: winapi::DWORD, + pos: winapi::COORD) + -> Result<()> { + let mut _count = 0; + check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle, + ' ' as winapi::CHAR, + length, + pos, + &mut _count)); + Ok(()) + } } impl Term for Console { - pub fn new() -> Console { + fn new() -> Console { use std::ptr; let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); Console { @@ -249,11 +273,11 @@ impl Term for Console { } /// Checking for an unsupported TERM in windows is a no-op - pub fn is_unsupported(&self) -> bool { + fn is_unsupported(&self) -> bool { false } - pub fn is_stdin_tty(&self) -> bool { + fn is_stdin_tty(&self) -> bool { self.stdin_isatty } @@ -263,46 +287,22 @@ impl Term for Console { /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. - pub fn get_columns(&self) -> usize { + fn get_columns(&self) -> usize { get_columns(self.stdout_handle) } /// Try to get the number of rows in the current terminal, /// or assume 24 if it fails. - pub fn get_rows(&self) -> usize { + fn get_rows(&self) -> usize { get_rows(self.stdout_handle) } - pub fn sigwinch(&self) -> bool { + fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } - pub fn clear_screen(&mut self, _: &mut Write) -> Result<()> { + fn clear_screen(&mut self, _: &mut Write) -> Result<()> { let info = try!(self.get_console_screen_buffer_info()); clear_screen(info, self.stdout_handle) } - - pub fn get_console_screen_buffer_info(&self) -> Result { - let mut info = unsafe { mem::zeroed() }; - check!(kernel32::GetConsoleScreenBufferInfo(self.stdout_handle, &mut info)); - Ok(info) - } - - pub fn set_console_cursor_position(&mut self, pos: winapi::COORD) -> Result<()> { - check!(kernel32::SetConsoleCursorPosition(self.stdout_handle, pos)); - Ok(()) - } - - pub fn fill_console_output_character(&mut self, - length: winapi::DWORD, - pos: winapi::COORD) - -> Result<()> { - let mut _count = 0; - check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle, - ' ' as winapi::CHAR, - length, - pos, - &mut _count)); - Ok(()) - } } From 84a96dc3b7233ceee46c164d06c237b71d3e8627 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 11:47:49 +0100 Subject: [PATCH 0186/1201] Fix Windows build --- src/tty/test.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/tty/test.rs b/src/tty/test.rs index 65d3116bcc..41cb960b79 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -62,18 +62,33 @@ impl DummyTerminal { #[cfg(windows)] pub fn get_console_screen_buffer_info(&self) -> Result { + let dw_size = winapi::COORD { X: 80, Y: 24 }; + let dw_cursor_osition = winapi::COORD { X: 0, Y: 0 }; + let sr_window = winapi::SMALL_RECT { + Left: 0, + Top: 0, + Right: 0, + Bottom: 0, + }; + let info = winapi::CONSOLE_SCREEN_BUFFER_INFO { + dwSize: dw_size, + dwCursorPosition: dw_cursor_osition, + wAttributes: 0, + srWindow: sr_window, + dwMaximumWindowSize: dw_size, + }; Ok(info) } #[cfg(windows)] - pub fn set_console_cursor_position(&mut self, pos: winapi::COORD) -> Result<()> { + pub fn set_console_cursor_position(&mut self, _: winapi::COORD) -> Result<()> { Ok(()) } #[cfg(windows)] pub fn fill_console_output_character(&mut self, - length: winapi::DWORD, - pos: winapi::COORD) + _: winapi::DWORD, + _: winapi::COORD) -> Result<()> { Ok(()) } From 0bceb74d875efead0eb54fb5b8cad64af2f9e720 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 18 Sep 2016 13:29:15 +0200 Subject: [PATCH 0187/1201] Upgrade to nix version 0.7 --- Cargo.toml | 2 +- src/tty/unix.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ec6e507f87..278b223907 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ unicode-width = "0.1.3" encode_unicode = "0.1.3" [target.'cfg(unix)'.dependencies] -nix = "0.5.0" +nix = "0.7" [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/src/tty/unix.rs b/src/tty/unix.rs index f83bb7b6ea..6eaf9b10c9 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -235,13 +235,13 @@ static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; fn install_sigwinch_handler() { SIGWINCH_ONCE.call_once(|| unsafe { let sigwinch = signal::SigAction::new(signal::SigHandler::Handler(sigwinch_handler), - signal::SaFlag::empty(), + signal::SaFlags::empty(), signal::SigSet::empty()); let _ = signal::sigaction(signal::SIGWINCH, &sigwinch); }); } -extern "C" fn sigwinch_handler(_: signal::SigNum) { +extern "C" fn sigwinch_handler(_: libc::c_int) { SIGWINCH.store(true, atomic::Ordering::SeqCst); } From 3feb87c3d806420a2f781d2d433cea1bc1b43963 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 16:10:13 +0100 Subject: [PATCH 0188/1201] Try to handle single ESC key (#66) --- src/config.rs | 13 ++++++++++++ src/lib.rs | 32 +++++++++++++++++----------- src/tty/mod.rs | 2 +- src/tty/test.rs | 4 ++-- src/tty/unix.rs | 52 ++++++++++++++++++++++++++++++++++++---------- src/tty/windows.rs | 2 +- 6 files changed, 78 insertions(+), 27 deletions(-) diff --git a/src/config.rs b/src/config.rs index 49add02a4e..4d09c79653 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,8 @@ pub struct Config { /// When listing completion alternatives, only display /// one screen of possibilities at a time. completion_prompt_limit: usize, + /// Duration (milliseconds) Rustyline will wait for a character when reading an ambiguous key sequence. + keyseq_timeout: i32, } impl Config { @@ -43,6 +45,10 @@ impl Config { pub fn completion_prompt_limit(&self) -> usize { self.completion_prompt_limit } + + pub fn keyseq_timeout(&self) -> i32 { + self.keyseq_timeout + } } impl Default for Config { @@ -53,6 +59,7 @@ impl Default for Config { history_ignore_space: false, completion_type: CompletionType::Circular, // TODO Validate completion_prompt_limit: 100, + keyseq_timeout: 500, } } } @@ -118,6 +125,12 @@ impl Builder { self } + /// Set `keyseq_timeout` in milliseconds. + pub fn keyseq_timeout(mut self, keyseq_timeout_ms: i32) -> Builder { + self.p.keyseq_timeout = keyseq_timeout_ms; + self + } + pub fn build(self) -> Config { self.p } diff --git a/src/lib.rs b/src/lib.rs index 2bdbe52523..ebf19d0436 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -553,7 +553,7 @@ fn complete_line(rdr: &mut R, s.snapshot(); } - key = try!(rdr.next_key(false)); + key = try!(rdr.next_key(config.keyseq_timeout())); match key { KeyPress::Tab => { i = (i + 1) % (candidates.len() + 1); // Circular @@ -589,7 +589,7 @@ fn complete_line(rdr: &mut R, } } // we can't complete any further, wait for second tab - let mut key = try!(rdr.next_key(false)); + let mut key = try!(rdr.next_key(config.keyseq_timeout())); // if any character other than tab, pass it to the main loop if key != KeyPress::Tab { return Ok(Some(key)); @@ -607,7 +607,7 @@ fn complete_line(rdr: &mut R, while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && key != KeyPress::Char('n') && key != KeyPress::Char('N') && key != KeyPress::Backspace { - key = try!(rdr.next_key(true)); + key = try!(rdr.next_key(config.keyseq_timeout())); } show_completions = match key { KeyPress::Char('y') | @@ -616,7 +616,7 @@ fn complete_line(rdr: &mut R, }; } if show_completions { - page_completions(rdr, s, &candidates) + page_completions(rdr, s, config, &candidates) } else { try!(s.refresh_line()); Ok(None) @@ -628,6 +628,7 @@ fn complete_line(rdr: &mut R, fn page_completions(rdr: &mut R, s: &mut State, + config: &Config, candidates: &[String]) -> Result> { use std::cmp; @@ -654,7 +655,7 @@ fn page_completions(rdr: &mut R, key != KeyPress::Char('Q') && key != KeyPress::Char(' ') && key != KeyPress::Backspace && key != KeyPress::Enter { - key = try!(rdr.next_key(true)); + key = try!(rdr.next_key(config.keyseq_timeout())); } match key { KeyPress::Char('y') | @@ -695,7 +696,8 @@ fn page_completions(rdr: &mut R, /// Incremental search fn reverse_incremental_search(rdr: &mut R, s: &mut State, - history: &History) + history: &History, + config: &Config) -> Result> { if history.is_empty() { return Ok(None); @@ -718,7 +720,7 @@ fn reverse_incremental_search(rdr: &mut R, }; try!(s.refresh_prompt_and_line(&prompt)); - key = try!(rdr.next_key(true)); + key = try!(rdr.next_key(config.keyseq_timeout())); if let KeyPress::Char(c) = key { search_buf.push(c); } else { @@ -791,7 +793,7 @@ fn readline_edit(prompt: &str, let mut rdr = try!(s.term.create_reader()); loop { - let rk = rdr.next_key(true); + let rk = rdr.next_key(editor.config.keyseq_timeout()); if rk.is_err() && s.term.sigwinch() { s.update_columns(); try!(s.refresh_line()); @@ -819,7 +821,8 @@ fn readline_edit(prompt: &str, } } else if key == KeyPress::Ctrl('R') { // Search history backward - let next = try!(reverse_incremental_search(&mut rdr, &mut s, &editor.history)); + let next = + try!(reverse_incremental_search(&mut rdr, &mut s, &editor.history, &editor.config)); if next.is_some() { key = next.unwrap(); } else { @@ -1124,7 +1127,10 @@ impl Editor { /// } /// ``` pub fn iter<'a>(&'a mut self, prompt: &'a str) -> Iter { - Iter { editor: self, prompt: prompt } + Iter { + editor: self, + prompt: prompt, + } } } @@ -1137,7 +1143,9 @@ impl fmt::Debug for Editor { } } -pub struct Iter<'a, C: Completer> where C: 'a { +pub struct Iter<'a, C: Completer> + where C: 'a +{ editor: &'a mut Editor, prompt: &'a str, } @@ -1151,7 +1159,7 @@ impl<'a, C: Completer> Iterator for Iter<'a, C> { Ok(l) => { self.editor.add_history_entry(l.as_ref()); // TODO Validate Some(Ok(l)) - }, + } Err(error::ReadlineError::Eof) => None, e @ Err(_) => Some(e), } diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 4c51ae221e..801fba7df1 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -5,7 +5,7 @@ use consts::KeyPress; pub trait RawReader: Sized { /// Blocking read of key pressed. - fn next_key(&mut self, esc_seq: bool) -> Result; + fn next_key(&mut self, timeout_ms: i32) -> Result; /// For CTRL-V support #[cfg(unix)] fn next_char(&mut self) -> Result; diff --git a/src/tty/test.rs b/src/tty/test.rs index 41cb960b79..d39fab8123 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -22,7 +22,7 @@ pub fn disable_raw_mode(_: Mode) -> Result<()> { } impl<'a> RawReader for Iter<'a, KeyPress> { - fn next_key(&mut self, _: bool) -> Result { + fn next_key(&mut self, _: i32) -> Result { match self.next() { Some(key) => Ok(*key), None => Err(ReadlineError::Eof), @@ -35,7 +35,7 @@ impl<'a> RawReader for Iter<'a, KeyPress> { } impl RawReader for IntoIter { - fn next_key(&mut self, _: bool) -> Result { + fn next_key(&mut self, _: i32) -> Result { match self.next() { Some(key) => Ok(key), None => Err(ReadlineError::Eof), diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 6eaf9b10c9..8c0654e511 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,10 +1,11 @@ //! Unix specific definitions use std; -use std::io::Write; +use std::io::{self, Read, Write}; use std::sync; use std::sync::atomic; use libc; use nix; +use nix::poll; use nix::sys::signal; use nix::sys::termios; @@ -73,7 +74,7 @@ fn is_unsupported_term() -> bool { Ok(term) => { for iter in &UNSUPPORTED_TERM { if (*iter).eq_ignore_ascii_case(&term) { - return true + return true; } } false @@ -124,13 +125,33 @@ fn clear_screen(w: &mut Write) -> Result<()> { Ok(()) } +// Rust std::io::Stdin is buffered with no way to know if bytes are available. +// So we use low-level stuff instead... +struct StdinRaw {} + +impl Read for StdinRaw { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let res = unsafe { + libc::read(STDIN_FILENO, + buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as libc::size_t) + }; + if res == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(res as usize) + } + } +} + /// Console input reader pub struct PosixRawReader { - chars: char_iter::Chars, + chars: char_iter::Chars, } impl PosixRawReader { - pub fn new(stdin: std::io::Stdin) -> Result { + pub fn new() -> Result { + let stdin = StdinRaw {}; Ok(PosixRawReader { chars: char_iter::chars(stdin) }) } @@ -207,15 +228,24 @@ impl PosixRawReader { } impl RawReader for PosixRawReader { - // As there is no read timeout to properly handle single ESC key, - // we make possible to deactivate escape sequence processing. - fn next_key(&mut self, esc_seq: bool) -> Result { + fn next_key(&mut self, timeout_ms: i32) -> Result { let c = try!(self.next_char()); let mut key = consts::char_to_key_press(c); - if esc_seq && key == KeyPress::Esc { - // escape sequence - key = try!(self.escape_sequence()); + if key == KeyPress::Esc { + let mut fds = + [poll::PollFd::new(STDIN_FILENO, poll::POLLIN, poll::EventFlags::empty())]; + match poll::poll(&mut fds, timeout_ms) { + Ok(n) if n == 0 => { + // single escape + } + Ok(_) => { + // escape sequence + key = try!(self.escape_sequence()) + } + // Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e.into()), + } } Ok(key) } @@ -256,7 +286,7 @@ pub struct PosixTerminal { impl PosixTerminal { /// Create a RAW reader pub fn create_reader(&self) -> Result { - PosixRawReader::new(std::io::stdin()) + PosixRawReader::new() } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 0e42a7abb8..9a12d53365 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -131,7 +131,7 @@ impl ConsoleRawReader { } impl RawReader for ConsoleRawReader { - fn next_key(&mut self, _: bool) -> Result { + fn next_key(&mut self, _: i32) -> Result { use std::char::decode_utf16; // use winapi::{LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED}; use winapi::{LEFT_ALT_PRESSED, RIGHT_ALT_PRESSED}; From cbbd858d52693665a7b718856b60db5c4e3929af Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 11 Nov 2016 18:33:27 +0100 Subject: [PATCH 0189/1201] Move create_reader into the Term trait --- src/tty/mod.rs | 4 ++++ src/tty/test.rs | 13 ++++++++----- src/tty/unix.rs | 14 +++++++------- src/tty/windows.rs | 10 ++++++---- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 801fba7df1..1b77010318 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -13,6 +13,8 @@ pub trait RawReader: Sized { /// Terminal contract pub trait Term: Clone { + type Reader: RawReader; + fn new() -> Self; /// Check if current terminal can provide a rich line-editing user interface. fn is_unsupported(&self) -> bool; @@ -24,6 +26,8 @@ pub trait Term: Clone { fn get_rows(&self) -> usize; /// Check if a SIGWINCH signal has been received fn sigwinch(&self) -> bool; + /// Create a RAW reader + fn create_reader(&self) -> Result; /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, w: &mut Write) -> Result<()>; } diff --git a/src/tty/test.rs b/src/tty/test.rs index d39fab8123..24b65001b2 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -55,11 +55,6 @@ pub struct DummyTerminal { } impl DummyTerminal { - /// Create a RAW reader - pub fn create_reader(&self) -> Result> { - Ok(self.keys.clone().into_iter()) - } - #[cfg(windows)] pub fn get_console_screen_buffer_info(&self) -> Result { let dw_size = winapi::COORD { X: 80, Y: 24 }; @@ -95,6 +90,8 @@ impl DummyTerminal { } impl Term for DummyTerminal { + type Reader = IntoIter; + fn new() -> DummyTerminal { DummyTerminal { keys: Vec::new() } } @@ -128,6 +125,12 @@ impl Term for DummyTerminal { false } + /// Create a RAW reader + fn create_reader(&self) -> Result> { + Ok(self.keys.clone().into_iter()) + } + + /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, _: &mut Write) -> Result<()> { Ok(()) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 8c0654e511..d733bfd04c 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -283,14 +283,9 @@ pub struct PosixTerminal { stdin_isatty: bool, } -impl PosixTerminal { - /// Create a RAW reader - pub fn create_reader(&self) -> Result { - PosixRawReader::new() - } -} - impl Term for PosixTerminal { + type Reader = PosixRawReader; + fn new() -> PosixTerminal { let term = PosixTerminal { unsupported: is_unsupported_term(), @@ -326,6 +321,11 @@ impl Term for PosixTerminal { get_rows() } + /// Create a RAW reader + fn create_reader(&self) -> Result { + PosixRawReader::new() + } + /// Check if a SIGWINCH signal has been received fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 9a12d53365..4a84806872 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -233,10 +233,6 @@ pub struct Console { } impl Console { - pub fn create_reader(&self) -> Result { - ConsoleRawReader::new() - } - pub fn get_console_screen_buffer_info(&self) -> Result { let mut info = unsafe { mem::zeroed() }; check!(kernel32::GetConsoleScreenBufferInfo(self.stdout_handle, &mut info)); @@ -263,6 +259,8 @@ impl Console { } impl Term for Console { + type Reader = ConsoleRawReader; + fn new() -> Console { use std::ptr; let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); @@ -297,6 +295,10 @@ impl Term for Console { get_rows(self.stdout_handle) } + fn create_reader(&self) -> Result { + ConsoleRawReader::new() + } + fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } From 4dd0cb77d7bf1c9ffeb85aa4f07d378162aea02f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 12 Nov 2016 12:01:56 +0100 Subject: [PATCH 0190/1201] Move enbale/disable_raw_mode in traits --- src/lib.rs | 10 ++-- src/tty/mod.rs | 8 +++ src/tty/test.rs | 16 +++--- src/tty/unix.rs | 99 ++++++++++++++++------------------- src/tty/windows.rs | 127 +++++++++++++++++++++++---------------------- 5 files changed, 132 insertions(+), 128 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a4053a4994..957bcf9033 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,7 @@ use std::path::Path; use std::result; #[cfg(unix)] use nix::sys::signal; -use tty::{RawReader, Terminal, Term}; +use tty::{RawMode, RawReader, Terminal, Term}; use completion::{Completer, longest_common_prefix}; use consts::KeyPress; @@ -936,9 +936,9 @@ fn readline_edit(prompt: &str, } #[cfg(unix)] KeyPress::Ctrl('Z') => { - try!(tty::disable_raw_mode(original_mode)); + try!(original_mode.disable_raw_mode()); try!(signal::raise(signal::SIGSTOP)); - try!(tty::enable_raw_mode()); // TODO original_mode may have changed + try!(s.term.enable_raw_mode()); // TODO original_mode may have changed try!(s.refresh_line()) } // TODO CTRL-_ // undo @@ -1029,14 +1029,14 @@ struct Guard(tty::Mode); impl Drop for Guard { fn drop(&mut self) { let Guard(mode) = *self; - tty::disable_raw_mode(mode); + mode.disable_raw_mode(); } } /// Readline method that will enable RAW mode, call the `readline_edit()` /// method and disable raw mode fn readline_raw(prompt: &str, editor: &mut Editor) -> Result { - let original_mode = try!(tty::enable_raw_mode()); + let original_mode = try!(editor.term.enable_raw_mode()); let guard = Guard(original_mode); let user_input = readline_edit(prompt, editor, original_mode); drop(guard); // try!(disable_raw_mode(original_mode)); diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 1b77010318..4a0475220e 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -3,6 +3,11 @@ use std::io::Write; use ::Result; use consts::KeyPress; +pub trait RawMode: Copy + Sized { + /// Disable RAW mode for the terminal. + fn disable_raw_mode(&self) -> Result<()>; +} + pub trait RawReader: Sized { /// Blocking read of key pressed. fn next_key(&mut self, timeout_ms: i32) -> Result; @@ -14,6 +19,7 @@ pub trait RawReader: Sized { /// Terminal contract pub trait Term: Clone { type Reader: RawReader; + type Mode; fn new() -> Self; /// Check if current terminal can provide a rich line-editing user interface. @@ -26,6 +32,8 @@ pub trait Term: Clone { fn get_rows(&self) -> usize; /// Check if a SIGWINCH signal has been received fn sigwinch(&self) -> bool; + /// Enable RAW mode for the terminal. + fn enable_raw_mode(&self) -> Result; /// Create a RAW reader fn create_reader(&self) -> Result; /// Clear the screen. Used to handle ctrl+l diff --git a/src/tty/test.rs b/src/tty/test.rs index 24b65001b2..0b85e560ef 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -10,15 +10,14 @@ use winapi; use consts::KeyPress; use ::error::ReadlineError; use ::Result; -use super::{RawReader, Term}; +use super::{RawMode, RawReader, Term}; pub type Mode = (); -pub fn enable_raw_mode() -> Result { - Ok(()) -} -pub fn disable_raw_mode(_: Mode) -> Result<()> { - Ok(()) +impl RawMode for Mode { + fn disable_raw_mode(&self) -> Result<()> { + Ok(()) + } } impl<'a> RawReader for Iter<'a, KeyPress> { @@ -91,6 +90,7 @@ impl DummyTerminal { impl Term for DummyTerminal { type Reader = IntoIter; + type Mode = Mode; fn new() -> DummyTerminal { DummyTerminal { keys: Vec::new() } @@ -125,6 +125,10 @@ impl Term for DummyTerminal { false } + fn enable_raw_mode(&self) -> Result { + Ok(()) + } + /// Create a RAW reader fn create_reader(&self) -> Result> { Ok(self.keys.clone().into_iter()) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index b7c9a38be1..f2d6fde0f8 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -12,9 +12,8 @@ use nix::sys::termios; use consts::{self, KeyPress}; use ::Result; use ::error; -use super::{RawReader, Term}; +use super::{RawMode, RawReader, Term}; -pub type Mode = termios::Termios; const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; @@ -30,20 +29,6 @@ const TIOCGWINSZ: libc::c_ulong = 0x5413; #[cfg(all(target_os = "linux", target_env = "musl"))] const TIOCGWINSZ: libc::c_int = 0x5413; -/// Try to get the number of columns in the current terminal, -/// or assume 80 if it fails. -fn get_columns() -> usize { - let (cols, _) = get_win_size(); - cols -} - -/// Try to get the number of rows in the current terminal, -/// or assume 24 if it fails. -fn get_rows() -> usize { - let (_, rows) = get_win_size(); - rows -} - fn get_win_size() -> (usize, usize) { use std::mem::zeroed; use libc::c_ushort; @@ -73,7 +58,7 @@ fn is_unsupported_term() -> bool { Ok(term) => { for iter in &UNSUPPORTED_TERM { if (*iter).eq_ignore_ascii_case(&term) { - return true + return true; } } false @@ -88,40 +73,14 @@ fn is_a_tty(fd: libc::c_int) -> bool { unsafe { libc::isatty(fd) != 0 } } -/// Enable RAW mode for the terminal. -pub fn enable_raw_mode() -> Result { - use nix::errno::Errno::ENOTTY; - use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, - /* OPOST, */ VMIN, VTIME}; - if !is_a_tty(STDIN_FILENO) { - try!(Err(nix::Error::from_errno(ENOTTY))); - } - let original_mode = try!(termios::tcgetattr(STDIN_FILENO)); - let mut raw = original_mode; - // disable BREAK interrupt, CR to NL conversion on input, - // input parity check, strip high bit (bit 8), output flow control - raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - // we don't want raw output, it turns newlines into straight linefeeds - // raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing - raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits) - // disable echoing, canonical mode, extended input processing and signals - raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); - raw.c_cc[VMIN] = 1; // One character-at-a-time input - raw.c_cc[VTIME] = 0; // with blocking read - try!(termios::tcsetattr(STDIN_FILENO, termios::TCSADRAIN, &raw)); - Ok(original_mode) -} - -/// Disable RAW mode for the terminal. -pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { - try!(termios::tcsetattr(STDIN_FILENO, termios::TCSADRAIN, &original_mode)); - Ok(()) -} +pub type Mode = termios::Termios; -fn clear_screen(w: &mut Write) -> Result<()> { - try!(w.write_all(b"\x1b[H\x1b[2J")); - try!(w.flush()); - Ok(()) +impl RawMode for Mode { + /// Disable RAW mode for the terminal. + fn disable_raw_mode(&self) -> Result<()> { + try!(termios::tcsetattr(STDIN_FILENO, termios::TCSADRAIN, self)); + Ok(()) + } } // Rust std::io::Stdin is buffered with no way to know if bytes are available. @@ -284,6 +243,7 @@ pub struct PosixTerminal { impl Term for PosixTerminal { type Reader = PosixRawReader; + type Mode = Mode; fn new() -> PosixTerminal { let term = PosixTerminal { @@ -310,14 +270,41 @@ impl Term for PosixTerminal { // Interactive loop: - /// Get the number of columns in the current terminal. + /// Try to get the number of columns in the current terminal, + /// or assume 80 if it fails. fn get_columns(&self) -> usize { - get_columns() + let (cols, _) = get_win_size(); + cols } - /// Get the number of rows in the current terminal. + /// Try to get the number of rows in the current terminal, + /// or assume 24 if it fails. fn get_rows(&self) -> usize { - get_rows() + let (_, rows) = get_win_size(); + rows + } + + fn enable_raw_mode(&self) -> Result { + use nix::errno::Errno::ENOTTY; + use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, + IXON, /* OPOST, */ VMIN, VTIME}; + if !self.stdin_isatty { + try!(Err(nix::Error::from_errno(ENOTTY))); + } + let original_mode = try!(termios::tcgetattr(STDIN_FILENO)); + let mut raw = original_mode; + // disable BREAK interrupt, CR to NL conversion on input, + // input parity check, strip high bit (bit 8), output flow control + raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + // we don't want raw output, it turns newlines into straight linefeeds + // raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing + raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits) + // disable echoing, canonical mode, extended input processing and signals + raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; // One character-at-a-time input + raw.c_cc[VTIME] = 0; // with blocking read + try!(termios::tcsetattr(STDIN_FILENO, termios::TCSADRAIN, &raw)); + Ok(original_mode) } /// Create a RAW reader @@ -332,7 +319,9 @@ impl Term for PosixTerminal { /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, w: &mut Write) -> Result<()> { - clear_screen(w) + try!(w.write_all(b"\x1b[H\x1b[2J")); + try!(w.flush()); + Ok(()) } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index ab5bc8d298..6cbb0c3d90 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -10,9 +10,8 @@ use winapi; use consts::{self, KeyPress}; use ::error; use ::Result; -use super::{RawReader, Term}; +use super::{RawMode, RawReader, Term}; -pub type Mode = winapi::DWORD; const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; const STDOUT_FILENO: winapi::DWORD = winapi::STD_OUTPUT_HANDLE; @@ -40,16 +39,6 @@ macro_rules! check { }; } -fn get_columns(handle: winapi::HANDLE) -> usize { - let (cols, _) = get_win_size(handle); - cols -} - -fn get_rows(handle: winapi::HANDLE) -> usize { - let (_, rows) = get_win_size(handle); - rows -} - fn get_win_size(handle: winapi::HANDLE) -> (usize, usize) { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { @@ -58,60 +47,26 @@ fn get_win_size(handle: winapi::HANDLE) -> (usize, usize) { } } -fn get_console_mode(handle: winapi::HANDLE) -> Result { +fn get_console_mode(handle: winapi::HANDLE) -> Result { let mut original_mode = 0; check!(kernel32::GetConsoleMode(handle, &mut original_mode)); Ok(original_mode) } -/// Return whether or not STDIN, STDOUT or STDERR is a TTY -fn is_a_tty(fd: winapi::DWORD) -> bool { - let handle = get_std_handle(fd); - match handle { - Ok(handle) => { - // If this function doesn't fail then fd is a TTY - get_console_mode(handle).is_ok() - } - Err(_) => false, - } -} - -/// Enable raw mode for the TERM -pub fn enable_raw_mode() -> Result { - let handle = try!(get_std_handle(STDIN_FILENO)); - let original_mode = try!(get_console_mode(handle)); - // Disable these modes - let raw = original_mode & - !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT | - winapi::wincon::ENABLE_PROCESSED_INPUT); - // Enable these modes - let raw = raw | winapi::wincon::ENABLE_EXTENDED_FLAGS; - let raw = raw | winapi::wincon::ENABLE_INSERT_MODE; - let raw = raw | winapi::wincon::ENABLE_QUICK_EDIT_MODE; - let raw = raw | winapi::wincon::ENABLE_WINDOW_INPUT; - check!(kernel32::SetConsoleMode(handle, raw)); - Ok(original_mode) -} +pub type Mode = ConsoleMode; -/// Disable Raw mode for the term -pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { - let handle = try!(get_std_handle(STDIN_FILENO)); - check!(kernel32::SetConsoleMode(handle, original_mode)); - Ok(()) +#[derive(Clone,Copy,Debug)] +pub struct ConsoleMode { + original_mode: winapi::DWORD, + stdin_handle: winapi::HANDLE, } -/// Clear the screen. Used to handle ctrl+l -fn clear_screen(info: winapi::CONSOLE_SCREEN_BUFFER_INFO, handle: winapi::HANDLE) -> Result<()> { - let coord = winapi::COORD { X: 0, Y: 0 }; - check!(kernel32::SetConsoleCursorPosition(handle, coord)); - let mut _count = 0; - let n = info.dwSize.X as winapi::DWORD * info.dwSize.Y as winapi::DWORD; - check!(kernel32::FillConsoleOutputCharacterA(handle, - ' ' as winapi::CHAR, - n, - coord, - &mut _count)); - Ok(()) +impl RawMode for Mode { + /// Disable RAW mode for the terminal. + fn disable_raw_mode(&self) -> Result<()> { + check!(kernel32::SetConsoleMode(self.stdin_handle, self.original_mode)); + Ok(()) + } } /// Console input reader @@ -231,6 +186,7 @@ pub type Terminal = Console; #[derive(Clone,Debug)] pub struct Console { stdin_isatty: bool, + stdin_handle: winapi::HANDLE, stdout_handle: winapi::HANDLE, } @@ -262,12 +218,23 @@ impl Console { impl Term for Console { type Reader = ConsoleRawReader; + type Mode = Mode; fn new() -> Console { use std::ptr; + let stdin_handle = get_std_handle(STDIN_FILENO); + let stdin_isatty = match stdin_handle { + Ok(handle) => { + // If this function doesn't fail then fd is a TTY + get_console_mode(handle).is_ok() + } + Err(_) => false, + }; + let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); Console { - stdin_isatty: is_a_tty(STDIN_FILENO), + stdin_isatty: stdin_isatty, + stdin_handle: stdin_handle.unwrap_or(ptr::null_mut()), stdout_handle: stdout_handle, } } @@ -288,15 +255,41 @@ impl Term for Console { /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. fn get_columns(&self) -> usize { - get_columns(self.stdout_handle) + let (cols, _) = get_win_size(self.stdout_handle); + cols } /// Try to get the number of rows in the current terminal, /// or assume 24 if it fails. fn get_rows(&self) -> usize { - get_rows(self.stdout_handle) + let (_, rows) = get_win_size(self.stdout_handle); + rows + } + + /// Enable RAW mode for the terminal. + fn enable_raw_mode(&self) -> Result { + if !self.stdin_isatty { + try!(Err(io::Error::new(io::ErrorKind::Other, + "no stdio handle available for this process"))); + } + let original_mode = try!(get_console_mode(self.stdin_handle)); + // Disable these modes + let raw = original_mode & + !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT | + winapi::wincon::ENABLE_PROCESSED_INPUT); + // Enable these modes + let raw = raw | winapi::wincon::ENABLE_EXTENDED_FLAGS; + let raw = raw | winapi::wincon::ENABLE_INSERT_MODE; + let raw = raw | winapi::wincon::ENABLE_QUICK_EDIT_MODE; + let raw = raw | winapi::wincon::ENABLE_WINDOW_INPUT; + check!(kernel32::SetConsoleMode(self.stdin_handle, raw)); + Ok(Mode { + original_mode: original_mode, + stdin_handle: self.stdin_handle, + }) } + fn create_reader(&self) -> Result { ConsoleRawReader::new() } @@ -305,8 +298,18 @@ impl Term for Console { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } + /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, _: &mut Write) -> Result<()> { let info = try!(self.get_console_screen_buffer_info()); - clear_screen(info, self.stdout_handle) + let coord = winapi::COORD { X: 0, Y: 0 }; + check!(kernel32::SetConsoleCursorPosition(self.stdout_handle, coord)); + let mut _count = 0; + let n = info.dwSize.X as winapi::DWORD * info.dwSize.Y as winapi::DWORD; + check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle, + ' ' as winapi::CHAR, + n, + coord, + &mut _count)); + Ok(()) } } From dccd5c42215f094990de5d5a1cb8b1c6e456c081 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 12 Nov 2016 12:01:56 +0100 Subject: [PATCH 0191/1201] Move enbale/disable_raw_mode in traits --- src/lib.rs | 10 ++-- src/tty/mod.rs | 8 +++ src/tty/test.rs | 16 +++--- src/tty/unix.rs | 97 +++++++++++++++------------------- src/tty/windows.rs | 127 +++++++++++++++++++++++---------------------- 5 files changed, 131 insertions(+), 127 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ebf19d0436..c4a493ec4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,7 @@ use std::path::Path; use std::result; #[cfg(unix)] use nix::sys::signal; -use tty::{RawReader, Terminal, Term}; +use tty::{RawMode, RawReader, Terminal, Term}; use encode_unicode::CharExt; use completion::{Completer, longest_common_prefix}; @@ -931,9 +931,9 @@ fn readline_edit(prompt: &str, } #[cfg(unix)] KeyPress::Ctrl('Z') => { - try!(tty::disable_raw_mode(original_mode)); + try!(original_mode.disable_raw_mode()); try!(signal::raise(signal::SIGSTOP)); - try!(tty::enable_raw_mode()); // TODO original_mode may have changed + try!(s.term.enable_raw_mode()); // TODO original_mode may have changed try!(s.refresh_line()) } // TODO CTRL-_ // undo @@ -1024,14 +1024,14 @@ struct Guard(tty::Mode); impl Drop for Guard { fn drop(&mut self) { let Guard(mode) = *self; - tty::disable_raw_mode(mode); + mode.disable_raw_mode(); } } /// Readline method that will enable RAW mode, call the `readline_edit()` /// method and disable raw mode fn readline_raw(prompt: &str, editor: &mut Editor) -> Result { - let original_mode = try!(tty::enable_raw_mode()); + let original_mode = try!(editor.term.enable_raw_mode()); let guard = Guard(original_mode); let user_input = readline_edit(prompt, editor, original_mode); drop(guard); // try!(disable_raw_mode(original_mode)); diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 1b77010318..4a0475220e 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -3,6 +3,11 @@ use std::io::Write; use ::Result; use consts::KeyPress; +pub trait RawMode: Copy + Sized { + /// Disable RAW mode for the terminal. + fn disable_raw_mode(&self) -> Result<()>; +} + pub trait RawReader: Sized { /// Blocking read of key pressed. fn next_key(&mut self, timeout_ms: i32) -> Result; @@ -14,6 +19,7 @@ pub trait RawReader: Sized { /// Terminal contract pub trait Term: Clone { type Reader: RawReader; + type Mode; fn new() -> Self; /// Check if current terminal can provide a rich line-editing user interface. @@ -26,6 +32,8 @@ pub trait Term: Clone { fn get_rows(&self) -> usize; /// Check if a SIGWINCH signal has been received fn sigwinch(&self) -> bool; + /// Enable RAW mode for the terminal. + fn enable_raw_mode(&self) -> Result; /// Create a RAW reader fn create_reader(&self) -> Result; /// Clear the screen. Used to handle ctrl+l diff --git a/src/tty/test.rs b/src/tty/test.rs index 24b65001b2..0b85e560ef 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -10,15 +10,14 @@ use winapi; use consts::KeyPress; use ::error::ReadlineError; use ::Result; -use super::{RawReader, Term}; +use super::{RawMode, RawReader, Term}; pub type Mode = (); -pub fn enable_raw_mode() -> Result { - Ok(()) -} -pub fn disable_raw_mode(_: Mode) -> Result<()> { - Ok(()) +impl RawMode for Mode { + fn disable_raw_mode(&self) -> Result<()> { + Ok(()) + } } impl<'a> RawReader for Iter<'a, KeyPress> { @@ -91,6 +90,7 @@ impl DummyTerminal { impl Term for DummyTerminal { type Reader = IntoIter; + type Mode = Mode; fn new() -> DummyTerminal { DummyTerminal { keys: Vec::new() } @@ -125,6 +125,10 @@ impl Term for DummyTerminal { false } + fn enable_raw_mode(&self) -> Result { + Ok(()) + } + /// Create a RAW reader fn create_reader(&self) -> Result> { Ok(self.keys.clone().into_iter()) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index d733bfd04c..c204bfcf3b 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -13,9 +13,8 @@ use char_iter; use consts::{self, KeyPress}; use ::Result; use ::error; -use super::{RawReader, Term}; +use super::{RawMode, RawReader, Term}; -pub type Mode = termios::Termios; const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO; const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; @@ -31,20 +30,6 @@ const TIOCGWINSZ: libc::c_ulong = 0x5413; #[cfg(all(target_os = "linux", target_env = "musl"))] const TIOCGWINSZ: libc::c_int = 0x5413; -/// Try to get the number of columns in the current terminal, -/// or assume 80 if it fails. -fn get_columns() -> usize { - let (cols, _) = get_win_size(); - cols -} - -/// Try to get the number of rows in the current terminal, -/// or assume 24 if it fails. -fn get_rows() -> usize { - let (_, rows) = get_win_size(); - rows -} - fn get_win_size() -> (usize, usize) { use std::mem::zeroed; use libc::c_ushort; @@ -89,40 +74,14 @@ fn is_a_tty(fd: libc::c_int) -> bool { unsafe { libc::isatty(fd) != 0 } } -/// Enable RAW mode for the terminal. -pub fn enable_raw_mode() -> Result { - use nix::errno::Errno::ENOTTY; - use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, - /* OPOST, */ VMIN, VTIME}; - if !is_a_tty(STDIN_FILENO) { - try!(Err(nix::Error::from_errno(ENOTTY))); - } - let original_mode = try!(termios::tcgetattr(STDIN_FILENO)); - let mut raw = original_mode; - // disable BREAK interrupt, CR to NL conversion on input, - // input parity check, strip high bit (bit 8), output flow control - raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - // we don't want raw output, it turns newlines into straight linefeeds - // raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing - raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits) - // disable echoing, canonical mode, extended input processing and signals - raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); - raw.c_cc[VMIN] = 1; // One character-at-a-time input - raw.c_cc[VTIME] = 0; // with blocking read - try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &raw)); - Ok(original_mode) -} - -/// Disable RAW mode for the terminal. -pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { - try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &original_mode)); - Ok(()) -} +pub type Mode = termios::Termios; -fn clear_screen(w: &mut Write) -> Result<()> { - try!(w.write_all(b"\x1b[H\x1b[2J")); - try!(w.flush()); - Ok(()) +impl RawMode for Mode { + /// Disable RAW mode for the terminal. + fn disable_raw_mode(&self) -> Result<()> { + try!(termios::tcsetattr(STDIN_FILENO, termios::TCSADRAIN, self)); + Ok(()) + } } // Rust std::io::Stdin is buffered with no way to know if bytes are available. @@ -285,6 +244,7 @@ pub struct PosixTerminal { impl Term for PosixTerminal { type Reader = PosixRawReader; + type Mode = Mode; fn new() -> PosixTerminal { let term = PosixTerminal { @@ -311,14 +271,41 @@ impl Term for PosixTerminal { // Interactive loop: - /// Get the number of columns in the current terminal. + /// Try to get the number of columns in the current terminal, + /// or assume 80 if it fails. fn get_columns(&self) -> usize { - get_columns() + let (cols, _) = get_win_size(); + cols } - /// Get the number of rows in the current terminal. + /// Try to get the number of rows in the current terminal, + /// or assume 24 if it fails. fn get_rows(&self) -> usize { - get_rows() + let (_, rows) = get_win_size(); + rows + } + + fn enable_raw_mode(&self) -> Result { + use nix::errno::Errno::ENOTTY; + use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, + IXON, /* OPOST, */ VMIN, VTIME}; + if !self.stdin_isatty { + try!(Err(nix::Error::from_errno(ENOTTY))); + } + let original_mode = try!(termios::tcgetattr(STDIN_FILENO)); + let mut raw = original_mode; + // disable BREAK interrupt, CR to NL conversion on input, + // input parity check, strip high bit (bit 8), output flow control + raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + // we don't want raw output, it turns newlines into straight linefeeds + // raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing + raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits) + // disable echoing, canonical mode, extended input processing and signals + raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; // One character-at-a-time input + raw.c_cc[VTIME] = 0; // with blocking read + try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &raw)); + Ok(original_mode) } /// Create a RAW reader @@ -333,7 +320,9 @@ impl Term for PosixTerminal { /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, w: &mut Write) -> Result<()> { - clear_screen(w) + try!(w.write_all(b"\x1b[H\x1b[2J")); + try!(w.flush()); + Ok(()) } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 4a84806872..4b2133835d 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -10,9 +10,8 @@ use winapi; use consts::{self, KeyPress}; use ::error; use ::Result; -use super::{RawReader, Term}; +use super::{RawMode, RawReader, Term}; -pub type Mode = winapi::DWORD; const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE; const STDOUT_FILENO: winapi::DWORD = winapi::STD_OUTPUT_HANDLE; @@ -40,16 +39,6 @@ macro_rules! check { }; } -fn get_columns(handle: winapi::HANDLE) -> usize { - let (cols, _) = get_win_size(handle); - cols -} - -fn get_rows(handle: winapi::HANDLE) -> usize { - let (_, rows) = get_win_size(handle); - rows -} - fn get_win_size(handle: winapi::HANDLE) -> (usize, usize) { let mut info = unsafe { mem::zeroed() }; match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } { @@ -58,60 +47,26 @@ fn get_win_size(handle: winapi::HANDLE) -> (usize, usize) { } } -fn get_console_mode(handle: winapi::HANDLE) -> Result { +fn get_console_mode(handle: winapi::HANDLE) -> Result { let mut original_mode = 0; check!(kernel32::GetConsoleMode(handle, &mut original_mode)); Ok(original_mode) } -/// Return whether or not STDIN, STDOUT or STDERR is a TTY -fn is_a_tty(fd: winapi::DWORD) -> bool { - let handle = get_std_handle(fd); - match handle { - Ok(handle) => { - // If this function doesn't fail then fd is a TTY - get_console_mode(handle).is_ok() - } - Err(_) => false, - } -} - -/// Enable raw mode for the TERM -pub fn enable_raw_mode() -> Result { - let handle = try!(get_std_handle(STDIN_FILENO)); - let original_mode = try!(get_console_mode(handle)); - // Disable these modes - let raw = original_mode & - !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT | - winapi::wincon::ENABLE_PROCESSED_INPUT); - // Enable these modes - let raw = raw | winapi::wincon::ENABLE_EXTENDED_FLAGS; - let raw = raw | winapi::wincon::ENABLE_INSERT_MODE; - let raw = raw | winapi::wincon::ENABLE_QUICK_EDIT_MODE; - let raw = raw | winapi::wincon::ENABLE_WINDOW_INPUT; - check!(kernel32::SetConsoleMode(handle, raw)); - Ok(original_mode) -} +pub type Mode = ConsoleMode; -/// Disable Raw mode for the term -pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { - let handle = try!(get_std_handle(STDIN_FILENO)); - check!(kernel32::SetConsoleMode(handle, original_mode)); - Ok(()) +#[derive(Clone,Copy,Debug)] +pub struct ConsoleMode { + original_mode: winapi::DWORD, + stdin_handle: winapi::HANDLE, } -/// Clear the screen. Used to handle ctrl+l -fn clear_screen(info: winapi::CONSOLE_SCREEN_BUFFER_INFO, handle: winapi::HANDLE) -> Result<()> { - let coord = winapi::COORD { X: 0, Y: 0 }; - check!(kernel32::SetConsoleCursorPosition(handle, coord)); - let mut _count = 0; - let n = info.dwSize.X as winapi::DWORD * info.dwSize.Y as winapi::DWORD; - check!(kernel32::FillConsoleOutputCharacterA(handle, - ' ' as winapi::CHAR, - n, - coord, - &mut _count)); - Ok(()) +impl RawMode for Mode { + /// Disable RAW mode for the terminal. + fn disable_raw_mode(&self) -> Result<()> { + check!(kernel32::SetConsoleMode(self.stdin_handle, self.original_mode)); + Ok(()) + } } /// Console input reader @@ -229,6 +184,7 @@ pub type Terminal = Console; #[derive(Clone,Debug)] pub struct Console { stdin_isatty: bool, + stdin_handle: winapi::HANDLE, stdout_handle: winapi::HANDLE, } @@ -260,12 +216,23 @@ impl Console { impl Term for Console { type Reader = ConsoleRawReader; + type Mode = Mode; fn new() -> Console { use std::ptr; + let stdin_handle = get_std_handle(STDIN_FILENO); + let stdin_isatty = match stdin_handle { + Ok(handle) => { + // If this function doesn't fail then fd is a TTY + get_console_mode(handle).is_ok() + } + Err(_) => false, + }; + let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); Console { - stdin_isatty: is_a_tty(STDIN_FILENO), + stdin_isatty: stdin_isatty, + stdin_handle: stdin_handle.unwrap_or(ptr::null_mut()), stdout_handle: stdout_handle, } } @@ -286,15 +253,41 @@ impl Term for Console { /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. fn get_columns(&self) -> usize { - get_columns(self.stdout_handle) + let (cols, _) = get_win_size(self.stdout_handle); + cols } /// Try to get the number of rows in the current terminal, /// or assume 24 if it fails. fn get_rows(&self) -> usize { - get_rows(self.stdout_handle) + let (_, rows) = get_win_size(self.stdout_handle); + rows + } + + /// Enable RAW mode for the terminal. + fn enable_raw_mode(&self) -> Result { + if !self.stdin_isatty { + try!(Err(io::Error::new(io::ErrorKind::Other, + "no stdio handle available for this process"))); + } + let original_mode = try!(get_console_mode(self.stdin_handle)); + // Disable these modes + let raw = original_mode & + !(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT | + winapi::wincon::ENABLE_PROCESSED_INPUT); + // Enable these modes + let raw = raw | winapi::wincon::ENABLE_EXTENDED_FLAGS; + let raw = raw | winapi::wincon::ENABLE_INSERT_MODE; + let raw = raw | winapi::wincon::ENABLE_QUICK_EDIT_MODE; + let raw = raw | winapi::wincon::ENABLE_WINDOW_INPUT; + check!(kernel32::SetConsoleMode(self.stdin_handle, raw)); + Ok(Mode { + original_mode: original_mode, + stdin_handle: self.stdin_handle, + }) } + fn create_reader(&self) -> Result { ConsoleRawReader::new() } @@ -303,8 +296,18 @@ impl Term for Console { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } + /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, _: &mut Write) -> Result<()> { let info = try!(self.get_console_screen_buffer_info()); - clear_screen(info, self.stdout_handle) + let coord = winapi::COORD { X: 0, Y: 0 }; + check!(kernel32::SetConsoleCursorPosition(self.stdout_handle, coord)); + let mut _count = 0; + let n = info.dwSize.X as winapi::DWORD * info.dwSize.Y as winapi::DWORD; + check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle, + ' ' as winapi::CHAR, + n, + coord, + &mut _count)); + Ok(()) } } From 1e84808d7c8af63a330a8cde6b5a82ffab9faf22 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 12 Nov 2016 12:13:02 +0100 Subject: [PATCH 0192/1201] Oops --- src/tty/unix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index c204bfcf3b..b3aa8285ee 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -304,7 +304,7 @@ impl Term for PosixTerminal { raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); raw.c_cc[VMIN] = 1; // One character-at-a-time input raw.c_cc[VTIME] = 0; // with blocking read - try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &raw)); + try!(termios::tcsetattr(STDIN_FILENO, termios::TCSADRAIN, &raw)); Ok(original_mode) } From a6f36b6db285e5c5a89ef27c3988cc13e38282c9 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 12 Nov 2016 14:36:23 +0100 Subject: [PATCH 0193/1201] Fix Ctrl-Z (#20) --- src/lib.rs | 4 +--- src/tty/test.rs | 5 +++++ src/tty/unix.rs | 8 ++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 957bcf9033..952ddcd3c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,8 +43,6 @@ use std::io::{self, Write}; use std::mem; use std::path::Path; use std::result; -#[cfg(unix)] -use nix::sys::signal; use tty::{RawMode, RawReader, Terminal, Term}; use completion::{Completer, longest_common_prefix}; @@ -937,7 +935,7 @@ fn readline_edit(prompt: &str, #[cfg(unix)] KeyPress::Ctrl('Z') => { try!(original_mode.disable_raw_mode()); - try!(signal::raise(signal::SIGSTOP)); + try!(tty::suspend()); try!(s.term.enable_raw_mode()); // TODO original_mode may have changed try!(s.refresh_line()) } diff --git a/src/tty/test.rs b/src/tty/test.rs index 0b85e560ef..19ce23ba5b 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -140,3 +140,8 @@ impl Term for DummyTerminal { Ok(()) } } + +#[cfg(unix)] +pub fn suspend() -> Result<()> { + Ok(()) +} diff --git a/src/tty/unix.rs b/src/tty/unix.rs index f2d6fde0f8..a19bb3b945 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -325,6 +325,14 @@ impl Term for PosixTerminal { } } +#[cfg(unix)] +pub fn suspend() -> Result<()> { + // For macos: + try!(signal::kill(nix::unistd::getppid(), signal::SIGTSTP)); + try!(signal::kill(nix::unistd::getpid(), signal::SIGTSTP)); + Ok(()) +} + #[cfg(all(unix,test))] mod test { #[test] From a304bb4e4bcd6f9b605084307bd097250b26745e Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 13 Nov 2016 10:04:52 +0100 Subject: [PATCH 0194/1201] Use rustup --- appveyor.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 53bfcf076b..1f8b930d34 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,9 +1,9 @@ environment: - TARGET: nightly-x86_64-pc-windows-gnu + TARGET: nightly-x86_64-pc-windows-msvc install: - - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" -FileName "rust-install.exe" - - ps: .\rust-install.exe /VERYSILENT /NORESTART /DIR="C:\rust" | Out-Null - - ps: $env:PATH="$env:PATH;C:\rust\bin" + - curl -sSf -o rustup-init.exe https://win.rustup.rs/ + - rustup-init.exe -y --default-host %TARGET% + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - rustc -V - cargo -V From 02c3a746ffbfae36a3bac78e88ab7915782b396a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 13 Nov 2016 10:09:16 +0100 Subject: [PATCH 0195/1201] Try to fix appveyor build --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1f8b930d34..bad02953ff 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ environment: - TARGET: nightly-x86_64-pc-windows-msvc + TARGET: nightly-msvc install: - curl -sSf -o rustup-init.exe https://win.rustup.rs/ - rustup-init.exe -y --default-host %TARGET% From f23a98684b90a196990145e8d55479b9e63f3c2a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 13 Nov 2016 10:17:25 +0100 Subject: [PATCH 0196/1201] Try to fix appveyor build --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index bad02953ff..a1abe76ddc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,8 @@ environment: - TARGET: nightly-msvc + TARGET: x86_64-pc-windows-msvc install: - curl -sSf -o rustup-init.exe https://win.rustup.rs/ - - rustup-init.exe -y --default-host %TARGET% + - rustup-init.exe -y --default-host %TARGET% --default-toolchain nightly - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - rustc -V - cargo -V From 23b08b5c6c28a0de3606f6dda50623ddd7eb2c70 Mon Sep 17 00:00:00 2001 From: Donald Huang Date: Thu, 3 Nov 2016 13:16:30 -0700 Subject: [PATCH 0197/1201] fix autocomplete cancel When you cycle through all the options and return to your original uncompleted input, we should update the State. --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 4f2dd57048..4eb15d4468 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -570,6 +570,9 @@ fn complete_line(rdr: &mut R, return Ok(None); } _ => { + if i == candidates.len() { + s.snapshot(); + } break; } } From 1ae47dcd27aee4407c2b557f10fd9127b8bcb822 Mon Sep 17 00:00:00 2001 From: Katsu K Date: Sun, 20 Nov 2016 09:50:20 -0500 Subject: [PATCH 0198/1201] Update travis configs --- .travis.yml | 2 +- deploy-docs.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a328ee24eb..76221a40dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,4 +13,4 @@ after_success: | bash deploy-docs.sh env: global: - secure: CEgJhYJN0LBGdrhBfeFywxPLKJLnjgAhu2H1A7Gl8r7PGhSlvMjLs1CgLluD83pUxrtxAxLxT/I3bJeUhPI5fbxwxfXO7V48yYqivAx11f0FCnvkBFRcxFCysZLazgEFpttDaxwySC69CL+uwoP93F4lO/YKulyUqiEbDdJsZdM= + secure: "XxaPXHiVplTwMaAytYC0VQR/nNnm7SJVzXiUuaVEjssHip0Uje/4f3vGqtJjnD70FfxwNWQKiSYOcbYjWPlsJeANRt4ZoCsRt5eLGUZ+wH79n1fOkp5EIpFT/isjCB51A4n8PRUvuWfQ2OtNNeGLL6akMxt19sHdXoiQkLOe338=" diff --git a/deploy-docs.sh b/deploy-docs.sh index 907a318ac4..a2c126c181 100644 --- a/deploy-docs.sh +++ b/deploy-docs.sh @@ -17,4 +17,4 @@ touch . git add -A . git commit -m "rebuild pages at ${rev}" -git push -q upstream HEAD:gh-pages +git push -q upstream HEAD:gh-pages > /dev/null 2>&1 From 41074a55e94752605ffe33b22bee3b8fcaf93c1a Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 09:13:55 +0100 Subject: [PATCH 0199/1201] Retry if read was interrupted by a signal https://github.com/antirez/linenoise/pull/70 --- src/tty/unix.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index a19bb3b945..d972a6d31b 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -89,15 +89,20 @@ struct StdinRaw {} impl Read for StdinRaw { fn read(&mut self, buf: &mut [u8]) -> io::Result { - let res = unsafe { - libc::read(STDIN_FILENO, - buf.as_mut_ptr() as *mut libc::c_void, - buf.len() as libc::size_t) - }; - if res == -1 { - Err(io::Error::last_os_error()) - } else { - Ok(res as usize) + loop { + let res = unsafe { + libc::read(STDIN_FILENO, + buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as libc::size_t) + }; + if res == -1 { + let error = io::Error::last_os_error(); + if error.kind() != io::ErrorKind::Interrupted { + return Err(error); + } + } else { + return Ok(res as usize); + } } } } From f8a970589436dc30dbbb718fa323cec2cb03754f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 09:16:21 +0100 Subject: [PATCH 0200/1201] Added escape sequence for Home ([1~) and End ([4~) keys for xterm https://github.com/antirez/linenoise/pull/76 --- src/tty/unix.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index d972a6d31b..44bad32302 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -129,9 +129,9 @@ impl PosixRawReader { let seq3 = try!(self.next_char()); if seq3 == '~' { match seq2 { - // '1' => Ok(KeyPress::Home), + '1' => Ok(KeyPress::Home), // xterm '3' => Ok(KeyPress::Delete), - // '4' => Ok(KeyPress::End), + '4' => Ok(KeyPress::End), // xterm '5' => Ok(KeyPress::PageUp), '6' => Ok(KeyPress::PageDown), '7' => Ok(KeyPress::Home), From 35db058ceaa36c8bc6c306ac896877b3c49f8891 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 10:30:54 +0100 Subject: [PATCH 0201/1201] Fix insecure history file creation. https://github.com/antirez/linenoise/commit/c894b9e59f02203dbe4e2be657572cf88c4230c3 --- src/history.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/history.rs b/src/history.rs index 12f6f5d8d1..6ce5011501 100644 --- a/src/history.rs +++ b/src/history.rs @@ -103,7 +103,15 @@ impl History { if self.is_empty() { return Ok(()); } + // TODO umask let file = try!(File::create(path)); + if cfg!(unix) { + use libc; + use std::os::unix::io::AsRawFd; + unsafe { + libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR); + } + } let mut wtr = BufWriter::new(file); for entry in &self.entries { try!(wtr.write_all(&entry.as_bytes())); From bb502d7e59e7a96eac3a3dfc5fe5e87165fd02fd Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 10:42:53 +0100 Subject: [PATCH 0202/1201] Fix insecure history file creation. --- src/history.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/history.rs b/src/history.rs index 6ce5011501..1f88916290 100644 --- a/src/history.rs +++ b/src/history.rs @@ -6,6 +6,10 @@ use std::fs::File; use std::iter::DoubleEndedIterator; use std::ops::Index; use std::path::Path; +#[cfg(unix)] +use libc; +#[cfg(unix)] +use std::os::unix::io::AsRawFd; use super::Result; use config::{Config, HistoryDuplicates}; @@ -103,11 +107,19 @@ impl History { if self.is_empty() { return Ok(()); } - // TODO umask - let file = try!(File::create(path)); + let old_umask = if cfg!(unix) { + unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) } + } else { + 0 + }; + let f = File::create(path); + if cfg!(unix) { + unsafe { + libc::umask(old_umask); + } + } + let file = try!(f); if cfg!(unix) { - use libc; - use std::os::unix::io::AsRawFd; unsafe { libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR); } From 00f349ce1fb2b0ec74ff8fbbde7a446e993a1d71 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 11:00:13 +0100 Subject: [PATCH 0203/1201] Fix Config doc. --- examples/example.rs | 12 ++++++------ src/config.rs | 5 ++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 63e88281e5..2ebea5dc35 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -30,18 +30,18 @@ fn main() { Ok(line) => { rl.add_history_entry(line.as_ref()); println!("Line: {}", line); - }, + } Err(ReadlineError::Interrupted) => { println!("CTRL-C"); - break - }, + break; + } Err(ReadlineError::Eof) => { println!("CTRL-D"); - break - }, + break; + } Err(err) => { println!("Error: {:?}", err); - break + break; } } } diff --git a/src/config.rs b/src/config.rs index 4d09c79653..2708527e8d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,8 +3,7 @@ use std::default::Default; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Config { - /// When listing completion alternatives, only display - /// one screen of possibilities at a time. + /// Maximum number of entries in History. max_history_size: usize, history_duplicates: HistoryDuplicates, history_ignore_space: bool, @@ -21,7 +20,7 @@ impl Config { Builder::new() } - /// Tell the maximum length for the history. + /// Tell the maximum length (i.e. number of entries) for the history. pub fn max_history_size(&self) -> usize { self.max_history_size } From 1a337aea9384a6e6e7ec40a68fda4bbe5903211b Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 12 Nov 2016 14:36:23 +0100 Subject: [PATCH 0204/1201] Fix Ctrl-Z (#20) --- src/lib.rs | 4 +--- src/tty/test.rs | 5 +++++ src/tty/unix.rs | 8 ++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6f8cd7e23d..129c9e888d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,8 +44,6 @@ use std::io::{self, Write}; use std::mem; use std::path::Path; use std::result; -#[cfg(unix)] -use nix::sys::signal; use tty::{RawMode, RawReader, Terminal, Term}; use encode_unicode::CharExt; @@ -935,7 +933,7 @@ fn readline_edit(prompt: &str, #[cfg(unix)] KeyPress::Ctrl('Z') => { try!(original_mode.disable_raw_mode()); - try!(signal::raise(signal::SIGSTOP)); + try!(tty::suspend()); try!(s.term.enable_raw_mode()); // TODO original_mode may have changed try!(s.refresh_line()) } diff --git a/src/tty/test.rs b/src/tty/test.rs index 0b85e560ef..19ce23ba5b 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -140,3 +140,8 @@ impl Term for DummyTerminal { Ok(()) } } + +#[cfg(unix)] +pub fn suspend() -> Result<()> { + Ok(()) +} diff --git a/src/tty/unix.rs b/src/tty/unix.rs index b3aa8285ee..25deb92f9e 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -326,6 +326,14 @@ impl Term for PosixTerminal { } } +#[cfg(unix)] +pub fn suspend() -> Result<()> { + // For macos: + try!(signal::kill(nix::unistd::getppid(), signal::SIGTSTP)); + try!(signal::kill(nix::unistd::getpid(), signal::SIGTSTP)); + Ok(()) +} + #[cfg(all(unix,test))] mod test { #[test] From 02e34fb47821c2c827f3ab44f5e8dbb47456f1d5 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 09:13:55 +0100 Subject: [PATCH 0205/1201] Retry if read was interrupted by a signal https://github.com/antirez/linenoise/pull/70 --- src/tty/unix.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 25deb92f9e..75cdc88b0a 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -90,15 +90,20 @@ struct StdinRaw {} impl Read for StdinRaw { fn read(&mut self, buf: &mut [u8]) -> io::Result { - let res = unsafe { - libc::read(STDIN_FILENO, - buf.as_mut_ptr() as *mut libc::c_void, - buf.len() as libc::size_t) - }; - if res == -1 { - Err(io::Error::last_os_error()) - } else { - Ok(res as usize) + loop { + let res = unsafe { + libc::read(STDIN_FILENO, + buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as libc::size_t) + }; + if res == -1 { + let error = io::Error::last_os_error(); + if error.kind() != io::ErrorKind::Interrupted { + return Err(error); + } + } else { + return Ok(res as usize); + } } } } From 999877877cb06cc1290f01d8ac3068cccc73abfa Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 09:16:21 +0100 Subject: [PATCH 0206/1201] Added escape sequence for Home ([1~) and End ([4~) keys for xterm https://github.com/antirez/linenoise/pull/76 --- src/tty/unix.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 75cdc88b0a..5a6b7aa59e 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -130,9 +130,9 @@ impl PosixRawReader { let seq3 = try!(self.next_char()); if seq3 == '~' { match seq2 { - // '1' => Ok(KeyPress::Home), + '1' => Ok(KeyPress::Home), // xterm '3' => Ok(KeyPress::Delete), - // '4' => Ok(KeyPress::End), + '4' => Ok(KeyPress::End), // xterm '5' => Ok(KeyPress::PageUp), '6' => Ok(KeyPress::PageDown), '7' => Ok(KeyPress::Home), From 9d6df7a6d87dfad91a1cb9cf393223acdcf8cc90 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 10:30:54 +0100 Subject: [PATCH 0207/1201] Fix insecure history file creation. https://github.com/antirez/linenoise/commit/c894b9e59f02203dbe4e2be657572cf88c4230c3 --- src/history.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/history.rs b/src/history.rs index 29d2ca7ed1..971028fd15 100644 --- a/src/history.rs +++ b/src/history.rs @@ -101,7 +101,15 @@ impl History { if self.is_empty() { return Ok(()); } + // TODO umask let file = try!(File::create(path)); + if cfg!(unix) { + use libc; + use std::os::unix::io::AsRawFd; + unsafe { + libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR); + } + } let mut wtr = BufWriter::new(file); for entry in &self.entries { try!(wtr.write_all(&entry.as_bytes())); From 06d8208f151686a209315693c420ac97522c61bd Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 10:42:53 +0100 Subject: [PATCH 0208/1201] Fix insecure history file creation. --- src/history.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/history.rs b/src/history.rs index 971028fd15..bebcf4e064 100644 --- a/src/history.rs +++ b/src/history.rs @@ -6,6 +6,10 @@ use std::fs::File; use std::iter::DoubleEndedIterator; use std::ops::Index; use std::path::Path; +#[cfg(unix)] +use libc; +#[cfg(unix)] +use std::os::unix::io::AsRawFd; use super::Result; use config::{Config, HistoryDuplicates}; @@ -101,11 +105,19 @@ impl History { if self.is_empty() { return Ok(()); } - // TODO umask - let file = try!(File::create(path)); + let old_umask = if cfg!(unix) { + unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) } + } else { + 0 + }; + let f = File::create(path); + if cfg!(unix) { + unsafe { + libc::umask(old_umask); + } + } + let file = try!(f); if cfg!(unix) { - use libc; - use std::os::unix::io::AsRawFd; unsafe { libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR); } From 9ff6ff55aff6666ef8e75a5042236b6979ea00b3 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 11:00:13 +0100 Subject: [PATCH 0209/1201] Fix Config doc. --- examples/example.rs | 12 ++++++------ src/config.rs | 5 ++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 63e88281e5..2ebea5dc35 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -30,18 +30,18 @@ fn main() { Ok(line) => { rl.add_history_entry(line.as_ref()); println!("Line: {}", line); - }, + } Err(ReadlineError::Interrupted) => { println!("CTRL-C"); - break - }, + break; + } Err(ReadlineError::Eof) => { println!("CTRL-D"); - break - }, + break; + } Err(err) => { println!("Error: {:?}", err); - break + break; } } } diff --git a/src/config.rs b/src/config.rs index 4d09c79653..2708527e8d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,8 +3,7 @@ use std::default::Default; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Config { - /// When listing completion alternatives, only display - /// one screen of possibilities at a time. + /// Maximum number of entries in History. max_history_size: usize, history_duplicates: HistoryDuplicates, history_ignore_space: bool, @@ -21,7 +20,7 @@ impl Config { Builder::new() } - /// Tell the maximum length for the history. + /// Tell the maximum length (i.e. number of entries) for the history. pub fn max_history_size(&self) -> usize { self.max_history_size } From c857f586ab935f194ec4056691cc714a0f24dacc Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 12:03:28 +0100 Subject: [PATCH 0210/1201] Introduce with_config construstor to ease migration --- examples/example.rs | 2 +- src/history.rs | 14 ++++++++------ src/lib.rs | 19 ++++++++++--------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 2ebea5dc35..e97650cedc 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -19,7 +19,7 @@ fn main() { .completion_type(CompletionType::List) .build(); let c = FilenameCompleter::new(); - let mut rl = Editor::new(config); + let mut rl = Editor::with_config(config); rl.set_completer(Some(c)); if rl.load_history("history.txt").is_err() { println!("No previous history."); diff --git a/src/history.rs b/src/history.rs index 1f88916290..1de124432d 100644 --- a/src/history.rs +++ b/src/history.rs @@ -29,7 +29,10 @@ pub struct History { } impl History { - pub fn new(config: Config) -> History { + pub fn new() -> History { + Self::with_config(Config::default()) + } + pub fn with_config(config: Config) -> History { History { entries: VecDeque::new(), max_len: config.max_history_size(), @@ -228,7 +231,7 @@ mod tests { use super::{Direction, History}; fn init() -> History { - let mut history = History::new(Config::default()); + let mut history = History::new(); assert!(history.add("line1")); assert!(history.add("line2")); assert!(history.add("line3")); @@ -237,9 +240,7 @@ mod tests { #[test] fn new() { - let config = Config::default(); - let history = History::new(config); - assert_eq!(config.max_history_size(), history.max_len); + let history = History::new(); assert_eq!(0, history.entries.len()); } @@ -248,7 +249,8 @@ mod tests { let config = Config::builder() .history_ignore_space(true) .build(); - let mut history = History::new(config); + let mut history = History::with_config(config); + assert_eq!(config.max_history_size(), history.max_len); assert!(history.add("line1")); assert!(history.add("line2")); assert!(!history.add("line2")); diff --git a/src/lib.rs b/src/lib.rs index 952ddcd3c3..59ae361f62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,7 @@ //! Usage //! //! ``` -//! let config = rustyline::Config::default(); -//! let mut rl = rustyline::Editor::<()>::new(config); +//! let mut rl = rustyline::Editor::<()>::new(); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -1061,11 +1060,15 @@ pub struct Editor { } impl Editor { - pub fn new(config: Config) -> Editor { + pub fn new() -> Editor { + Self::with_config(Config::default()) + } + + pub fn with_config(config: Config) -> Editor { let term = Terminal::new(); Editor { term: term, - history: History::new(config), + history: History::with_config(config), completer: None, kill_ring: KillRing::new(60), config: config, @@ -1115,8 +1118,7 @@ impl Editor { } /// ``` - /// let config = rustyline::Config::default(); - /// let mut rl = rustyline::Editor::<()>::new(config); + /// let mut rl = rustyline::Editor::<()>::new(); /// for readline in rl.iter("> ") { /// match readline { /// Ok(line) => { @@ -1203,8 +1205,7 @@ mod test { } fn init_editor(keys: &[KeyPress]) -> Editor<()> { - let config = Config::default(); - let mut editor = Editor::<()>::new(config); + let mut editor = Editor::<()>::new(); editor.term.keys.extend(keys.iter().cloned()); editor } @@ -1214,7 +1215,7 @@ mod test { let mut out = ::std::io::sink(); let line = "current edited line"; let mut s = init_state(&mut out, line, 6, 80); - let mut history = History::new(Config::default()); + let mut history = History::new(); history.add("line0"); history.add("line1"); s.history_index = history.len(); From 9aebacc38a967187d1b53b3f7995973e287ac55d Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 12:03:28 +0100 Subject: [PATCH 0211/1201] Introduce with_config construstor to ease migration --- examples/example.rs | 2 +- src/history.rs | 16 +++++++++------- src/lib.rs | 19 ++++++++++--------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 63e88281e5..819f5f1573 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -19,7 +19,7 @@ fn main() { .completion_type(CompletionType::List) .build(); let c = FilenameCompleter::new(); - let mut rl = Editor::new(config); + let mut rl = Editor::with_config(config); rl.set_completer(Some(c)); if rl.load_history("history.txt").is_err() { println!("No previous history."); diff --git a/src/history.rs b/src/history.rs index 29d2ca7ed1..be4b948e6b 100644 --- a/src/history.rs +++ b/src/history.rs @@ -25,7 +25,10 @@ pub struct History { } impl History { - pub fn new(config: Config) -> History { + pub fn new() -> History { + Self::with_config(Config::default()) + } + pub fn with_config(config: Config) -> History { History { entries: VecDeque::new(), max_len: config.max_history_size(), @@ -205,8 +208,8 @@ mod tests { use super::{Direction, History}; use config::Config; - fn init() -> super::History { - let mut history = History::new(Config::default()); + fn init() -> History { + let mut history = History::new(); assert!(history.add("line1")); assert!(history.add("line2")); assert!(history.add("line3")); @@ -215,9 +218,7 @@ mod tests { #[test] fn new() { - let config = Config::default(); - let history = History::new(config); - assert_eq!(config.max_history_size(), history.max_len); + let history = History::new(); assert_eq!(0, history.entries.len()); } @@ -226,7 +227,8 @@ mod tests { let config = Config::builder() .history_ignore_space(true) .build(); - let mut history = History::new(config); + let mut history = History::with_config(config); + assert_eq!(config.max_history_size(), history.max_len); assert!(history.add("line1")); assert!(history.add("line2")); assert!(!history.add("line2")); diff --git a/src/lib.rs b/src/lib.rs index 6f8cd7e23d..261433a161 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,7 @@ //! Usage //! //! ``` -//! let config = rustyline::Config::default(); -//! let mut rl = rustyline::Editor::<()>::new(config); +//! let mut rl = rustyline::Editor::<()>::new(); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -1061,11 +1060,15 @@ pub struct Editor { } impl Editor { - pub fn new(config: Config) -> Editor { + pub fn new() -> Editor { + Self::with_config(Config::default()) + } + + pub fn with_config(config: Config) -> Editor { let term = Terminal::new(); Editor { term: term, - history: History::new(config), + history: History::with_config(config), completer: None, kill_ring: KillRing::new(60), config: config, @@ -1115,8 +1118,7 @@ impl Editor { } /// ``` - /// let config = rustyline::Config::default(); - /// let mut rl = rustyline::Editor::<()>::new(config); + /// let mut rl = rustyline::Editor::<()>::new(); /// for readline in rl.iter("> ") { /// match readline { /// Ok(line) => { @@ -1202,8 +1204,7 @@ mod test { } fn init_editor(keys: &[KeyPress]) -> Editor<()> { - let config = Config::default(); - let mut editor = Editor::<()>::new(config); + let mut editor = Editor::<()>::new(); editor.term.keys.extend(keys.iter().cloned()); editor } @@ -1213,7 +1214,7 @@ mod test { let mut out = ::std::io::sink(); let line = "current edited line"; let mut s = init_state(&mut out, line, 6, 80); - let mut history = History::new(Config::default()); + let mut history = History::new(); history.add("line0"); history.add("line1"); s.history_index = history.len(); From 0ac463e2c5036f639017426045e6a94a1f67807b Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 12:50:21 +0100 Subject: [PATCH 0212/1201] Try to fix windows build --- src/history.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/history.rs b/src/history.rs index 1de124432d..d7b7cac508 100644 --- a/src/history.rs +++ b/src/history.rs @@ -6,10 +6,7 @@ use std::fs::File; use std::iter::DoubleEndedIterator; use std::ops::Index; use std::path::Path; -#[cfg(unix)] use libc; -#[cfg(unix)] -use std::os::unix::io::AsRawFd; use super::Result; use config::{Config, HistoryDuplicates}; @@ -123,6 +120,7 @@ impl History { } let file = try!(f); if cfg!(unix) { + use std::os::unix::io::AsRawFd; unsafe { libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR); } From 61a381ec1e04f37058a2b9936a36e7d81141328b Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 12:50:21 +0100 Subject: [PATCH 0213/1201] Try to fix windows build --- src/history.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/history.rs b/src/history.rs index bebcf4e064..8ea880be04 100644 --- a/src/history.rs +++ b/src/history.rs @@ -6,10 +6,7 @@ use std::fs::File; use std::iter::DoubleEndedIterator; use std::ops::Index; use std::path::Path; -#[cfg(unix)] use libc; -#[cfg(unix)] -use std::os::unix::io::AsRawFd; use super::Result; use config::{Config, HistoryDuplicates}; @@ -118,6 +115,7 @@ impl History { } let file = try!(f); if cfg!(unix) { + use std::os::unix::io::AsRawFd; unsafe { libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR); } From 8c55a41afb59ed43c2df9527cd42e8220a2d3477 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 13:11:36 +0100 Subject: [PATCH 0214/1201] Try to fix windows build --- src/history.rs | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/history.rs b/src/history.rs index d7b7cac508..5027369542 100644 --- a/src/history.rs +++ b/src/history.rs @@ -6,6 +6,7 @@ use std::fs::File; use std::iter::DoubleEndedIterator; use std::ops::Index; use std::path::Path; +#[cfg(unix)] use libc; use super::Result; @@ -107,24 +108,11 @@ impl History { if self.is_empty() { return Ok(()); } - let old_umask = if cfg!(unix) { - unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) } - } else { - 0 - }; + let old_umask = umask(); let f = File::create(path); - if cfg!(unix) { - unsafe { - libc::umask(old_umask); - } - } + restore_umask(old_umask); let file = try!(f); - if cfg!(unix) { - use std::os::unix::io::AsRawFd; - unsafe { - libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR); - } - } + fix_perm(&file); let mut wtr = BufWriter::new(file); for entry in &self.entries { try!(wtr.write_all(&entry.as_bytes())); @@ -221,6 +209,33 @@ impl<'a> DoubleEndedIterator for Iter<'a> { } } +#[cfg(windows)] +fn umask() -> u16 { + 0 +} +#[cfg(unix)] +fn umask() -> u16 { + unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) } +} +#[cfg(windows)] +fn restore_umask(_: u16) {} +#[cfg(unix)] +fn restore_umask(old_umask: u16) { + unsafe { + libc::umask(old_umask); + } +} + +#[cfg(windows)] +fn fix_perm(file: &File) {} +#[cfg(unix)] +fn fix_perm(file: &File) { + use std::os::unix::io::AsRawFd; + unsafe { + libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR); + } +} + #[cfg(test)] mod tests { extern crate tempdir; From 36b370334635364401365c4d7094f7c930608c42 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 13:11:36 +0100 Subject: [PATCH 0215/1201] Try to fix windows build --- src/history.rs | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/history.rs b/src/history.rs index 8ea880be04..303e0e2c6a 100644 --- a/src/history.rs +++ b/src/history.rs @@ -6,6 +6,7 @@ use std::fs::File; use std::iter::DoubleEndedIterator; use std::ops::Index; use std::path::Path; +#[cfg(unix)] use libc; use super::Result; @@ -102,24 +103,11 @@ impl History { if self.is_empty() { return Ok(()); } - let old_umask = if cfg!(unix) { - unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) } - } else { - 0 - }; + let old_umask = umask(); let f = File::create(path); - if cfg!(unix) { - unsafe { - libc::umask(old_umask); - } - } + restore_umask(old_umask); let file = try!(f); - if cfg!(unix) { - use std::os::unix::io::AsRawFd; - unsafe { - libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR); - } - } + fix_perm(&file); let mut wtr = BufWriter::new(file); for entry in &self.entries { try!(wtr.write_all(&entry.as_bytes())); @@ -216,6 +204,33 @@ impl<'a> DoubleEndedIterator for Iter<'a> { } } +#[cfg(windows)] +fn umask() -> u16 { + 0 +} +#[cfg(unix)] +fn umask() -> u16 { + unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) } +} +#[cfg(windows)] +fn restore_umask(_: u16) {} +#[cfg(unix)] +fn restore_umask(old_umask: u16) { + unsafe { + libc::umask(old_umask); + } +} + +#[cfg(windows)] +fn fix_perm(file: &File) {} +#[cfg(unix)] +fn fix_perm(file: &File) { + use std::os::unix::io::AsRawFd; + unsafe { + libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR); + } +} + #[cfg(test)] mod tests { extern crate tempdir; From d7643c45567d01fc2ce675b3e8c687130a33369e Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 15:13:49 +0100 Subject: [PATCH 0216/1201] Try to fix travis build. --- src/history.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/history.rs b/src/history.rs index 5027369542..7d77f4283d 100644 --- a/src/history.rs +++ b/src/history.rs @@ -214,13 +214,13 @@ fn umask() -> u16 { 0 } #[cfg(unix)] -fn umask() -> u16 { +fn umask() -> libc::mode_t { unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) } } #[cfg(windows)] fn restore_umask(_: u16) {} #[cfg(unix)] -fn restore_umask(old_umask: u16) { +fn restore_umask(old_umask: libc::mode_t) { unsafe { libc::umask(old_umask); } From 95b3cabca6e91e249b4dd8be1a5e3f0ae9e19fc9 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 27 Nov 2016 15:13:49 +0100 Subject: [PATCH 0217/1201] Try to fix travis build. --- src/history.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/history.rs b/src/history.rs index 303e0e2c6a..d383fa57f8 100644 --- a/src/history.rs +++ b/src/history.rs @@ -209,13 +209,13 @@ fn umask() -> u16 { 0 } #[cfg(unix)] -fn umask() -> u16 { +fn umask() -> libc::mode_t { unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) } } #[cfg(windows)] fn restore_umask(_: u16) {} #[cfg(unix)] -fn restore_umask(old_umask: u16) { +fn restore_umask(old_umask: libc::mode_t) { unsafe { libc::umask(old_umask); } From d8d614c431946046d0f6c1ccb2b9541169108193 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 28 Nov 2016 18:35:02 +0100 Subject: [PATCH 0218/1201] Fix warning on windows --- src/history.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/history.rs b/src/history.rs index 7d77f4283d..eab0f0a762 100644 --- a/src/history.rs +++ b/src/history.rs @@ -227,7 +227,7 @@ fn restore_umask(old_umask: libc::mode_t) { } #[cfg(windows)] -fn fix_perm(file: &File) {} +fn fix_perm(_: &File) {} #[cfg(unix)] fn fix_perm(file: &File) { use std::os::unix::io::AsRawFd; From cc20518e61ba22e5c90c035855c2799cc37144bb Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 28 Nov 2016 18:35:02 +0100 Subject: [PATCH 0219/1201] Fix warning on windows --- src/history.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/history.rs b/src/history.rs index d383fa57f8..f9d4e2d28d 100644 --- a/src/history.rs +++ b/src/history.rs @@ -222,7 +222,7 @@ fn restore_umask(old_umask: libc::mode_t) { } #[cfg(windows)] -fn fix_perm(file: &File) {} +fn fix_perm(_: &File) {} #[cfg(unix)] fn fix_perm(file: &File) { use std::os::unix::io::AsRawFd; From 092d37a6dfbb4c0480192ebd060a625d99af79c4 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 4 Dec 2016 09:29:42 +0100 Subject: [PATCH 0220/1201] Use libc winsize and TIOCGWINSZ --- src/tty/unix.rs | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 44bad32302..4e7f5f18ee 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -20,30 +20,12 @@ const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; /// Unsupported Terminals that don't support RAW mode static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"]; -#[cfg(any(target_os = "macos", target_os = "freebsd"))] -const TIOCGWINSZ: libc::c_ulong = 0x40087468; - -#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "android"))] -const TIOCGWINSZ: libc::c_ulong = 0x5413; - -#[cfg(all(target_os = "linux", target_env = "musl"))] -const TIOCGWINSZ: libc::c_int = 0x5413; - fn get_win_size() -> (usize, usize) { use std::mem::zeroed; - use libc::c_ushort; unsafe { - #[repr(C)] - struct winsize { - ws_row: c_ushort, - ws_col: c_ushort, - ws_xpixel: c_ushort, - ws_ypixel: c_ushort, - } - - let mut size: winsize = zeroed(); - match libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size) { + let mut size: libc::winsize = zeroed(); + match libc::ioctl(STDOUT_FILENO, libc::TIOCGWINSZ, &mut size) { 0 => (size.ws_col as usize, size.ws_row as usize), // TODO getCursorPosition _ => (80, 24), } From 4af82d586ba5b7b86f67aec82f9d12fd5acd450c Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 4 Dec 2016 09:39:56 +0100 Subject: [PATCH 0221/1201] Clean Cargo.toml --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 76fa9fed18..fd9af2e367 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ keywords = ["readline"] license = "MIT" [dependencies] -libc = "0.2.7" -unicode-width = "0.1.3" +libc = "0.2" +unicode-width = "0.1" [target.'cfg(unix)'.dependencies] nix = "0.7" @@ -21,4 +21,4 @@ winapi = "0.2" kernel32-sys = "0.2" [dev-dependencies] -tempdir = "0.3.4" +tempdir = "0.3" From 6603c2b99938ca10fecbf2b24a8a0e3ddfe95bc2 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 4 Dec 2016 09:40:56 +0100 Subject: [PATCH 0222/1201] Update TODO list --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a2c8c9c5f..85eeb6d75f 100644 --- a/README.md +++ b/README.md @@ -116,10 +116,11 @@ Meta-U | Upper-case the next word Meta-Y | See Ctrl-Y Meta-BackSpace | Kill from the start of the current word, or, if between words, to the start of the previous word +[Readline Emacs Editing Mode Cheat Sheet](http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf) + ## ToDo - Undos - - Read input with timeout to properly handle single ESC key - expose an API callable from C ## Wine From d1fa71c9d2c1ec1b0385ee207b8ce13031f1f6cd Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 4 Dec 2016 09:29:42 +0100 Subject: [PATCH 0223/1201] Use libc winsize and TIOCGWINSZ --- src/tty/unix.rs | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index b3aa8285ee..a4e43631b7 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -21,30 +21,12 @@ const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO; /// Unsupported Terminals that don't support RAW mode static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"]; -#[cfg(any(target_os = "macos", target_os = "freebsd"))] -const TIOCGWINSZ: libc::c_ulong = 0x40087468; - -#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "android"))] -const TIOCGWINSZ: libc::c_ulong = 0x5413; - -#[cfg(all(target_os = "linux", target_env = "musl"))] -const TIOCGWINSZ: libc::c_int = 0x5413; - fn get_win_size() -> (usize, usize) { use std::mem::zeroed; - use libc::c_ushort; unsafe { - #[repr(C)] - struct winsize { - ws_row: c_ushort, - ws_col: c_ushort, - ws_xpixel: c_ushort, - ws_ypixel: c_ushort, - } - - let mut size: winsize = zeroed(); - match libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size) { + let mut size: libc::winsize = zeroed(); + match libc::ioctl(STDOUT_FILENO, libc::TIOCGWINSZ, &mut size) { 0 => (size.ws_col as usize, size.ws_row as usize), // TODO getCursorPosition _ => (80, 24), } From 0cc2641d399454510d704f06e0bf87c5cace176e Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 10 Dec 2016 19:05:15 +0100 Subject: [PATCH 0224/1201] First draft for vi mode support (#94) --- README.md | 1 + src/config.rs | 17 +++ src/keymap.rs | 338 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- src/line_buffer.rs | 11 ++ src/tty/unix.rs | 2 + src/tty/windows.rs | 4 + 7 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 src/keymap.rs diff --git a/README.md b/README.md index 85eeb6d75f..88e8eb368f 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ Meta-Y | See Ctrl-Y Meta-BackSpace | Kill from the start of the current word, or, if between words, to the start of the previous word [Readline Emacs Editing Mode Cheat Sheet](http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf) +[Terminal codes (ANSI/VT100)](http://wiki.bash-hackers.org/scripting/terminalcodes) ## ToDo diff --git a/src/config.rs b/src/config.rs index 2708527e8d..2585c3e33a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,6 +13,7 @@ pub struct Config { completion_prompt_limit: usize, /// Duration (milliseconds) Rustyline will wait for a character when reading an ambiguous key sequence. keyseq_timeout: i32, + edit_mode: EditMode, } impl Config { @@ -48,6 +49,10 @@ impl Config { pub fn keyseq_timeout(&self) -> i32 { self.keyseq_timeout } + + pub fn edit_mode(&self) -> EditMode { + self.edit_mode + } } impl Default for Config { @@ -59,6 +64,7 @@ impl Default for Config { completion_type: CompletionType::Circular, // TODO Validate completion_prompt_limit: 100, keyseq_timeout: 500, + edit_mode: EditMode::Emacs, } } } @@ -79,6 +85,12 @@ pub enum CompletionType { List, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum EditMode { + Emacs, + Vi, +} + #[derive(Debug)] pub struct Builder { p: Config, @@ -130,6 +142,11 @@ impl Builder { self } + pub fn edit_mode(mut self, edit_mode: EditMode) -> Builder { + self.p.edit_mode = edit_mode; + self + } + pub fn build(self) -> Config { self.p } diff --git a/src/keymap.rs b/src/keymap.rs new file mode 100644 index 0000000000..5acf4c1b19 --- /dev/null +++ b/src/keymap.rs @@ -0,0 +1,338 @@ +use super::Config; +use super::EditMode; +use super::KeyPress; +use super::RawReader; +use super::Result; + +//#[derive(Clone)] +pub enum Cmd { + Abort, // Miscellaneous Command + AcceptLine, // Command For History + BackwardChar, // Command For Moving + BackwardDeleteChar, // Command For Text + BackwardKillWord, // Command For Killing + BackwardWord, // Command For Moving + BeginningOfHistory, // Command For History + BeginningOfLine, // Command For Moving + CapitalizeWord, // Command For Text + CharacterSearch(bool), // Miscellaneous Command (TODO Move right to the next occurance of c) + CharacterSearchBackward(bool), /* Miscellaneous Command (TODO Move left to the previous occurance of c) */ + ClearScreen, // Command For Moving + Complete, // Command For Completion + DeleteChar, // Command For Text + DowncaseWord, // Command For Text + EndOfFile, // Command For Text + EndOfHistory, // Command For History + EndOfLine, // Command For Moving + ForwardChar, // Command For Moving + ForwardSearchHistory, // Command For History + ForwardWord, // Command For Moving + KillLine, // Command For Killing + KillWholeLine, // Command For Killing (TODO Delete current line) + KillWord, // Command For Killing + NextHistory, // Command For History + Noop, + PreviousHistory, // Command For History + QuotedInsert, // Command For Text + Replace, // TODO DeleteChar + SelfInsert + ReverseSearchHistory, // Command For History + SelfInsert, // Command For Text + TransposeChars, // Command For Text + TransposeWords, // Command For Text + Unknown, + UnixLikeDiscard, // Command For Killing + UnixWordRubout, // Command For Killing + UpcaseWord, // Command For Text + Yank, // Command For Killing + YankPop, // Command For Killing +} + +// TODO numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 +pub struct EditState { + mode: EditMode, + // TODO Validate Vi Command, Insert, Visual mode + insert: bool, // vi only ? +} + +impl EditState { + pub fn new(config: &Config) -> EditState { + EditState { + mode: config.edit_mode(), + insert: true, + } + } + + pub fn next_cmd(&mut self, + rdr: &mut R, + config: &Config) + -> Result<(KeyPress, Cmd)> { + match self.mode { + EditMode::Emacs => self.emacs(rdr, config), + EditMode::Vi if self.insert => self.vi_insert(rdr, config), + EditMode::Vi => self.vi_command(rdr, config), + } + } + + fn emacs(&mut self, rdr: &mut R, config: &Config) -> Result<(KeyPress, Cmd)> { + let key = try!(rdr.next_key(config.keyseq_timeout())); + let cmd = match key { + KeyPress::Char(_) => Cmd::SelfInsert, + KeyPress::Esc => Cmd::Abort, // TODO Validate + KeyPress::Ctrl('A') => Cmd::BeginningOfLine, + KeyPress::Home => Cmd::BeginningOfLine, + KeyPress::Ctrl('B') => Cmd::BackwardChar, + KeyPress::Left => Cmd::BackwardChar, + // KeyPress::Ctrl('D') if s.line.is_empty() => Cmd::EndOfFile, + KeyPress::Ctrl('D') => Cmd::DeleteChar, + KeyPress::Delete => Cmd::DeleteChar, + KeyPress::Ctrl('E') => Cmd::EndOfLine, + KeyPress::End => Cmd::EndOfLine, + KeyPress::Ctrl('F') => Cmd::ForwardChar, + KeyPress::Right => Cmd::ForwardChar, + KeyPress::Ctrl('G') => Cmd::Abort, + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, + KeyPress::Backspace => Cmd::BackwardDeleteChar, + KeyPress::Tab => Cmd::Complete, + KeyPress::Ctrl('J') => Cmd::AcceptLine, + KeyPress::Enter => Cmd::AcceptLine, + KeyPress::Ctrl('K') => Cmd::KillLine, + KeyPress::Ctrl('L') => Cmd::ClearScreen, + KeyPress::Ctrl('N') => Cmd::NextHistory, + KeyPress::Down => Cmd::NextHistory, + KeyPress::Ctrl('P') => Cmd::PreviousHistory, + KeyPress::Up => Cmd::PreviousHistory, + KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution + KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, + KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution + KeyPress::Ctrl('T') => Cmd::TransposeChars, + KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, + KeyPress::Ctrl('V') => Cmd::QuotedInsert, + KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Meta('\x08') => Cmd::BackwardKillWord, + KeyPress::Meta('\x7f') => Cmd::BackwardKillWord, + // KeyPress::Meta('-') => { // digit-argument + // } + // KeyPress::Meta('0'...'9') => { // digit-argument + // } + KeyPress::Meta('<') => Cmd::BeginningOfHistory, + KeyPress::Meta('>') => Cmd::EndOfHistory, + KeyPress::Meta('B') => Cmd::BackwardWord, + KeyPress::Meta('C') => Cmd::CapitalizeWord, + KeyPress::Meta('D') => Cmd::KillWord, + KeyPress::Meta('F') => Cmd::ForwardWord, + KeyPress::Meta('L') => Cmd::DowncaseWord, + KeyPress::Meta('T') => Cmd::TransposeWords, + KeyPress::Meta('U') => Cmd::UpcaseWord, + KeyPress::Meta('Y') => Cmd::YankPop, + _ => Cmd::Unknown, + }; + Ok((key, cmd)) + } + + fn vi_command(&mut self, + rdr: &mut R, + config: &Config) + -> Result<(KeyPress, Cmd)> { + let key = try!(rdr.next_key(config.keyseq_timeout())); + let cmd = match key { + KeyPress::Char('$') => Cmd::EndOfLine, + // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket + KeyPress::Char('0') => Cmd::BeginningOfLine, // vi-zero: Vi move to the beginning of line. + // KeyPress::Char('1'...'9') => Cmd::???, // vi-arg-digit + KeyPress::Char('^') => Cmd::BeginningOfLine, // TODO Move to the first non-blank character of line. + KeyPress::Char('a') => { + // vi-append-mode: Vi enter insert mode after the cursor. + self.insert = true; + Cmd::ForwardChar + } + KeyPress::Char('A') => { + // vi-append-eol: Vi enter insert mode at end of line. + self.insert = true; + Cmd::EndOfLine + } + KeyPress::Char('b') => Cmd::BackwardWord, + // TODO KeyPress::Char('B') => Cmd::???, Move one non-blank word left. + KeyPress::Char('c') => { + self.insert = true; + let mvt = try!(rdr.next_key(config.keyseq_timeout())); + match mvt { + KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. + KeyPress::Char('0') => Cmd::UnixLikeDiscard, + KeyPress::Char('c') => Cmd::KillWholeLine, + // TODO KeyPress::Char('f') => ???, + // TODO KeyPress::Char('F') => ???, + KeyPress::Char('h') => Cmd::BackwardDeleteChar, + KeyPress::Char('l') => Cmd::DeleteChar, + KeyPress::Char(' ') => Cmd::DeleteChar, + // TODO KeyPress::Char('t') => ???, + // TODO KeyPress::Char('T') => ???, + KeyPress::Char('w') => Cmd::KillWord, + _ => Cmd::Unknown, + } + } + KeyPress::Char('C') => { + self.insert = true; + Cmd::KillLine + } + KeyPress::Char('d') => { + let mvt = try!(rdr.next_key(config.keyseq_timeout())); + match mvt { + KeyPress::Char('$') => Cmd::KillLine, + KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. + KeyPress::Char('d') => Cmd::KillWholeLine, + // TODO KeyPress::Char('f') => ???, + // TODO KeyPress::Char('F') => ???, + KeyPress::Char('h') => Cmd::BackwardDeleteChar, // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Char('l') => Cmd::DeleteChar, + KeyPress::Char(' ') => Cmd::DeleteChar, + // TODO KeyPress::Char('t') => ???, + // TODO KeyPress::Char('T') => ???, + KeyPress::Char('w') => Cmd::KillWord, + _ => Cmd::Unknown, + } + } + KeyPress::Char('D') => Cmd::KillLine, + // TODO KeyPress::Char('e') => Cmd::???, vi-to-end-word: Vi move to the end of the current word. Move to the end of the current word. + // TODO KeyPress::Char('E') => Cmd::???, vi-end-word: Vi move to the end of the current space delimited word. Move to the end of the current non-blank word. + KeyPress::Char('i') => { + // vi-insert: Vi enter insert mode. + self.insert = true; + Cmd::Noop + } + KeyPress::Char('I') => { + // vi-insert-at-bol: Vi enter insert mode at the beginning of line. + self.insert = true; + Cmd::BeginningOfLine + } + KeyPress::Char('f') => { + // vi-next-char: Vi move to the character specified next. + let ch = try!(rdr.next_key(config.keyseq_timeout())); + match ch { + KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearch(false))), + _ => Cmd::Unknown, + } + } + KeyPress::Char('F') => { + // vi-prev-char: Vi move to the character specified previous. + let ch = try!(rdr.next_key(config.keyseq_timeout())); + match ch { + KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearchBackward(false))), + _ => Cmd::Unknown, + } + } + // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n + KeyPress::Char('p') => Cmd::Yank, // vi-paste-next: Vi paste previous deletion to the right of the cursor. + KeyPress::Char('P') => Cmd::Yank, // vi-paste-prev: Vi paste previous deletion to the left of the cursor. TODO Insert the yanked text before the cursor. + KeyPress::Char('r') => { + // vi-replace-char: Vi replace character under the cursor with the next character typed. + let ch = try!(rdr.next_key(config.keyseq_timeout())); + match ch { + KeyPress::Char(_) => return Ok((ch, Cmd::Replace)), + KeyPress::Esc => Cmd::Noop, + _ => Cmd::Unknown, + } + } + // TODO KeyPress::Char('R') => Cmd::???, vi-replace-mode: Vi enter replace mode. Replaces characters under the cursor. (overwrite-mode) + KeyPress::Char('s') => { + // vi-substitute-char: Vi replace character under the cursor and enter insert mode. + self.insert = true; + Cmd::DeleteChar + } + KeyPress::Char('S') => { + // vi-substitute-line: Vi substitute entire line. + self.insert = true; + Cmd::KillWholeLine + } + KeyPress::Char('t') => { + // vi-to-next-char: Vi move up to the character specified next. + let ch = try!(rdr.next_key(config.keyseq_timeout())); + match ch { + KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearchBackward(true))), + _ => Cmd::Unknown, + } + } + KeyPress::Char('T') => { + // vi-to-prev-char: Vi move up to the character specified previous. + let ch = try!(rdr.next_key(config.keyseq_timeout())); + match ch { + KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearch(true))), + _ => Cmd::Unknown, + } + } + // KeyPress::Char('U') => Cmd::???, // revert-line + KeyPress::Char('w') => Cmd::ForwardWord, // vi-next-word: Vi move to the next word. + // TODO KeyPress::Char('W') => Cmd::???, // vi-next-space-word: Vi move to the next space delimited word. Move one non-blank word right. + KeyPress::Char('x') => Cmd::DeleteChar, // vi-delete: TODO move backward if eol + KeyPress::Char('X') => Cmd::BackwardDeleteChar, + KeyPress::Home => Cmd::BeginningOfLine, + KeyPress::Char('h') => Cmd::BackwardChar, + KeyPress::Left => Cmd::BackwardChar, + KeyPress::Ctrl('D') => Cmd::EndOfFile, + KeyPress::Delete => Cmd::DeleteChar, + KeyPress::End => Cmd::EndOfLine, + KeyPress::Ctrl('G') => Cmd::Abort, + KeyPress::Ctrl('H') => Cmd::BackwardChar, + KeyPress::Char('l') => Cmd::ForwardChar, + KeyPress::Char(' ') => Cmd::ForwardChar, + KeyPress::Right => Cmd::ForwardChar, + KeyPress::Ctrl('L') => Cmd::ClearScreen, + KeyPress::Ctrl('J') => Cmd::AcceptLine, + KeyPress::Enter => Cmd::AcceptLine, + KeyPress::Char('+') => Cmd::NextHistory, + KeyPress::Char('j') => Cmd::NextHistory, + KeyPress::Ctrl('N') => Cmd::NextHistory, + KeyPress::Down => Cmd::NextHistory, + KeyPress::Char('-') => Cmd::PreviousHistory, + KeyPress::Char('k') => Cmd::PreviousHistory, + KeyPress::Ctrl('P') => Cmd::PreviousHistory, + KeyPress::Up => Cmd::PreviousHistory, + KeyPress::Ctrl('K') => Cmd::KillLine, + KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution + KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, + KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, + KeyPress::Ctrl('T') => Cmd::TransposeChars, + KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, + KeyPress::Ctrl('V') => Cmd::QuotedInsert, + KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Esc => Cmd::Noop, + _ => Cmd::Unknown, + }; + Ok((key, cmd)) + } + + fn vi_insert(&mut self, rdr: &mut R, config: &Config) -> Result<(KeyPress, Cmd)> { + let key = try!(rdr.next_key(config.keyseq_timeout())); + let cmd = match key { + KeyPress::Char(_) => Cmd::SelfInsert, + KeyPress::Home => Cmd::BeginningOfLine, + KeyPress::Left => Cmd::BackwardChar, + KeyPress::Ctrl('D') => Cmd::EndOfFile, // vi-eof-maybe + KeyPress::Delete => Cmd::DeleteChar, + KeyPress::End => Cmd::EndOfLine, + KeyPress::Right => Cmd::ForwardChar, + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, + KeyPress::Backspace => Cmd::BackwardDeleteChar, + KeyPress::Tab => Cmd::Complete, + KeyPress::Ctrl('J') => Cmd::AcceptLine, + KeyPress::Enter => Cmd::AcceptLine, + KeyPress::Down => Cmd::NextHistory, + KeyPress::Up => Cmd::PreviousHistory, + KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, + KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, + KeyPress::Ctrl('T') => Cmd::TransposeChars, + KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, + KeyPress::Ctrl('V') => Cmd::QuotedInsert, + KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Esc => { + // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). + self.insert = false; + Cmd::Noop + } + _ => Cmd::Unknown, + }; + Ok((key, cmd)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 59ae361f62..ca2bb25193 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ pub mod completion; mod consts; pub mod error; pub mod history; +mod keymap; mod kill_ring; pub mod line_buffer; pub mod config; @@ -49,7 +50,7 @@ use consts::KeyPress; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; use kill_ring::{Mode, KillRing}; -pub use config::{CompletionType, Config, HistoryDuplicates}; +pub use config::{CompletionType, Config, EditMode, HistoryDuplicates}; /// The error type for I/O and Linux Syscalls (Errno) pub type Result = result::Result; diff --git a/src/line_buffer.rs b/src/line_buffer.rs index abfe1856dc..613fce1cd0 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -183,6 +183,11 @@ impl LineBuffer { } } + /// Replace a single character under the cursor (Vi mode) + pub fn replace_char(&mut self, ch: char) -> Option { + if self.delete() { self.insert(ch) } else { None } + } + /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. pub fn delete(&mut self) -> bool { @@ -206,6 +211,12 @@ impl LineBuffer { } } + /// Kill all characters on the current line. + pub fn kill_whole_line(&mut self) -> Option { + self.move_home(); + self.kill_line() + } + /// Kill the text from point to the end of the line. pub fn kill_line(&mut self) -> Option { if !self.buf.is_empty() && self.pos < self.buf.len() { diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 4e7f5f18ee..b51cdba208 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -152,6 +152,8 @@ impl PosixRawReader { // TODO ESC-R (r): Undo all changes made to this line. match seq1 { '\x08' => Ok(KeyPress::Meta('\x08')), // Backspace + '-' => return Ok(KeyPress::Meta('-')), + '0'...'9' => return Ok(KeyPress::Meta(seq1)), '<' => Ok(KeyPress::Meta('<')), '>' => Ok(KeyPress::Meta('>')), 'b' | 'B' => Ok(KeyPress::Meta('B')), diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 6cbb0c3d90..1ebf108d9d 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -151,6 +151,10 @@ impl RawReader for ConsoleRawReader { let c = try!(orc.unwrap()); if meta { match c { + '-' => return Ok(KeyPress::Meta('-')), + '0'...'9' => return Ok(KeyPress::Meta(c)), + '<' => Ok(KeyPress::Meta('<')), + '>' => Ok(KeyPress::Meta('>')), 'b' | 'B' => return Ok(KeyPress::Meta('B')), 'c' | 'C' => return Ok(KeyPress::Meta('C')), 'd' | 'D' => return Ok(KeyPress::Meta('D')), From 3e293b2ccbc18b66c09b160cfcff65fa71008448 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 11 Dec 2016 11:33:14 +0100 Subject: [PATCH 0225/1201] Second draft for vi mode support (#94) --- src/keymap.rs | 208 +++++++++++++++++++++++++++----------------------- 1 file changed, 114 insertions(+), 94 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 5acf4c1b19..e2a44e5e2d 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -10,13 +10,11 @@ pub enum Cmd { AcceptLine, // Command For History BackwardChar, // Command For Moving BackwardDeleteChar, // Command For Text - BackwardKillWord, // Command For Killing - BackwardWord, // Command For Moving + BackwardKillWord(Word), // Command For Killing + BackwardWord(Word), // Command For Moving BeginningOfHistory, // Command For History BeginningOfLine, // Command For Moving CapitalizeWord, // Command For Text - CharacterSearch(bool), // Miscellaneous Command (TODO Move right to the next occurance of c) - CharacterSearchBackward(bool), /* Miscellaneous Command (TODO Move left to the previous occurance of c) */ ClearScreen, // Command For Moving Complete, // Command For Completion DeleteChar, // Command For Text @@ -26,12 +24,12 @@ pub enum Cmd { EndOfLine, // Command For Moving ForwardChar, // Command For Moving ForwardSearchHistory, // Command For History - ForwardWord, // Command For Moving + ForwardWord(Word), // Command For Moving KillLine, // Command For Killing KillWholeLine, // Command For Killing (TODO Delete current line) - KillWord, // Command For Killing + KillWord(Word), // Command For Killing NextHistory, // Command For History - Noop, + Noop, // TODO PreviousHistory, // Command For History QuotedInsert, // Command For Text Replace, // TODO DeleteChar + SelfInsert @@ -41,16 +39,37 @@ pub enum Cmd { TransposeWords, // Command For Text Unknown, UnixLikeDiscard, // Command For Killing - UnixWordRubout, // Command For Killing + // UnixWordRubout, // = KillWord(Word::BigWord) Command For Killing UpcaseWord, // Command For Text + ViCharSearch(CharSearch), // TODO + ViEndWord(Word), // TODO + ViKillTo(CharSearch), // TODO Yank, // Command For Killing YankPop, // Command For Killing } +pub enum Word { + // non-blanks characters + BigWord, + // alphanumeric characters + Word, + // alphanumeric (and '_') characters + ViWord, +} + +pub enum CharSearch { + Forward(char), + // until + ForwardBefore(char), + Backward(char), + // until + BackwardAfter(char), +} + // TODO numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 pub struct EditState { mode: EditMode, - // TODO Validate Vi Command, Insert, Visual mode + // Vi Command/Alternate, Insert/Input mode insert: bool, // vi only ? } @@ -107,20 +126,20 @@ impl EditState { KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, - KeyPress::Meta('\x08') => Cmd::BackwardKillWord, - KeyPress::Meta('\x7f') => Cmd::BackwardKillWord, + KeyPress::Meta('\x08') => Cmd::BackwardKillWord(Word::Word), + KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(Word::Word), // KeyPress::Meta('-') => { // digit-argument // } // KeyPress::Meta('0'...'9') => { // digit-argument // } KeyPress::Meta('<') => Cmd::BeginningOfHistory, KeyPress::Meta('>') => Cmd::EndOfHistory, - KeyPress::Meta('B') => Cmd::BackwardWord, + KeyPress::Meta('B') => Cmd::BackwardWord(Word::Word), KeyPress::Meta('C') => Cmd::CapitalizeWord, - KeyPress::Meta('D') => Cmd::KillWord, - KeyPress::Meta('F') => Cmd::ForwardWord, + KeyPress::Meta('D') => Cmd::KillWord(Word::Word), + KeyPress::Meta('F') => Cmd::ForwardWord(Word::Word), KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, @@ -137,10 +156,12 @@ impl EditState { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { KeyPress::Char('$') => Cmd::EndOfLine, + KeyPress::End => Cmd::EndOfLine, // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket KeyPress::Char('0') => Cmd::BeginningOfLine, // vi-zero: Vi move to the beginning of line. + KeyPress::Home => Cmd::BeginningOfLine, // KeyPress::Char('1'...'9') => Cmd::???, // vi-arg-digit - KeyPress::Char('^') => Cmd::BeginningOfLine, // TODO Move to the first non-blank character of line. + KeyPress::Char('^') => Cmd::BeginningOfLine, // vi-first-print TODO Move to the first non-blank character of line. KeyPress::Char('a') => { // vi-append-mode: Vi enter insert mode after the cursor. self.insert = true; @@ -151,79 +172,41 @@ impl EditState { self.insert = true; Cmd::EndOfLine } - KeyPress::Char('b') => Cmd::BackwardWord, - // TODO KeyPress::Char('B') => Cmd::???, Move one non-blank word left. + KeyPress::Char('b') => Cmd::BackwardWord(Word::ViWord), // vi-prev-word + KeyPress::Char('B') => Cmd::BackwardWord(Word::BigWord), KeyPress::Char('c') => { self.insert = true; - let mvt = try!(rdr.next_key(config.keyseq_timeout())); - match mvt { - KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. - KeyPress::Char('0') => Cmd::UnixLikeDiscard, - KeyPress::Char('c') => Cmd::KillWholeLine, - // TODO KeyPress::Char('f') => ???, - // TODO KeyPress::Char('F') => ???, - KeyPress::Char('h') => Cmd::BackwardDeleteChar, - KeyPress::Char('l') => Cmd::DeleteChar, - KeyPress::Char(' ') => Cmd::DeleteChar, - // TODO KeyPress::Char('t') => ???, - // TODO KeyPress::Char('T') => ???, - KeyPress::Char('w') => Cmd::KillWord, - _ => Cmd::Unknown, - } + try!(self.vi_delete_motion(rdr, config, key)) } KeyPress::Char('C') => { self.insert = true; Cmd::KillLine } - KeyPress::Char('d') => { - let mvt = try!(rdr.next_key(config.keyseq_timeout())); - match mvt { - KeyPress::Char('$') => Cmd::KillLine, - KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. - KeyPress::Char('d') => Cmd::KillWholeLine, - // TODO KeyPress::Char('f') => ???, - // TODO KeyPress::Char('F') => ???, - KeyPress::Char('h') => Cmd::BackwardDeleteChar, // vi-delete-prev-char: Vi move to previous character (backspace). - KeyPress::Char('l') => Cmd::DeleteChar, - KeyPress::Char(' ') => Cmd::DeleteChar, - // TODO KeyPress::Char('t') => ???, - // TODO KeyPress::Char('T') => ???, - KeyPress::Char('w') => Cmd::KillWord, - _ => Cmd::Unknown, - } - } + KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), KeyPress::Char('D') => Cmd::KillLine, - // TODO KeyPress::Char('e') => Cmd::???, vi-to-end-word: Vi move to the end of the current word. Move to the end of the current word. - // TODO KeyPress::Char('E') => Cmd::???, vi-end-word: Vi move to the end of the current space delimited word. Move to the end of the current non-blank word. + KeyPress::Char('e') => Cmd::ViEndWord(Word::ViWord), + KeyPress::Char('E') => Cmd::ViEndWord(Word::BigWord), KeyPress::Char('i') => { - // vi-insert: Vi enter insert mode. + // vi-insertion-mode self.insert = true; Cmd::Noop } KeyPress::Char('I') => { - // vi-insert-at-bol: Vi enter insert mode at the beginning of line. + // vi-insert-beg self.insert = true; Cmd::BeginningOfLine } - KeyPress::Char('f') => { - // vi-next-char: Vi move to the character specified next. - let ch = try!(rdr.next_key(config.keyseq_timeout())); - match ch { - KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearch(false))), - _ => Cmd::Unknown, - } - } - KeyPress::Char('F') => { - // vi-prev-char: Vi move to the character specified previous. - let ch = try!(rdr.next_key(config.keyseq_timeout())); - match ch { - KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearchBackward(false))), - _ => Cmd::Unknown, + KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { + // vi-char-search + let cs = try!(self.vi_char_search(rdr, config, c)); + match cs { + Some(cs) => Cmd::ViCharSearch(cs), + None => Cmd::Unknown, } } // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank, // vi-paste-next: Vi paste previous deletion to the right of the cursor. - KeyPress::Char('P') => Cmd::Yank, // vi-paste-prev: Vi paste previous deletion to the left of the cursor. TODO Insert the yanked text before the cursor. + KeyPress::Char('p') => Cmd::Yank, // vi-put + KeyPress::Char('P') => Cmd::Yank, // vi-put TODO Insert the yanked text before the cursor. KeyPress::Char('r') => { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); @@ -244,35 +227,20 @@ impl EditState { self.insert = true; Cmd::KillWholeLine } - KeyPress::Char('t') => { - // vi-to-next-char: Vi move up to the character specified next. - let ch = try!(rdr.next_key(config.keyseq_timeout())); - match ch { - KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearchBackward(true))), - _ => Cmd::Unknown, - } - } - KeyPress::Char('T') => { - // vi-to-prev-char: Vi move up to the character specified previous. - let ch = try!(rdr.next_key(config.keyseq_timeout())); - match ch { - KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearch(true))), - _ => Cmd::Unknown, - } - } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord, // vi-next-word: Vi move to the next word. - // TODO KeyPress::Char('W') => Cmd::???, // vi-next-space-word: Vi move to the next space delimited word. Move one non-blank word right. + KeyPress::Char('w') => Cmd::ForwardWord(Word::ViWord), // vi-next-word + KeyPress::Char('W') => Cmd::ForwardWord(Word::BigWord), // vi-next-word KeyPress::Char('x') => Cmd::DeleteChar, // vi-delete: TODO move backward if eol - KeyPress::Char('X') => Cmd::BackwardDeleteChar, - KeyPress::Home => Cmd::BeginningOfLine, + KeyPress::Char('X') => Cmd::BackwardDeleteChar, // vi-rubout + // KeyPress::Char('y') => Cmd::???, // vi-yank-to + // KeyPress::Char('Y') => Cmd::???, // vi-yank-to KeyPress::Char('h') => Cmd::BackwardChar, + KeyPress::Ctrl('H') => Cmd::BackwardChar, + KeyPress::Backspace => Cmd::BackwardChar, // TODO Validate KeyPress::Left => Cmd::BackwardChar, KeyPress::Ctrl('D') => Cmd::EndOfFile, KeyPress::Delete => Cmd::DeleteChar, - KeyPress::End => Cmd::EndOfLine, KeyPress::Ctrl('G') => Cmd::Abort, - KeyPress::Ctrl('H') => Cmd::BackwardChar, KeyPress::Char('l') => Cmd::ForwardChar, KeyPress::Char(' ') => Cmd::ForwardChar, KeyPress::Right => Cmd::ForwardChar, @@ -294,7 +262,7 @@ impl EditState { KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, KeyPress::Esc => Cmd::Noop, _ => Cmd::Unknown, @@ -324,7 +292,7 @@ impl EditState { KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, KeyPress::Esc => { // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). @@ -335,4 +303,56 @@ impl EditState { }; Ok((key, cmd)) } + + fn vi_delete_motion(&mut self, + rdr: &mut R, + config: &Config, + key: KeyPress) + -> Result { + let mvt = try!(rdr.next_key(config.keyseq_timeout())); + Ok(match mvt { + KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. + KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. + KeyPress::Char('b') => Cmd::BackwardKillWord(Word::ViWord), + KeyPress::Char('B') => Cmd::BackwardKillWord(Word::BigWord), + x if x == key => Cmd::KillWholeLine, + KeyPress::Char('e') => Cmd::KillWord(Word::ViWord), + KeyPress::Char('E') => Cmd::KillWord(Word::BigWord), + KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { + let cs = try!(self.vi_char_search(rdr, config, c)); + match cs { + Some(cs) => Cmd::ViKillTo(cs), + None => Cmd::Unknown, + } + } + KeyPress::Char('h') => Cmd::BackwardDeleteChar, // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, + KeyPress::Backspace => Cmd::BackwardDeleteChar, + KeyPress::Char('l') => Cmd::DeleteChar, + KeyPress::Char(' ') => Cmd::DeleteChar, + KeyPress::Char('w') => Cmd::KillWord(Word::ViWord), + KeyPress::Char('W') => Cmd::KillWord(Word::BigWord), + _ => Cmd::Unknown, + }) + } + + fn vi_char_search(&mut self, + rdr: &mut R, + config: &Config, + cmd: char) + -> Result> { + let ch = try!(rdr.next_key(config.keyseq_timeout())); + Ok(match ch { + KeyPress::Char(ch) => { + Some(match cmd { + 'f' => CharSearch::Forward(ch), + 't' => CharSearch::ForwardBefore(ch), + 'F' => CharSearch::Backward(ch), + 'T' => CharSearch::BackwardAfter(ch), + _ => unreachable!(), + }) + } + _ => None, + }) + } } From e56e5c9fb91992987ebef1513916a386dc14e6eb Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 11 Dec 2016 17:15:21 +0100 Subject: [PATCH 0226/1201] Vi mode support (#94) --- src/keymap.rs | 69 ++++++++++------ src/lib.rs | 213 +++++++++++++++++++++++++------------------------- 2 files changed, 152 insertions(+), 130 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index e2a44e5e2d..7f2e7dbb38 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -4,7 +4,7 @@ use super::KeyPress; use super::RawReader; use super::Result; -//#[derive(Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Cmd { Abort, // Miscellaneous Command AcceptLine, // Command For History @@ -25,16 +25,18 @@ pub enum Cmd { ForwardChar, // Command For Moving ForwardSearchHistory, // Command For History ForwardWord(Word), // Command For Moving + Interrupt, KillLine, // Command For Killing KillWholeLine, // Command For Killing (TODO Delete current line) KillWord(Word), // Command For Killing NextHistory, // Command For History - Noop, // TODO + Noop, PreviousHistory, // Command For History QuotedInsert, // Command For Text - Replace, // TODO DeleteChar + SelfInsert + Replace(char), // TODO DeleteChar + SelfInsert ReverseSearchHistory, // Command For History - SelfInsert, // Command For Text + SelfInsert(char), // Command For Text + Suspend, TransposeChars, // Command For Text TransposeWords, // Command For Text Unknown, @@ -48,6 +50,7 @@ pub enum Cmd { YankPop, // Command For Killing } +#[derive(Debug, Clone, PartialEq)] pub enum Word { // non-blanks characters BigWord, @@ -57,6 +60,7 @@ pub enum Word { ViWord, } +#[derive(Debug, Clone, PartialEq)] pub enum CharSearch { Forward(char), // until @@ -81,10 +85,11 @@ impl EditState { } } - pub fn next_cmd(&mut self, - rdr: &mut R, - config: &Config) - -> Result<(KeyPress, Cmd)> { + pub fn is_emacs_mode(&self) -> bool { + self.mode == EditMode::Emacs + } + + pub fn next_cmd(&mut self, rdr: &mut R, config: &Config) -> Result { match self.mode { EditMode::Emacs => self.emacs(rdr, config), EditMode::Vi if self.insert => self.vi_insert(rdr, config), @@ -92,17 +97,18 @@ impl EditState { } } - fn emacs(&mut self, rdr: &mut R, config: &Config) -> Result<(KeyPress, Cmd)> { + fn emacs(&mut self, rdr: &mut R, config: &Config) -> Result { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { - KeyPress::Char(_) => Cmd::SelfInsert, + KeyPress::Char(c) => Cmd::SelfInsert(c), KeyPress::Esc => Cmd::Abort, // TODO Validate KeyPress::Ctrl('A') => Cmd::BeginningOfLine, KeyPress::Home => Cmd::BeginningOfLine, KeyPress::Ctrl('B') => Cmd::BackwardChar, KeyPress::Left => Cmd::BackwardChar, - // KeyPress::Ctrl('D') if s.line.is_empty() => Cmd::EndOfFile, - KeyPress::Ctrl('D') => Cmd::DeleteChar, + KeyPress::Ctrl('C') => Cmd::Interrupt, + KeyPress::Ctrl('D') => Cmd::EndOfFile, + // KeyPress::Ctrl('D') => Cmd::DeleteChar, KeyPress::Delete => Cmd::DeleteChar, KeyPress::Ctrl('E') => Cmd::EndOfLine, KeyPress::End => Cmd::EndOfLine, @@ -128,6 +134,7 @@ impl EditState { KeyPress::Ctrl('V') => Cmd::QuotedInsert, KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Meta('\x08') => Cmd::BackwardKillWord(Word::Word), KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(Word::Word), // KeyPress::Meta('-') => { // digit-argument @@ -144,15 +151,13 @@ impl EditState { KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, KeyPress::Meta('Y') => Cmd::YankPop, + KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, }; - Ok((key, cmd)) + Ok(cmd) } - fn vi_command(&mut self, - rdr: &mut R, - config: &Config) - -> Result<(KeyPress, Cmd)> { + fn vi_command(&mut self, rdr: &mut R, config: &Config) -> Result { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { KeyPress::Char('$') => Cmd::EndOfLine, @@ -211,7 +216,7 @@ impl EditState { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); match ch { - KeyPress::Char(_) => return Ok((ch, Cmd::Replace)), + KeyPress::Char(c) => Cmd::Replace(c), KeyPress::Esc => Cmd::Noop, _ => Cmd::Unknown, } @@ -238,6 +243,7 @@ impl EditState { KeyPress::Ctrl('H') => Cmd::BackwardChar, KeyPress::Backspace => Cmd::BackwardChar, // TODO Validate KeyPress::Left => Cmd::BackwardChar, + KeyPress::Ctrl('C') => Cmd::Interrupt, KeyPress::Ctrl('D') => Cmd::EndOfFile, KeyPress::Delete => Cmd::DeleteChar, KeyPress::Ctrl('G') => Cmd::Abort, @@ -257,25 +263,34 @@ impl EditState { KeyPress::Up => Cmd::PreviousHistory, KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution - KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, - KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, + KeyPress::Ctrl('R') => { + self.insert = true; // TODO Validate + Cmd::ReverseSearchHistory + } + KeyPress::Ctrl('S') => { + self.insert = true; // TODO Validate + Cmd::ForwardSearchHistory + } KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Esc => Cmd::Noop, + KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, }; - Ok((key, cmd)) + Ok(cmd) } - fn vi_insert(&mut self, rdr: &mut R, config: &Config) -> Result<(KeyPress, Cmd)> { + fn vi_insert(&mut self, rdr: &mut R, config: &Config) -> Result { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { - KeyPress::Char(_) => Cmd::SelfInsert, + KeyPress::Char(c) => Cmd::SelfInsert(c), KeyPress::Home => Cmd::BeginningOfLine, KeyPress::Left => Cmd::BackwardChar, + KeyPress::Ctrl('C') => Cmd::Interrupt, KeyPress::Ctrl('D') => Cmd::EndOfFile, // vi-eof-maybe KeyPress::Delete => Cmd::DeleteChar, KeyPress::End => Cmd::EndOfLine, @@ -294,14 +309,16 @@ impl EditState { KeyPress::Ctrl('V') => Cmd::QuotedInsert, KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Esc => { // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). self.insert = false; Cmd::Noop } + KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, }; - Ok((key, cmd)) + Ok(cmd) } fn vi_delete_motion(&mut self, @@ -310,12 +327,14 @@ impl EditState { key: KeyPress) -> Result { let mvt = try!(rdr.next_key(config.keyseq_timeout())); + if mvt == key { + return Ok(Cmd::KillWholeLine); + } Ok(match mvt { KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. KeyPress::Char('b') => Cmd::BackwardKillWord(Word::ViWord), KeyPress::Char('B') => Cmd::BackwardKillWord(Word::BigWord), - x if x == key => Cmd::KillWholeLine, KeyPress::Char('e') => Cmd::KillWord(Word::ViWord), KeyPress::Char('E') => Cmd::KillWord(Word::BigWord), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { diff --git a/src/lib.rs b/src/lib.rs index ca2bb25193..2f5a801158 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,7 @@ use completion::{Completer, longest_common_prefix}; use consts::KeyPress; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; +use keymap::{CharSearch, Cmd, EditState, Word}; use kill_ring::{Mode, KillRing}; pub use config::{CompletionType, Config, EditMode, HistoryDuplicates}; @@ -68,6 +69,7 @@ struct State<'out, 'prompt> { snapshot: LineBuffer, // Current edited line before history browsing/completion term: Terminal, // terminal byte_buffer: [u8; 4], + edit_state: EditState, } #[derive(Copy, Clone, Debug, Default)] @@ -79,6 +81,7 @@ struct Position { impl<'out, 'prompt> State<'out, 'prompt> { fn new(out: &'out mut Write, term: Terminal, + config: &Config, prompt: &'prompt str, history_index: usize) -> State<'out, 'prompt> { @@ -97,6 +100,19 @@ impl<'out, 'prompt> State<'out, 'prompt> { snapshot: LineBuffer::with_capacity(capacity), term: term, byte_buffer: [0; 4], + edit_state: EditState::new(config), + } + } + + fn next_cmd(&mut self, rdr: &mut R, config: &Config) -> Result { + loop { + let rc = self.edit_state.next_cmd(rdr, config); + if rc.is_err() && self.term.sigwinch() { + self.update_columns(); + try!(self.refresh_line()); + continue; + } + return rc; } } @@ -529,7 +545,7 @@ fn complete_line(rdr: &mut R, s: &mut State, completer: &Completer, config: &Config) - -> Result> { + -> Result> { // get a list of completions let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); // if no completions, we are done @@ -539,7 +555,7 @@ fn complete_line(rdr: &mut R, } else if CompletionType::Circular == config.completion_type() { // Save the current edited line before to overwrite it s.backup(); - let mut key; + let mut cmd; let mut i = 0; loop { // Show completion or original buffer @@ -553,15 +569,15 @@ fn complete_line(rdr: &mut R, s.snapshot(); } - key = try!(rdr.next_key(config.keyseq_timeout())); - match key { - KeyPress::Tab => { + cmd = try!(s.next_cmd(rdr, config)); + match cmd { + Cmd::Complete => { i = (i + 1) % (candidates.len() + 1); // Circular if i == candidates.len() { try!(beep()); } } - KeyPress::Esc => { + Cmd::Abort => { // Re-show original buffer s.snapshot(); if i < candidates.len() { @@ -577,7 +593,7 @@ fn complete_line(rdr: &mut R, } } } - Ok(Some(key)) + Ok(Some(cmd)) } else if CompletionType::List == config.completion_type() { // beep if ambiguous if candidates.len() > 1 { @@ -592,10 +608,10 @@ fn complete_line(rdr: &mut R, } } // we can't complete any further, wait for second tab - let mut key = try!(rdr.next_key(config.keyseq_timeout())); + let mut cmd = try!(s.next_cmd(rdr, config)); // if any character other than tab, pass it to the main loop - if key != KeyPress::Tab { - return Ok(Some(key)); + if cmd != Cmd::Complete { + return Ok(Some(cmd)); } // move cursor to EOL to avoid overwriting the command line let save_pos = s.line.pos(); @@ -607,14 +623,14 @@ fn complete_line(rdr: &mut R, let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len()); try!(write_and_flush(s.out, msg.as_bytes())); s.old_rows += 1; - while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && - key != KeyPress::Char('n') && key != KeyPress::Char('N') && - key != KeyPress::Backspace { - key = try!(rdr.next_key(config.keyseq_timeout())); + while cmd != Cmd::SelfInsert('y') && cmd != Cmd::SelfInsert('Y') && + cmd != Cmd::SelfInsert('n') && cmd != Cmd::SelfInsert('N') && + cmd != Cmd::BackwardDeleteChar { + cmd = try!(s.next_cmd(rdr, config)); } - show_completions = match key { - KeyPress::Char('y') | - KeyPress::Char('Y') => true, + show_completions = match cmd { + Cmd::SelfInsert('y') | + Cmd::SelfInsert('Y') => true, _ => false, }; } @@ -633,7 +649,7 @@ fn page_completions(rdr: &mut R, s: &mut State, config: &Config, candidates: &[String]) - -> Result> { + -> Result> { use std::cmp; use unicode_width::UnicodeWidthStr; @@ -701,7 +717,7 @@ fn reverse_incremental_search(rdr: &mut R, s: &mut State, history: &History, config: &Config) - -> Result> { + -> Result> { if history.is_empty() { return Ok(None); } @@ -713,7 +729,7 @@ fn reverse_incremental_search(rdr: &mut R, let mut direction = Direction::Reverse; let mut success = true; - let mut key; + let mut cmd; // Display the reverse-i-search prompt and process chars loop { let prompt = if success { @@ -723,17 +739,16 @@ fn reverse_incremental_search(rdr: &mut R, }; try!(s.refresh_prompt_and_line(&prompt)); - key = try!(rdr.next_key(config.keyseq_timeout())); - if let KeyPress::Char(c) = key { + cmd = try!(s.next_cmd(rdr, config)); + if let Cmd::SelfInsert(c) = cmd { search_buf.push(c); } else { - match key { - KeyPress::Ctrl('H') | - KeyPress::Backspace => { + match cmd { + Cmd::BackwardDeleteChar => { search_buf.pop(); continue; } - KeyPress::Ctrl('R') => { + Cmd::ReverseSearchHistory => { direction = Direction::Reverse; if history_idx > 0 { history_idx -= 1; @@ -742,7 +757,7 @@ fn reverse_incremental_search(rdr: &mut R, continue; } } - KeyPress::Ctrl('S') => { + Cmd::ForwardSearchHistory => { direction = Direction::Forward; if history_idx < history.len() - 1 { history_idx += 1; @@ -751,7 +766,7 @@ fn reverse_incremental_search(rdr: &mut R, continue; } } - KeyPress::Ctrl('G') => { + Cmd::Abort => { // Restore current edited line (before search) s.snapshot(); try!(s.refresh_line()); @@ -771,7 +786,7 @@ fn reverse_incremental_search(rdr: &mut R, _ => false, }; } - Ok(Some(key)) + Ok(Some(cmd)) } /// Handles reading and editting the readline buffer. @@ -789,6 +804,7 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); let mut s = State::new(&mut stdout, editor.term.clone(), + &editor.config, prompt, editor.history.len()); try!(s.refresh_line()); @@ -796,159 +812,135 @@ fn readline_edit(prompt: &str, let mut rdr = try!(s.term.create_reader()); loop { - let rk = rdr.next_key(editor.config.keyseq_timeout()); - if rk.is_err() && s.term.sigwinch() { - s.update_columns(); - try!(s.refresh_line()); - continue; - } - let mut key = try!(rk); - if let KeyPress::Char(c) = key { - editor.kill_ring.reset(); - try!(edit_insert(&mut s, c)); - continue; - } + let rc = s.next_cmd(&mut rdr, &editor.config); + let mut cmd = try!(rc); // autocomplete - if key == KeyPress::Tab && completer.is_some() { + if cmd == Cmd::Complete && completer.is_some() { let next = try!(complete_line(&mut rdr, &mut s, completer.unwrap(), &editor.config)); if next.is_some() { editor.kill_ring.reset(); - key = next.unwrap(); - if let KeyPress::Char(c) = key { - try!(edit_insert(&mut s, c)); - continue; - } + cmd = next.unwrap(); } else { continue; } - } else if key == KeyPress::Ctrl('R') { + } + + if let Cmd::SelfInsert(c) = cmd { + editor.kill_ring.reset(); + try!(edit_insert(&mut s, c)); + continue; + } + + if cmd == Cmd::ReverseSearchHistory { // Search history backward let next = try!(reverse_incremental_search(&mut rdr, &mut s, &editor.history, &editor.config)); if next.is_some() { - key = next.unwrap(); + cmd = next.unwrap(); } else { continue; } - } else if key == KeyPress::UnknownEscSeq { - continue; } - match key { - KeyPress::Ctrl('A') | - KeyPress::Home => { + match cmd { + Cmd::BeginningOfLine => { editor.kill_ring.reset(); // Move to the beginning of line. try!(edit_move_home(&mut s)) } - KeyPress::Ctrl('B') | - KeyPress::Left => { + Cmd::BackwardChar => { editor.kill_ring.reset(); // Move back a character. try!(edit_move_left(&mut s)) } - KeyPress::Ctrl('C') => { + Cmd::DeleteChar => { editor.kill_ring.reset(); - return Err(error::ReadlineError::Interrupted); + // Delete (forward) one character at point. + try!(edit_delete(&mut s)) } - KeyPress::Ctrl('D') => { + Cmd::EndOfFile => { editor.kill_ring.reset(); - if s.line.is_empty() { + if !s.edit_state.is_emacs_mode() || s.line.is_empty() { return Err(error::ReadlineError::Eof); } else { - // Delete (forward) one character at point. try!(edit_delete(&mut s)) } } - KeyPress::Ctrl('E') | - KeyPress::End => { + Cmd::EndOfLine => { editor.kill_ring.reset(); // Move to the end of line. try!(edit_move_end(&mut s)) } - KeyPress::Ctrl('F') | - KeyPress::Right => { + Cmd::ForwardChar => { editor.kill_ring.reset(); // Move forward a character. try!(edit_move_right(&mut s)) } - KeyPress::Ctrl('H') | - KeyPress::Backspace => { + Cmd::BackwardDeleteChar => { editor.kill_ring.reset(); // Delete one character backward. try!(edit_backspace(&mut s)) } - KeyPress::Ctrl('K') => { + Cmd::KillLine => { // Kill the text from point to the end of the line. if let Some(text) = try!(edit_kill_line(&mut s)) { editor.kill_ring.kill(&text, Mode::Append) } } - KeyPress::Ctrl('L') => { + Cmd::ClearScreen => { // Clear the screen leaving the current line at the top of the screen. try!(s.term.clear_screen(&mut s.out)); try!(s.refresh_line()) } - KeyPress::Ctrl('N') | - KeyPress::Down => { + Cmd::NextHistory => { editor.kill_ring.reset(); // Fetch the next command from the history list. try!(edit_history_next(&mut s, &editor.history, false)) } - KeyPress::Ctrl('P') | - KeyPress::Up => { + Cmd::PreviousHistory => { editor.kill_ring.reset(); // Fetch the previous command from the history list. try!(edit_history_next(&mut s, &editor.history, true)) } - KeyPress::Ctrl('T') => { + Cmd::TransposeChars => { editor.kill_ring.reset(); // Exchange the char before cursor with the character at cursor. try!(edit_transpose_chars(&mut s)) } - KeyPress::Ctrl('U') => { + Cmd::UnixLikeDiscard => { // Kill backward from point to the beginning of the line. if let Some(text) = try!(edit_discard_line(&mut s)) { editor.kill_ring.kill(&text, Mode::Prepend) } } #[cfg(unix)] - KeyPress::Ctrl('V') => { + Cmd::QuotedInsert => { // Quoted insert editor.kill_ring.reset(); let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c)) // FIXME } - KeyPress::Ctrl('W') => { + Cmd::KillWord(Word::BigWord) => { // Kill the word behind point, using white space as a word boundary if let Some(text) = try!(edit_delete_prev_word(&mut s, char::is_whitespace)) { editor.kill_ring.kill(&text, Mode::Prepend) } } - KeyPress::Ctrl('Y') => { + Cmd::Yank => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { try!(edit_yank(&mut s, text)) } } - #[cfg(unix)] - KeyPress::Ctrl('Z') => { - try!(original_mode.disable_raw_mode()); - try!(tty::suspend()); - try!(s.term.enable_raw_mode()); // TODO original_mode may have changed - try!(s.refresh_line()) - } // TODO CTRL-_ // undo - KeyPress::Enter | - KeyPress::Ctrl('J') => { + Cmd::AcceptLine => { // Accept the line regardless of where the cursor is. editor.kill_ring.reset(); try!(edit_move_end(&mut s)); break; } - KeyPress::Meta('\x08') | - KeyPress::Meta('\x7f') => { + Cmd::BackwardKillWord(Word::Word) => { // kill one word backward // Kill from the cursor to the start of the current word, or, if between words, to the start of the previous word. if let Some(text) = try!(edit_delete_prev_word(&mut s, @@ -956,62 +948,71 @@ fn readline_edit(prompt: &str, editor.kill_ring.kill(&text, Mode::Prepend) } } - KeyPress::Meta('<') => { + Cmd::BeginningOfHistory => { // move to first entry in history editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, true)) } - KeyPress::Meta('>') => { + Cmd::EndOfHistory => { // move to last entry in history editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, false)) } - KeyPress::Meta('B') => { + Cmd::BackwardWord(Word::Word) => { // move backwards one word editor.kill_ring.reset(); try!(edit_move_to_prev_word(&mut s)) } - KeyPress::Meta('C') => { + Cmd::CapitalizeWord => { // capitalize word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - KeyPress::Meta('D') => { + Cmd::KillWord(Word::Word) => { // kill one word forward if let Some(text) = try!(edit_delete_word(&mut s)) { editor.kill_ring.kill(&text, Mode::Append) } } - KeyPress::Meta('F') => { + Cmd::ForwardWord(Word::Word) => { // move forwards one word editor.kill_ring.reset(); try!(edit_move_to_next_word(&mut s)) } - KeyPress::Meta('L') => { + Cmd::DowncaseWord => { // lowercase word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::LOWERCASE)) } - KeyPress::Meta('T') => { + Cmd::TransposeWords => { // transpose words editor.kill_ring.reset(); try!(edit_transpose_words(&mut s)) } - KeyPress::Meta('U') => { + Cmd::UpcaseWord => { // uppercase word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::UPPERCASE)) } - KeyPress::Meta('Y') => { + Cmd::YankPop => { // yank-pop if let Some((yank_size, text)) = editor.kill_ring.yank_pop() { try!(edit_yank_pop(&mut s, yank_size, text)) } } - KeyPress::Delete => { + Cmd::Interrupt => { editor.kill_ring.reset(); - try!(edit_delete(&mut s)) + return Err(error::ReadlineError::Interrupted); + } + #[cfg(unix)] + Cmd::Suspend => { + try!(original_mode.disable_raw_mode()); + try!(tty::suspend()); + try!(s.term.enable_raw_mode()); // TODO original_mode may have changed + try!(s.refresh_line()); + continue; } + Cmd::Noop => {} _ => { editor.kill_ring.reset(); // Ignore the character typed. @@ -1180,8 +1181,8 @@ mod test { use completion::Completer; use config::Config; use consts::KeyPress; - use {Position, State}; - use super::{Editor, Result}; + use keymap::{Cmd, EditState}; + use super::{Editor, Position, Result, State}; use tty::{Terminal, Term}; fn init_state<'out>(out: &'out mut Write, @@ -1190,6 +1191,7 @@ mod test { cols: usize) -> State<'out, 'static> { let term = Terminal::new(); + let config = Config::default(); State { out: out, prompt: "", @@ -1202,6 +1204,7 @@ mod test { snapshot: LineBuffer::with_capacity(100), term: term, byte_buffer: [0; 4], + edit_state : EditState::new(&config), } } @@ -1263,8 +1266,8 @@ mod test { let keys = &[KeyPress::Enter]; let mut rdr = keys.iter(); let completer = SimpleCompleter; - let key = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); - assert_eq!(Some(KeyPress::Enter), key); + let cmd = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); + assert_eq!(Some(Cmd::AcceptLine), cmd); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); } From 4eddf071009501b62c7a6542252241f4d7651581 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 11 Dec 2016 20:39:48 +0100 Subject: [PATCH 0227/1201] Handle more Vi cmds (#94) Replace char ('r') KillWholeLine ('S', 'dd', 'cc') --- examples/example.rs | 3 ++- src/keymap.rs | 4 ++-- src/lib.rs | 40 ++++++++++++++++++++++++++-------------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index e97650cedc..689c1d557b 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -2,7 +2,7 @@ extern crate rustyline; use rustyline::completion::FilenameCompleter; use rustyline::error::ReadlineError; -use rustyline::{Config, CompletionType, Editor}; +use rustyline::{Config, CompletionType, Editor, EditMode}; // On unix platforms you can use ANSI escape sequences #[cfg(unix)] @@ -17,6 +17,7 @@ fn main() { let config = Config::builder() .history_ignore_space(true) .completion_type(CompletionType::List) + .edit_mode(EditMode::Emacs) .build(); let c = FilenameCompleter::new(); let mut rl = Editor::with_config(config); diff --git a/src/keymap.rs b/src/keymap.rs index 7f2e7dbb38..726f807f1d 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -27,7 +27,7 @@ pub enum Cmd { ForwardWord(Word), // Command For Moving Interrupt, KillLine, // Command For Killing - KillWholeLine, // Command For Killing (TODO Delete current line) + KillWholeLine, // Command For Killing KillWord(Word), // Command For Killing NextHistory, // Command For History Noop, @@ -313,7 +313,7 @@ impl EditState { KeyPress::Esc => { // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). self.insert = false; - Cmd::Noop + Cmd::BackwardChar } KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, diff --git a/src/lib.rs b/src/lib.rs index 2f5a801158..3ee3d6feb0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -667,22 +667,22 @@ fn page_completions(rdr: &mut R, for row in 0..num_rows { if row == pause_row { try!(write_and_flush(s.out, b"\n--More--")); - let mut key = KeyPress::Null; - while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && - key != KeyPress::Char('n') && key != KeyPress::Char('N') && - key != KeyPress::Char('q') && - key != KeyPress::Char('Q') && - key != KeyPress::Char(' ') && - key != KeyPress::Backspace && key != KeyPress::Enter { - key = try!(rdr.next_key(config.keyseq_timeout())); + let mut cmd = Cmd::Noop; + while cmd != Cmd::SelfInsert('y') && cmd != Cmd::SelfInsert('Y') && + cmd != Cmd::SelfInsert('n') && cmd != Cmd::SelfInsert('N') && + cmd != Cmd::SelfInsert('q') && + cmd != Cmd::SelfInsert('Q') && + cmd != Cmd::SelfInsert(' ') && + cmd != Cmd::BackwardDeleteChar && cmd != Cmd::AcceptLine { + cmd = try!(s.next_cmd(rdr, config)); } - match key { - KeyPress::Char('y') | - KeyPress::Char('Y') | - KeyPress::Char(' ') => { + match cmd { + Cmd::SelfInsert('y') | + Cmd::SelfInsert('Y') | + Cmd::SelfInsert(' ') => { pause_row += s.term.get_rows() - 1; } - KeyPress::Enter => { + Cmd::AcceptLine => { pause_row += 1; } _ => break, @@ -859,6 +859,12 @@ fn readline_edit(prompt: &str, // Delete (forward) one character at point. try!(edit_delete(&mut s)) } + Cmd::Replace(c) => { + editor.kill_ring.reset(); + try!(edit_delete(&mut s)); + try!(edit_insert(&mut s, c)); + try!(edit_move_left(&mut s)) + } Cmd::EndOfFile => { editor.kill_ring.reset(); if !s.edit_state.is_emacs_mode() || s.line.is_empty() { @@ -888,6 +894,12 @@ fn readline_edit(prompt: &str, editor.kill_ring.kill(&text, Mode::Append) } } + Cmd::KillWholeLine => { + try!(edit_move_home(&mut s)); + if let Some(text) = try!(edit_kill_line(&mut s)) { + editor.kill_ring.kill(&text, Mode::Append) + } + } Cmd::ClearScreen => { // Clear the screen leaving the current line at the top of the screen. try!(s.term.clear_screen(&mut s.out)); @@ -1204,7 +1216,7 @@ mod test { snapshot: LineBuffer::with_capacity(100), term: term, byte_buffer: [0; 4], - edit_state : EditState::new(&config), + edit_state: EditState::new(&config), } } From 8adfbdf4b0bd5f9dcdd4e22af3dd4b153482d5e7 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 12 Dec 2016 21:09:57 +0100 Subject: [PATCH 0228/1201] Handle Vi specific word movements (#94) --- src/keymap.rs | 14 ++++---- src/lib.rs | 43 +++++++++------------- src/line_buffer.rs | 79 +++++++++++++++++++++++++--------------- src/tty/unix.rs | 90 +++++++++++++++++++++++----------------------- src/tty/windows.rs | 30 ++++++++-------- 5 files changed, 133 insertions(+), 123 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 726f807f1d..0f8421cdfa 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -1,7 +1,7 @@ -use super::Config; -use super::EditMode; -use super::KeyPress; -use super::RawReader; +use config::Config; +use config::EditMode; +use consts::KeyPress; +use tty::RawReader; use super::Result; #[derive(Debug, Clone, PartialEq)] @@ -41,7 +41,7 @@ pub enum Cmd { TransposeWords, // Command For Text Unknown, UnixLikeDiscard, // Command For Killing - // UnixWordRubout, // = KillWord(Word::BigWord) Command For Killing + // UnixWordRubout, // = BackwardKillWord(Word::BigWord) Command For Killing UpcaseWord, // Command For Text ViCharSearch(CharSearch), // TODO ViEndWord(Word), // TODO @@ -50,7 +50,7 @@ pub enum Cmd { YankPop, // Command For Killing } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub enum Word { // non-blanks characters BigWord, @@ -132,7 +132,7 @@ impl EditState { KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), + KeyPress::Ctrl('W') => Cmd::BackwardKillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Meta('\x08') => Cmd::BackwardKillWord(Word::Word), diff --git a/src/lib.rs b/src/lib.rs index 3ee3d6feb0..714f1361b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,6 @@ use std::result; use tty::{RawMode, RawReader, Terminal, Term}; use completion::{Completer, longest_common_prefix}; -use consts::KeyPress; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; use keymap::{CharSearch, Cmd, EditState, Word}; @@ -427,8 +426,8 @@ fn edit_transpose_chars(s: &mut State) -> Result<()> { } } -fn edit_move_to_prev_word(s: &mut State) -> Result<()> { - if s.line.move_to_prev_word() { +fn edit_move_to_prev_word(s: &mut State, word_def: Word) -> Result<()> { + if s.line.move_to_prev_word(word_def) { s.refresh_line() } else { Ok(()) @@ -437,10 +436,8 @@ fn edit_move_to_prev_word(s: &mut State) -> Result<()> { /// Delete the previous word, maintaining the cursor at the start of the /// current word. -fn edit_delete_prev_word(s: &mut State, test: F) -> Result> - where F: Fn(char) -> bool -{ - if let Some(text) = s.line.delete_prev_word(test) { +fn edit_delete_prev_word(s: &mut State, word_def: Word) -> Result> { + if let Some(text) = s.line.delete_prev_word(word_def) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -448,8 +445,8 @@ fn edit_delete_prev_word(s: &mut State, test: F) -> Result> } } -fn edit_move_to_next_word(s: &mut State) -> Result<()> { - if s.line.move_to_next_word() { +fn edit_move_to_next_word(s: &mut State, word_def: Word) -> Result<()> { + if s.line.move_to_next_word(word_def) { s.refresh_line() } else { Ok(()) @@ -457,8 +454,8 @@ fn edit_move_to_next_word(s: &mut State) -> Result<()> { } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. -fn edit_delete_word(s: &mut State) -> Result> { - if let Some(text) = s.line.delete_word() { +fn edit_delete_word(s: &mut State, word_def: Word) -> Result> { + if let Some(text) = s.line.delete_word(word_def) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -933,12 +930,6 @@ fn readline_edit(prompt: &str, let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c)) // FIXME } - Cmd::KillWord(Word::BigWord) => { - // Kill the word behind point, using white space as a word boundary - if let Some(text) = try!(edit_delete_prev_word(&mut s, char::is_whitespace)) { - editor.kill_ring.kill(&text, Mode::Prepend) - } - } Cmd::Yank => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { @@ -952,11 +943,9 @@ fn readline_edit(prompt: &str, try!(edit_move_end(&mut s)); break; } - Cmd::BackwardKillWord(Word::Word) => { + Cmd::BackwardKillWord(word_def) => { // kill one word backward - // Kill from the cursor to the start of the current word, or, if between words, to the start of the previous word. - if let Some(text) = try!(edit_delete_prev_word(&mut s, - |ch| !ch.is_alphanumeric())) { + if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def)) { editor.kill_ring.kill(&text, Mode::Prepend) } } @@ -970,26 +959,26 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, false)) } - Cmd::BackwardWord(Word::Word) => { + Cmd::BackwardWord(word_def) => { // move backwards one word editor.kill_ring.reset(); - try!(edit_move_to_prev_word(&mut s)) + try!(edit_move_to_prev_word(&mut s, word_def)) } Cmd::CapitalizeWord => { // capitalize word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - Cmd::KillWord(Word::Word) => { + Cmd::KillWord(word_def) => { // kill one word forward - if let Some(text) = try!(edit_delete_word(&mut s)) { + if let Some(text) = try!(edit_delete_word(&mut s, word_def)) { editor.kill_ring.kill(&text, Mode::Append) } } - Cmd::ForwardWord(Word::Word) => { + Cmd::ForwardWord(word_def) => { // move forwards one word editor.kill_ring.reset(); - try!(edit_move_to_next_word(&mut s)) + try!(edit_move_to_next_word(&mut s, word_def)) } Cmd::DowncaseWord => { // lowercase word after point diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 613fce1cd0..854fa49d55 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,5 +1,6 @@ //! Line buffer with current cursor position use std::ops::Deref; +use keymap::Word; /// Maximum buffer size for the line read pub static MAX_LINE: usize = 4096; @@ -261,18 +262,17 @@ impl LineBuffer { true } - fn prev_word_pos(&self, pos: usize, test: F) -> Option - where F: Fn(char) -> bool - { + fn prev_word_pos(&self, pos: usize, word_def: Word) -> Option { if pos == 0 { return None; } + let test = is_break_char(word_def); let mut pos = pos; // eat any spaces on the left pos -= self.buf[..pos] .chars() .rev() - .take_while(|ch| test(*ch)) + .take_while(|ch| test(ch)) .map(char::len_utf8) .sum(); if pos > 0 { @@ -280,7 +280,7 @@ impl LineBuffer { pos -= self.buf[..pos] .chars() .rev() - .take_while(|ch| !test(*ch)) + .take_while(|ch| !test(ch)) .map(char::len_utf8) .sum(); } @@ -288,8 +288,8 @@ impl LineBuffer { } /// Moves the cursor to the beginning of previous word. - pub fn move_to_prev_word(&mut self) -> bool { - if let Some(pos) = self.prev_word_pos(self.pos, |ch| !ch.is_alphanumeric()) { + pub fn move_to_prev_word(&mut self, word_def: Word) -> bool { + if let Some(pos) = self.prev_word_pos(self.pos, word_def) { self.pos = pos; true } else { @@ -299,10 +299,8 @@ impl LineBuffer { /// Delete the previous word, maintaining the cursor at the start of the /// current word. - pub fn delete_prev_word(&mut self, test: F) -> Option - where F: Fn(char) -> bool - { - if let Some(pos) = self.prev_word_pos(self.pos, test) { + pub fn delete_prev_word(&mut self, word_def: Word) -> Option { + if let Some(pos) = self.prev_word_pos(self.pos, word_def) { let word = self.buf.drain(pos..self.pos).collect(); self.pos = pos; Some(word) @@ -312,13 +310,14 @@ impl LineBuffer { } /// Returns the position (start, end) of the next word. - pub fn next_word_pos(&self, pos: usize) -> Option<(usize, usize)> { + pub fn next_word_pos(&self, pos: usize, word_def: Word) -> Option<(usize, usize)> { if pos < self.buf.len() { + let test = is_break_char(word_def); let mut pos = pos; // eat any spaces pos += self.buf[pos..] .chars() - .take_while(|ch| !ch.is_alphanumeric()) + .take_while(test) .map(char::len_utf8) .sum(); let start = pos; @@ -326,7 +325,7 @@ impl LineBuffer { // eat any non-spaces pos += self.buf[pos..] .chars() - .take_while(|ch| ch.is_alphanumeric()) + .take_while(|ch| !test(ch)) .map(char::len_utf8) .sum(); } @@ -337,8 +336,8 @@ impl LineBuffer { } /// Moves the cursor to the end of next word. - pub fn move_to_next_word(&mut self) -> bool { - if let Some((_, end)) = self.next_word_pos(self.pos) { + pub fn move_to_next_word(&mut self, word_def: Word) -> bool { + if let Some((_, end)) = self.next_word_pos(self.pos, word_def) { self.pos = end; true } else { @@ -346,9 +345,12 @@ impl LineBuffer { } } + // TODO move_to_end_of_word (Vi mode) + // TODO move_to (Vi mode: f|F|t|T) + /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. - pub fn delete_word(&mut self) -> Option { - if let Some((_, end)) = self.next_word_pos(self.pos) { + pub fn delete_word(&mut self, word_def: Word) -> Option { + if let Some((_, end)) = self.next_word_pos(self.pos, word_def) { let word = self.buf.drain(self.pos..end).collect(); Some(word) } else { @@ -358,7 +360,7 @@ impl LineBuffer { /// Alter the next word. pub fn edit_word(&mut self, a: WordAction) -> bool { - if let Some((start, end)) = self.next_word_pos(self.pos) { + if let Some((start, end)) = self.next_word_pos(self.pos, Word::Word) { if start == end { return false; } @@ -388,16 +390,17 @@ impl LineBuffer { // prevword___oneword__ // ^ ^ ^ // prev_start start self.pos/end - if let Some(start) = self.prev_word_pos(self.pos, |ch| !ch.is_alphanumeric()) { - if let Some(prev_start) = self.prev_word_pos(start, |ch| !ch.is_alphanumeric()) { - let (_, prev_end) = self.next_word_pos(prev_start).unwrap(); + let word_def = Word::Word; + if let Some(start) = self.prev_word_pos(self.pos, word_def) { + if let Some(prev_start) = self.prev_word_pos(start, word_def) { + let (_, prev_end) = self.next_word_pos(prev_start, word_def).unwrap(); if prev_end >= start { return false; } - let (_, mut end) = self.next_word_pos(start).unwrap(); + let (_, mut end) = self.next_word_pos(start, word_def).unwrap(); if end < self.pos { if self.pos < self.buf.len() { - let (s, _) = self.next_word_pos(self.pos).unwrap(); + let (s, _) = self.next_word_pos(self.pos, word_def).unwrap(); end = s; } else { end = self.pos; @@ -467,9 +470,27 @@ fn insert_str(buf: &mut String, idx: usize, s: &str) { } } +fn is_break_char(word_def: Word) -> fn(&char) -> bool { + match word_def { + Word::Word => is_not_alphanumeric, + Word::ViWord => is_not_alphanumeric_and_underscore, + Word::BigWord => is_whitespace, + } +} + +fn is_not_alphanumeric(ch: &char) -> bool { + !ch.is_alphanumeric() +} +fn is_not_alphanumeric_and_underscore(ch: &char) -> bool { + !ch.is_alphanumeric() && *ch != '_' +} +fn is_whitespace(ch: &char) -> bool { + ch.is_whitespace() +} + #[cfg(test)] mod test { - use super::{LineBuffer, MAX_LINE, WordAction}; + use super::{LineBuffer, MAX_LINE, Word, WordAction}; #[test] fn insert() { @@ -570,7 +591,7 @@ mod test { #[test] fn move_to_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let ok = s.move_to_prev_word(); + let ok = s.move_to_prev_word(Word::Word); assert_eq!("a ß c", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); @@ -579,7 +600,7 @@ mod test { #[test] fn delete_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let text = s.delete_prev_word(char::is_whitespace); + let text = s.delete_prev_word(Word::BigWord); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); @@ -588,7 +609,7 @@ mod test { #[test] fn move_to_next_word() { let mut s = LineBuffer::init("a ß c", 1); - let ok = s.move_to_next_word(); + let ok = s.move_to_next_word(Word::Word); assert_eq!("a ß c", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); @@ -597,7 +618,7 @@ mod test { #[test] fn delete_word() { let mut s = LineBuffer::init("a ß c", 1); - let text = s.delete_word(); + let text = s.delete_word(Word::Word); assert_eq!("a c", s.buf); assert_eq!(1, s.pos); assert_eq!(Some(" ß".to_string()), text); diff --git a/src/tty/unix.rs b/src/tty/unix.rs index b51cdba208..8436116dbd 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -110,66 +110,66 @@ impl PosixRawReader { // Extended escape, read additional byte. let seq3 = try!(self.next_char()); if seq3 == '~' { - match seq2 { - '1' => Ok(KeyPress::Home), // xterm - '3' => Ok(KeyPress::Delete), - '4' => Ok(KeyPress::End), // xterm - '5' => Ok(KeyPress::PageUp), - '6' => Ok(KeyPress::PageDown), - '7' => Ok(KeyPress::Home), - '8' => Ok(KeyPress::End), - _ => Ok(KeyPress::UnknownEscSeq), - } + Ok(match seq2 { + '1' => KeyPress::Home, // xterm + '3' => KeyPress::Delete, + '4' => KeyPress::End, // xterm + '5' => KeyPress::PageUp, + '6' => KeyPress::PageDown, + '7' => KeyPress::Home, + '8' => KeyPress::End, + _ => KeyPress::UnknownEscSeq, + }) } else { Ok(KeyPress::UnknownEscSeq) } } else { - match seq2 { - 'A' => Ok(KeyPress::Up), // ANSI - 'B' => Ok(KeyPress::Down), - 'C' => Ok(KeyPress::Right), - 'D' => Ok(KeyPress::Left), - 'F' => Ok(KeyPress::End), - 'H' => Ok(KeyPress::Home), - _ => Ok(KeyPress::UnknownEscSeq), - } + Ok(match seq2 { + 'A' => KeyPress::Up, // ANSI + 'B' => KeyPress::Down, + 'C' => KeyPress::Right, + 'D' => KeyPress::Left, + 'F' => KeyPress::End, + 'H' => KeyPress::Home, + _ => KeyPress::UnknownEscSeq, + }) } } else if seq1 == 'O' { // ESC O sequences. let seq2 = try!(self.next_char()); - match seq2 { - 'A' => Ok(KeyPress::Up), - 'B' => Ok(KeyPress::Down), - 'C' => Ok(KeyPress::Right), - 'D' => Ok(KeyPress::Left), - 'F' => Ok(KeyPress::End), - 'H' => Ok(KeyPress::Home), - _ => Ok(KeyPress::UnknownEscSeq), - } + Ok(match seq2 { + 'A' => KeyPress::Up, + 'B' => KeyPress::Down, + 'C' => KeyPress::Right, + 'D' => KeyPress::Left, + 'F' => KeyPress::End, + 'H' => KeyPress::Home, + _ => KeyPress::UnknownEscSeq, + }) } else { // TODO ESC-N (n): search history forward not interactively // TODO ESC-P (p): search history backward not interactively // TODO ESC-R (r): Undo all changes made to this line. - match seq1 { - '\x08' => Ok(KeyPress::Meta('\x08')), // Backspace - '-' => return Ok(KeyPress::Meta('-')), - '0'...'9' => return Ok(KeyPress::Meta(seq1)), - '<' => Ok(KeyPress::Meta('<')), - '>' => Ok(KeyPress::Meta('>')), - 'b' | 'B' => Ok(KeyPress::Meta('B')), - 'c' | 'C' => Ok(KeyPress::Meta('C')), - 'd' | 'D' => Ok(KeyPress::Meta('D')), - 'f' | 'F' => Ok(KeyPress::Meta('F')), - 'l' | 'L' => Ok(KeyPress::Meta('L')), - 't' | 'T' => Ok(KeyPress::Meta('T')), - 'u' | 'U' => Ok(KeyPress::Meta('U')), - 'y' | 'Y' => Ok(KeyPress::Meta('Y')), - '\x7f' => Ok(KeyPress::Meta('\x7f')), // Delete + Ok(match seq1 { + '\x08' => KeyPress::Meta('\x08'), // Backspace + '-' => KeyPress::Meta('-'), + '0'...'9' => KeyPress::Meta(seq1), + '<' => KeyPress::Meta('<'), + '>' => KeyPress::Meta('>'), + 'b' | 'B' => KeyPress::Meta('B'), + 'c' | 'C' => KeyPress::Meta('C'), + 'd' | 'D' => KeyPress::Meta('D'), + 'f' | 'F' => KeyPress::Meta('F'), + 'l' | 'L' => KeyPress::Meta('L'), + 't' | 'T' => KeyPress::Meta('T'), + 'u' | 'U' => KeyPress::Meta('U'), + 'y' | 'Y' => KeyPress::Meta('Y'), + '\x7f' => KeyPress::Meta('\x7f'), // Delete _ => { // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::Esc, seq1).unwrap(); - Ok(KeyPress::UnknownEscSeq) + KeyPress::UnknownEscSeq } - } + }) } } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 1ebf108d9d..da67c37d52 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -150,21 +150,21 @@ impl RawReader for ConsoleRawReader { } let c = try!(orc.unwrap()); if meta { - match c { - '-' => return Ok(KeyPress::Meta('-')), - '0'...'9' => return Ok(KeyPress::Meta(c)), - '<' => Ok(KeyPress::Meta('<')), - '>' => Ok(KeyPress::Meta('>')), - 'b' | 'B' => return Ok(KeyPress::Meta('B')), - 'c' | 'C' => return Ok(KeyPress::Meta('C')), - 'd' | 'D' => return Ok(KeyPress::Meta('D')), - 'f' | 'F' => return Ok(KeyPress::Meta('F')), - 'l' | 'L' => return Ok(KeyPress::Meta('L')), - 't' | 'T' => return Ok(KeyPress::Meta('T')), - 'u' | 'U' => return Ok(KeyPress::Meta('U')), - 'y' | 'Y' => return Ok(KeyPress::Meta('Y')), - _ => return Ok(KeyPress::UnknownEscSeq), - } + return Ok(match c { + '-' => KeyPress::Meta('-'), + '0'...'9' => KeyPress::Meta(c), + '<' => KeyPress::Meta('<'), + '>' => KeyPress::Meta('>'), + 'b' | 'B' => KeyPress::Meta('B'), + 'c' | 'C' => KeyPress::Meta('C'), + 'd' | 'D' => KeyPress::Meta('D'), + 'f' | 'F' => KeyPress::Meta('F'), + 'l' | 'L' => KeyPress::Meta('L'), + 't' | 'T' => KeyPress::Meta('T'), + 'u' | 'U' => KeyPress::Meta('U'), + 'y' | 'Y' => KeyPress::Meta('Y'), + _ => KeyPress::UnknownEscSeq, + }); } else { return Ok(consts::char_to_key_press(c)); } From b08a9aba742b33b8bff93cee9e17508483d41708 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 16 Dec 2016 19:07:15 +0100 Subject: [PATCH 0229/1201] Factorize common commands between emacs and vi --- src/keymap.rs | 93 +++++++++++++++------------------------------- src/tty/windows.rs | 6 +-- 2 files changed, 32 insertions(+), 67 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 0f8421cdfa..9325528633 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -103,38 +103,17 @@ impl EditState { KeyPress::Char(c) => Cmd::SelfInsert(c), KeyPress::Esc => Cmd::Abort, // TODO Validate KeyPress::Ctrl('A') => Cmd::BeginningOfLine, - KeyPress::Home => Cmd::BeginningOfLine, KeyPress::Ctrl('B') => Cmd::BackwardChar, - KeyPress::Left => Cmd::BackwardChar, - KeyPress::Ctrl('C') => Cmd::Interrupt, - KeyPress::Ctrl('D') => Cmd::EndOfFile, - // KeyPress::Ctrl('D') => Cmd::DeleteChar, - KeyPress::Delete => Cmd::DeleteChar, KeyPress::Ctrl('E') => Cmd::EndOfLine, - KeyPress::End => Cmd::EndOfLine, KeyPress::Ctrl('F') => Cmd::ForwardChar, - KeyPress::Right => Cmd::ForwardChar, KeyPress::Ctrl('G') => Cmd::Abort, KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, KeyPress::Backspace => Cmd::BackwardDeleteChar, KeyPress::Tab => Cmd::Complete, - KeyPress::Ctrl('J') => Cmd::AcceptLine, - KeyPress::Enter => Cmd::AcceptLine, KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Ctrl('N') => Cmd::NextHistory, - KeyPress::Down => Cmd::NextHistory, KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Up => Cmd::PreviousHistory, - KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution - KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, - KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution - KeyPress::Ctrl('T') => Cmd::TransposeChars, - KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, - KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::BackwardKillWord(Word::BigWord), - KeyPress::Ctrl('Y') => Cmd::Yank, - KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Meta('\x08') => Cmd::BackwardKillWord(Word::Word), KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(Word::Word), // KeyPress::Meta('-') => { // digit-argument @@ -151,8 +130,7 @@ impl EditState { KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, KeyPress::Meta('Y') => Cmd::YankPop, - KeyPress::UnknownEscSeq => Cmd::Noop, - _ => Cmd::Unknown, + _ => self.common(key), }; Ok(cmd) } @@ -164,7 +142,6 @@ impl EditState { KeyPress::End => Cmd::EndOfLine, // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket KeyPress::Char('0') => Cmd::BeginningOfLine, // vi-zero: Vi move to the beginning of line. - KeyPress::Home => Cmd::BeginningOfLine, // KeyPress::Char('1'...'9') => Cmd::???, // vi-arg-digit KeyPress::Char('^') => Cmd::BeginningOfLine, // vi-first-print TODO Move to the first non-blank character of line. KeyPress::Char('a') => { @@ -242,27 +219,17 @@ impl EditState { KeyPress::Char('h') => Cmd::BackwardChar, KeyPress::Ctrl('H') => Cmd::BackwardChar, KeyPress::Backspace => Cmd::BackwardChar, // TODO Validate - KeyPress::Left => Cmd::BackwardChar, - KeyPress::Ctrl('C') => Cmd::Interrupt, - KeyPress::Ctrl('D') => Cmd::EndOfFile, - KeyPress::Delete => Cmd::DeleteChar, KeyPress::Ctrl('G') => Cmd::Abort, KeyPress::Char('l') => Cmd::ForwardChar, KeyPress::Char(' ') => Cmd::ForwardChar, - KeyPress::Right => Cmd::ForwardChar, KeyPress::Ctrl('L') => Cmd::ClearScreen, - KeyPress::Ctrl('J') => Cmd::AcceptLine, - KeyPress::Enter => Cmd::AcceptLine, KeyPress::Char('+') => Cmd::NextHistory, KeyPress::Char('j') => Cmd::NextHistory, KeyPress::Ctrl('N') => Cmd::NextHistory, - KeyPress::Down => Cmd::NextHistory, KeyPress::Char('-') => Cmd::PreviousHistory, KeyPress::Char('k') => Cmd::PreviousHistory, KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Up => Cmd::PreviousHistory, KeyPress::Ctrl('K') => Cmd::KillLine, - KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('R') => { self.insert = true; // TODO Validate Cmd::ReverseSearchHistory @@ -271,15 +238,8 @@ impl EditState { self.insert = true; // TODO Validate Cmd::ForwardSearchHistory } - KeyPress::Ctrl('T') => Cmd::TransposeChars, - KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, - KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), - KeyPress::Ctrl('Y') => Cmd::Yank, - KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Esc => Cmd::Noop, - KeyPress::UnknownEscSeq => Cmd::Noop, - _ => Cmd::Unknown, + _ => self.common(key), }; Ok(cmd) } @@ -288,35 +248,15 @@ impl EditState { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), - KeyPress::Home => Cmd::BeginningOfLine, - KeyPress::Left => Cmd::BackwardChar, - KeyPress::Ctrl('C') => Cmd::Interrupt, - KeyPress::Ctrl('D') => Cmd::EndOfFile, // vi-eof-maybe - KeyPress::Delete => Cmd::DeleteChar, - KeyPress::End => Cmd::EndOfLine, - KeyPress::Right => Cmd::ForwardChar, KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, KeyPress::Backspace => Cmd::BackwardDeleteChar, KeyPress::Tab => Cmd::Complete, - KeyPress::Ctrl('J') => Cmd::AcceptLine, - KeyPress::Enter => Cmd::AcceptLine, - KeyPress::Down => Cmd::NextHistory, - KeyPress::Up => Cmd::PreviousHistory, - KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, - KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, - KeyPress::Ctrl('T') => Cmd::TransposeChars, - KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, - KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), - KeyPress::Ctrl('Y') => Cmd::Yank, - KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Esc => { // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). self.insert = false; Cmd::BackwardChar } - KeyPress::UnknownEscSeq => Cmd::Noop, - _ => Cmd::Unknown, + _ => self.common(key), }; Ok(cmd) } @@ -374,4 +314,31 @@ impl EditState { _ => None, }) } + + fn common(&mut self, key: KeyPress) -> Cmd { + match key { + KeyPress::Home => Cmd::BeginningOfLine, + KeyPress::Left => Cmd::BackwardChar, + KeyPress::Ctrl('C') => Cmd::Interrupt, + KeyPress::Ctrl('D') => Cmd::EndOfFile, + KeyPress::Delete => Cmd::DeleteChar, + KeyPress::End => Cmd::EndOfLine, + KeyPress::Right => Cmd::ForwardChar, + KeyPress::Ctrl('J') => Cmd::AcceptLine, + KeyPress::Enter => Cmd::AcceptLine, + KeyPress::Down => Cmd::NextHistory, + KeyPress::Up => Cmd::PreviousHistory, + KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution + KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, + KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution + KeyPress::Ctrl('T') => Cmd::TransposeChars, + KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, + KeyPress::Ctrl('V') => Cmd::QuotedInsert, + KeyPress::Ctrl('W') => Cmd::BackwardKillWord(Word::BigWord), + KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Ctrl('Z') => Cmd::Suspend, + KeyPress::UnknownEscSeq => Cmd::Noop, + _ => Cmd::Unknown, + } + } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index da67c37d52..44894ebb5a 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -93,7 +93,6 @@ impl RawReader for ConsoleRawReader { let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; let mut count = 0; - let mut esc_seen = false; loop { // TODO GetNumberOfConsoleInputEvents check!(kernel32::ReadConsoleInputW(self.handle, @@ -121,7 +120,7 @@ impl RawReader for ConsoleRawReader { (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); // let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) == // (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); - let meta = alt || esc_seen; + let meta = alt; let utf16 = key_event.UnicodeChar; if utf16 == 0 { @@ -139,8 +138,7 @@ impl RawReader for ConsoleRawReader { _ => continue, }; } else if utf16 == 27 { - esc_seen = true; - continue; + return Ok(KeyPress::Esc); } else { // TODO How to support surrogate pair ? self.buf = Some(utf16); From bb135c4d3378b7b5d8ee634997c71c16e2d199ab Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 17 Dec 2016 09:59:32 +0100 Subject: [PATCH 0230/1201] Numeric arguments But commands are not repeated --- README.md | 9 +++ src/keymap.rs | 199 +++++++++++++++++++++++++++++++++----------------- src/lib.rs | 26 +++---- 3 files changed, 153 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 88e8eb368f..4bb13a9eea 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,15 @@ $ wineconsole --backend=curses target/x86_64-pc-windows-gnu/debug/examples/examp ... ``` +## Terminal checks + +```sh +$ # current settings of all terminal attributes: +$ stty -a +$ # key bindings: +$ bind -p +``` + ## Similar projects - [copperline](https://github.com/srijs/rust-copperline) (Rust) diff --git a/src/keymap.rs b/src/keymap.rs index 9325528633..1f4f013b41 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -8,32 +8,32 @@ use super::Result; pub enum Cmd { Abort, // Miscellaneous Command AcceptLine, // Command For History - BackwardChar, // Command For Moving - BackwardDeleteChar, // Command For Text - BackwardKillWord(Word), // Command For Killing - BackwardWord(Word), // Command For Moving + BackwardChar(i32), // Command For Moving + BackwardDeleteChar(i32), // Command For Text + BackwardKillWord(i32, Word), // Command For Killing + BackwardWord(i32, Word), // Command For Moving BeginningOfHistory, // Command For History BeginningOfLine, // Command For Moving CapitalizeWord, // Command For Text ClearScreen, // Command For Moving Complete, // Command For Completion - DeleteChar, // Command For Text + DeleteChar(i32), // Command For Text DowncaseWord, // Command For Text EndOfFile, // Command For Text EndOfHistory, // Command For History EndOfLine, // Command For Moving - ForwardChar, // Command For Moving + ForwardChar(i32), // Command For Moving ForwardSearchHistory, // Command For History - ForwardWord(Word), // Command For Moving + ForwardWord(i32, Word), // Command For Moving Interrupt, KillLine, // Command For Killing KillWholeLine, // Command For Killing - KillWord(Word), // Command For Killing + KillWord(i32, Word), // Command For Killing NextHistory, // Command For History Noop, PreviousHistory, // Command For History QuotedInsert, // Command For Text - Replace(char), // TODO DeleteChar + SelfInsert + Replace(i32, char), // TODO DeleteChar + SelfInsert ReverseSearchHistory, // Command For History SelfInsert(char), // Command For Text Suspend, @@ -44,9 +44,9 @@ pub enum Cmd { // UnixWordRubout, // = BackwardKillWord(Word::BigWord) Command For Killing UpcaseWord, // Command For Text ViCharSearch(CharSearch), // TODO - ViEndWord(Word), // TODO - ViKillTo(CharSearch), // TODO - Yank, // Command For Killing + ViEndWord(i32, Word), // TODO + ViKillTo(i32, CharSearch), // TODO + Yank(i32), // Command For Killing YankPop, // Command For Killing } @@ -70,11 +70,12 @@ pub enum CharSearch { BackwardAfter(char), } -// TODO numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 pub struct EditState { mode: EditMode, // Vi Command/Alternate, Insert/Input mode insert: bool, // vi only ? + // numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 + num_args: i32, } impl EditState { @@ -82,6 +83,7 @@ impl EditState { EditState { mode: config.edit_mode(), insert: true, + num_args: 0, } } @@ -97,35 +99,64 @@ impl EditState { } } + fn digit_argument(&mut self, + rdr: &mut R, + config: &Config, + digit: char) + -> Result { + match digit { + '0'...'9' => { + self.num_args = digit.to_digit(10).unwrap() as i32; + } + '-' => { + self.num_args = -1; + } + _ => unreachable!(), + } + loop { + let key = try!(rdr.next_key(config.keyseq_timeout())); + match key { + KeyPress::Char(digit @ '0'...'9') => { + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + } + KeyPress::Meta(digit @ '0'...'9') => { + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + } + _ => return Ok(key), + }; + } + } + fn emacs(&mut self, rdr: &mut R, config: &Config) -> Result { - let key = try!(rdr.next_key(config.keyseq_timeout())); + let mut key = try!(rdr.next_key(config.keyseq_timeout())); + if let KeyPress::Meta(digit @ '-') = key { + key = try!(self.digit_argument(rdr, config, digit)); + } else if let KeyPress::Meta(digit @ '0'...'9') = key { + key = try!(self.digit_argument(rdr, config, digit)); + } let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), KeyPress::Esc => Cmd::Abort, // TODO Validate KeyPress::Ctrl('A') => Cmd::BeginningOfLine, - KeyPress::Ctrl('B') => Cmd::BackwardChar, + KeyPress::Ctrl('B') => Cmd::BackwardChar(self.num_args()), KeyPress::Ctrl('E') => Cmd::EndOfLine, - KeyPress::Ctrl('F') => Cmd::ForwardChar, + KeyPress::Ctrl('F') => Cmd::ForwardChar(self.num_args()), KeyPress::Ctrl('G') => Cmd::Abort, - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, - KeyPress::Backspace => Cmd::BackwardDeleteChar, + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(self.num_args()), + KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), KeyPress::Tab => Cmd::Complete, KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Ctrl('N') => Cmd::NextHistory, KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Meta('\x08') => Cmd::BackwardKillWord(Word::Word), - KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(Word::Word), - // KeyPress::Meta('-') => { // digit-argument - // } - // KeyPress::Meta('0'...'9') => { // digit-argument - // } + KeyPress::Meta('\x08') => Cmd::BackwardKillWord(self.num_args(), Word::Word), + KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(self.num_args(), Word::Word), KeyPress::Meta('<') => Cmd::BeginningOfHistory, KeyPress::Meta('>') => Cmd::EndOfHistory, - KeyPress::Meta('B') => Cmd::BackwardWord(Word::Word), + KeyPress::Meta('B') => Cmd::BackwardWord(self.num_args(), Word::Word), KeyPress::Meta('C') => Cmd::CapitalizeWord, - KeyPress::Meta('D') => Cmd::KillWord(Word::Word), - KeyPress::Meta('F') => Cmd::ForwardWord(Word::Word), + KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), Word::Word), + KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), Word::Word), KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, @@ -135,27 +166,46 @@ impl EditState { Ok(cmd) } + fn vi_arg_digit(&mut self, + rdr: &mut R, + config: &Config, + digit: char) + -> Result { + self.num_args = digit.to_digit(10).unwrap() as i32; + loop { + let key = try!(rdr.next_key(config.keyseq_timeout())); + match key { + KeyPress::Char(digit @ '0'...'9') => { + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + } + _ => return Ok(key), + }; + } + } + fn vi_command(&mut self, rdr: &mut R, config: &Config) -> Result { - let key = try!(rdr.next_key(config.keyseq_timeout())); + let mut key = try!(rdr.next_key(config.keyseq_timeout())); + if let KeyPress::Char(digit @ '1'...'9') = key { + key = try!(self.vi_arg_digit(rdr, config, digit)); + } let cmd = match key { KeyPress::Char('$') => Cmd::EndOfLine, KeyPress::End => Cmd::EndOfLine, // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket KeyPress::Char('0') => Cmd::BeginningOfLine, // vi-zero: Vi move to the beginning of line. - // KeyPress::Char('1'...'9') => Cmd::???, // vi-arg-digit KeyPress::Char('^') => Cmd::BeginningOfLine, // vi-first-print TODO Move to the first non-blank character of line. KeyPress::Char('a') => { // vi-append-mode: Vi enter insert mode after the cursor. self.insert = true; - Cmd::ForwardChar + Cmd::ForwardChar(self.num_args()) } KeyPress::Char('A') => { // vi-append-eol: Vi enter insert mode at end of line. self.insert = true; Cmd::EndOfLine } - KeyPress::Char('b') => Cmd::BackwardWord(Word::ViWord), // vi-prev-word - KeyPress::Char('B') => Cmd::BackwardWord(Word::BigWord), + KeyPress::Char('b') => Cmd::BackwardWord(self.num_args(), Word::ViWord), // vi-prev-word + KeyPress::Char('B') => Cmd::BackwardWord(self.num_args(), Word::BigWord), KeyPress::Char('c') => { self.insert = true; try!(self.vi_delete_motion(rdr, config, key)) @@ -166,8 +216,8 @@ impl EditState { } KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), KeyPress::Char('D') => Cmd::KillLine, - KeyPress::Char('e') => Cmd::ViEndWord(Word::ViWord), - KeyPress::Char('E') => Cmd::ViEndWord(Word::BigWord), + KeyPress::Char('e') => Cmd::ViEndWord(self.num_args(), Word::ViWord), + KeyPress::Char('E') => Cmd::ViEndWord(self.num_args(), Word::BigWord), KeyPress::Char('i') => { // vi-insertion-mode self.insert = true; @@ -187,13 +237,13 @@ impl EditState { } } // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank, // vi-put - KeyPress::Char('P') => Cmd::Yank, // vi-put TODO Insert the yanked text before the cursor. + KeyPress::Char('p') => Cmd::Yank(self.num_args()), // vi-put FIXME cursor at end + KeyPress::Char('P') => Cmd::Yank(self.num_args()), // vi-put TODO Insert the yanked text before the cursor. KeyPress::Char('r') => { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); match ch { - KeyPress::Char(c) => Cmd::Replace(c), + KeyPress::Char(c) => Cmd::Replace(self.num_args(), c), KeyPress::Esc => Cmd::Noop, _ => Cmd::Unknown, } @@ -202,7 +252,7 @@ impl EditState { KeyPress::Char('s') => { // vi-substitute-char: Vi replace character under the cursor and enter insert mode. self.insert = true; - Cmd::DeleteChar + Cmd::DeleteChar(self.num_args()) } KeyPress::Char('S') => { // vi-substitute-line: Vi substitute entire line. @@ -210,18 +260,18 @@ impl EditState { Cmd::KillWholeLine } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord(Word::ViWord), // vi-next-word - KeyPress::Char('W') => Cmd::ForwardWord(Word::BigWord), // vi-next-word - KeyPress::Char('x') => Cmd::DeleteChar, // vi-delete: TODO move backward if eol - KeyPress::Char('X') => Cmd::BackwardDeleteChar, // vi-rubout + KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), Word::ViWord), // vi-next-word FIXME + KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), Word::BigWord), // vi-next-word FIXME + KeyPress::Char('x') => Cmd::DeleteChar(self.num_args()), // vi-delete: TODO move backward if eol + KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.num_args()), // vi-rubout // KeyPress::Char('y') => Cmd::???, // vi-yank-to // KeyPress::Char('Y') => Cmd::???, // vi-yank-to - KeyPress::Char('h') => Cmd::BackwardChar, - KeyPress::Ctrl('H') => Cmd::BackwardChar, - KeyPress::Backspace => Cmd::BackwardChar, // TODO Validate + KeyPress::Char('h') => Cmd::BackwardChar(self.num_args()), + KeyPress::Ctrl('H') => Cmd::BackwardChar(self.num_args()), + KeyPress::Backspace => Cmd::BackwardChar(self.num_args()), // TODO Validate KeyPress::Ctrl('G') => Cmd::Abort, - KeyPress::Char('l') => Cmd::ForwardChar, - KeyPress::Char(' ') => Cmd::ForwardChar, + KeyPress::Char('l') => Cmd::ForwardChar(self.num_args()), + KeyPress::Char(' ') => Cmd::ForwardChar(self.num_args()), KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Char('+') => Cmd::NextHistory, KeyPress::Char('j') => Cmd::NextHistory, @@ -248,13 +298,13 @@ impl EditState { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, - KeyPress::Backspace => Cmd::BackwardDeleteChar, + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(1), + KeyPress::Backspace => Cmd::BackwardDeleteChar(1), KeyPress::Tab => Cmd::Complete, KeyPress::Esc => { // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). self.insert = false; - Cmd::BackwardChar + Cmd::BackwardChar(1) } _ => self.common(key), }; @@ -266,31 +316,35 @@ impl EditState { config: &Config, key: KeyPress) -> Result { - let mvt = try!(rdr.next_key(config.keyseq_timeout())); + let mut mvt = try!(rdr.next_key(config.keyseq_timeout())); if mvt == key { return Ok(Cmd::KillWholeLine); } + if let KeyPress::Char(digit @ '1'...'9') = mvt { + // vi-arg-digit + mvt = try!(self.vi_arg_digit(rdr, config, digit)); + } Ok(match mvt { KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. - KeyPress::Char('b') => Cmd::BackwardKillWord(Word::ViWord), - KeyPress::Char('B') => Cmd::BackwardKillWord(Word::BigWord), - KeyPress::Char('e') => Cmd::KillWord(Word::ViWord), - KeyPress::Char('E') => Cmd::KillWord(Word::BigWord), + KeyPress::Char('b') => Cmd::BackwardKillWord(self.num_args(), Word::ViWord), + KeyPress::Char('B') => Cmd::BackwardKillWord(self.num_args(), Word::BigWord), + KeyPress::Char('e') => Cmd::KillWord(self.num_args(), Word::ViWord), + KeyPress::Char('E') => Cmd::KillWord(self.num_args(), Word::BigWord), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViKillTo(cs), + Some(cs) => Cmd::ViKillTo(self.num_args(), cs), None => Cmd::Unknown, } } - KeyPress::Char('h') => Cmd::BackwardDeleteChar, // vi-delete-prev-char: Vi move to previous character (backspace). - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, - KeyPress::Backspace => Cmd::BackwardDeleteChar, - KeyPress::Char('l') => Cmd::DeleteChar, - KeyPress::Char(' ') => Cmd::DeleteChar, - KeyPress::Char('w') => Cmd::KillWord(Word::ViWord), - KeyPress::Char('W') => Cmd::KillWord(Word::BigWord), + KeyPress::Char('h') => Cmd::BackwardDeleteChar(self.num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(self.num_args()), + KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), + KeyPress::Char('l') => Cmd::DeleteChar(self.num_args()), + KeyPress::Char(' ') => Cmd::DeleteChar(self.num_args()), + KeyPress::Char('w') => Cmd::KillWord(self.num_args(), Word::ViWord), // FIXME + KeyPress::Char('W') => Cmd::KillWord(self.num_args(), Word::BigWord), // FIXME _ => Cmd::Unknown, }) } @@ -318,12 +372,12 @@ impl EditState { fn common(&mut self, key: KeyPress) -> Cmd { match key { KeyPress::Home => Cmd::BeginningOfLine, - KeyPress::Left => Cmd::BackwardChar, + KeyPress::Left => Cmd::BackwardChar(self.num_args()), KeyPress::Ctrl('C') => Cmd::Interrupt, KeyPress::Ctrl('D') => Cmd::EndOfFile, - KeyPress::Delete => Cmd::DeleteChar, + KeyPress::Delete => Cmd::DeleteChar(self.num_args()), KeyPress::End => Cmd::EndOfLine, - KeyPress::Right => Cmd::ForwardChar, + KeyPress::Right => Cmd::ForwardChar(self.num_args()), KeyPress::Ctrl('J') => Cmd::AcceptLine, KeyPress::Enter => Cmd::AcceptLine, KeyPress::Down => Cmd::NextHistory, @@ -334,11 +388,20 @@ impl EditState { KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::BackwardKillWord(Word::BigWord), - KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Ctrl('W') => Cmd::BackwardKillWord(self.num_args(), Word::BigWord), + KeyPress::Ctrl('Y') => Cmd::Yank(self.num_args()), KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, } } + + fn num_args(&mut self) -> i32 { + let num_args = match self.num_args { + 0 => 1, + _ => self.num_args, + }; + self.num_args = 0; + num_args + } } diff --git a/src/lib.rs b/src/lib.rs index 714f1361b9..ce835f0b9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -622,7 +622,7 @@ fn complete_line(rdr: &mut R, s.old_rows += 1; while cmd != Cmd::SelfInsert('y') && cmd != Cmd::SelfInsert('Y') && cmd != Cmd::SelfInsert('n') && cmd != Cmd::SelfInsert('N') && - cmd != Cmd::BackwardDeleteChar { + cmd != Cmd::BackwardDeleteChar(1) { cmd = try!(s.next_cmd(rdr, config)); } show_completions = match cmd { @@ -670,7 +670,7 @@ fn page_completions(rdr: &mut R, cmd != Cmd::SelfInsert('q') && cmd != Cmd::SelfInsert('Q') && cmd != Cmd::SelfInsert(' ') && - cmd != Cmd::BackwardDeleteChar && cmd != Cmd::AcceptLine { + cmd != Cmd::BackwardDeleteChar(1) && cmd != Cmd::AcceptLine { cmd = try!(s.next_cmd(rdr, config)); } match cmd { @@ -741,7 +741,7 @@ fn reverse_incremental_search(rdr: &mut R, search_buf.push(c); } else { match cmd { - Cmd::BackwardDeleteChar => { + Cmd::BackwardDeleteChar(_) => { search_buf.pop(); continue; } @@ -846,17 +846,17 @@ fn readline_edit(prompt: &str, // Move to the beginning of line. try!(edit_move_home(&mut s)) } - Cmd::BackwardChar => { + Cmd::BackwardChar(_) => { editor.kill_ring.reset(); // Move back a character. try!(edit_move_left(&mut s)) } - Cmd::DeleteChar => { + Cmd::DeleteChar(_) => { editor.kill_ring.reset(); // Delete (forward) one character at point. try!(edit_delete(&mut s)) } - Cmd::Replace(c) => { + Cmd::Replace(_, c) => { editor.kill_ring.reset(); try!(edit_delete(&mut s)); try!(edit_insert(&mut s, c)); @@ -875,12 +875,12 @@ fn readline_edit(prompt: &str, // Move to the end of line. try!(edit_move_end(&mut s)) } - Cmd::ForwardChar => { + Cmd::ForwardChar(_) => { editor.kill_ring.reset(); // Move forward a character. try!(edit_move_right(&mut s)) } - Cmd::BackwardDeleteChar => { + Cmd::BackwardDeleteChar(_) => { editor.kill_ring.reset(); // Delete one character backward. try!(edit_backspace(&mut s)) @@ -930,7 +930,7 @@ fn readline_edit(prompt: &str, let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c)) // FIXME } - Cmd::Yank => { + Cmd::Yank(_) => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { try!(edit_yank(&mut s, text)) @@ -943,7 +943,7 @@ fn readline_edit(prompt: &str, try!(edit_move_end(&mut s)); break; } - Cmd::BackwardKillWord(word_def) => { + Cmd::BackwardKillWord(_, word_def) => { // kill one word backward if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def)) { editor.kill_ring.kill(&text, Mode::Prepend) @@ -959,7 +959,7 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, false)) } - Cmd::BackwardWord(word_def) => { + Cmd::BackwardWord(_, word_def) => { // move backwards one word editor.kill_ring.reset(); try!(edit_move_to_prev_word(&mut s, word_def)) @@ -969,13 +969,13 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - Cmd::KillWord(word_def) => { + Cmd::KillWord(_, word_def) => { // kill one word forward if let Some(text) = try!(edit_delete_word(&mut s, word_def)) { editor.kill_ring.kill(&text, Mode::Append) } } - Cmd::ForwardWord(word_def) => { + Cmd::ForwardWord(_, word_def) => { // move forwards one word editor.kill_ring.reset(); try!(edit_move_to_next_word(&mut s, word_def)) From 17b3048357d6309c62a100e388b985ba537ccf0b Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 17 Dec 2016 16:25:57 +0100 Subject: [PATCH 0231/1201] Fix Vi word actions --- src/keymap.rs | 95 ++++++++++++++++++++++++---------------------- src/lib.rs | 18 ++++----- src/line_buffer.rs | 80 ++++++++++++++++++++++++++++++++------ 3 files changed, 127 insertions(+), 66 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 1f4f013b41..54cb071f3d 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -7,47 +7,46 @@ use super::Result; #[derive(Debug, Clone, PartialEq)] pub enum Cmd { Abort, // Miscellaneous Command - AcceptLine, // Command For History - BackwardChar(i32), // Command For Moving - BackwardDeleteChar(i32), // Command For Text - BackwardKillWord(i32, Word), // Command For Killing - BackwardWord(i32, Word), // Command For Moving - BeginningOfHistory, // Command For History - BeginningOfLine, // Command For Moving - CapitalizeWord, // Command For Text - ClearScreen, // Command For Moving - Complete, // Command For Completion - DeleteChar(i32), // Command For Text - DowncaseWord, // Command For Text - EndOfFile, // Command For Text - EndOfHistory, // Command For History - EndOfLine, // Command For Moving - ForwardChar(i32), // Command For Moving - ForwardSearchHistory, // Command For History - ForwardWord(i32, Word), // Command For Moving + AcceptLine, + BackwardChar(i32), + BackwardDeleteChar(i32), + BackwardKillWord(i32, Word), // Backward until start of word + BackwardWord(i32, Word), // Backward until start of word + BeginningOfHistory, + BeginningOfLine, + CapitalizeWord, + ClearScreen, + Complete, + DeleteChar(i32), + DowncaseWord, + EndOfFile, + EndOfHistory, + EndOfLine, + ForwardChar(i32), + ForwardSearchHistory, + ForwardWord(i32, At, Word), // Forward until start/end of word Interrupt, - KillLine, // Command For Killing - KillWholeLine, // Command For Killing - KillWord(i32, Word), // Command For Killing - NextHistory, // Command For History + KillLine, + KillWholeLine, + KillWord(i32, At, Word), // Forward until start/end of word + NextHistory, Noop, - PreviousHistory, // Command For History - QuotedInsert, // Command For Text + PreviousHistory, + QuotedInsert, Replace(i32, char), // TODO DeleteChar + SelfInsert - ReverseSearchHistory, // Command For History - SelfInsert(char), // Command For Text + ReverseSearchHistory, + SelfInsert(char), Suspend, - TransposeChars, // Command For Text - TransposeWords, // Command For Text + TransposeChars, + TransposeWords, Unknown, - UnixLikeDiscard, // Command For Killing - // UnixWordRubout, // = BackwardKillWord(Word::BigWord) Command For Killing - UpcaseWord, // Command For Text + UnixLikeDiscard, + // UnixWordRubout, // = BackwardKillWord(Word::BigWord) + UpcaseWord, ViCharSearch(CharSearch), // TODO - ViEndWord(i32, Word), // TODO ViKillTo(i32, CharSearch), // TODO - Yank(i32), // Command For Killing - YankPop, // Command For Killing + Yank(i32), + YankPop, } #[derive(Debug, Clone, PartialEq, Copy)] @@ -60,6 +59,12 @@ pub enum Word { ViWord, } +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum At { + Start, + End, +} + #[derive(Debug, Clone, PartialEq)] pub enum CharSearch { Forward(char), @@ -107,7 +112,7 @@ impl EditState { match digit { '0'...'9' => { self.num_args = digit.to_digit(10).unwrap() as i32; - } + } '-' => { self.num_args = -1; } @@ -155,8 +160,8 @@ impl EditState { KeyPress::Meta('>') => Cmd::EndOfHistory, KeyPress::Meta('B') => Cmd::BackwardWord(self.num_args(), Word::Word), KeyPress::Meta('C') => Cmd::CapitalizeWord, - KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), Word::Word), - KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), Word::Word), + KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), At::End, Word::Word), + KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), At::End, Word::Word), KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, @@ -216,8 +221,8 @@ impl EditState { } KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), KeyPress::Char('D') => Cmd::KillLine, - KeyPress::Char('e') => Cmd::ViEndWord(self.num_args(), Word::ViWord), - KeyPress::Char('E') => Cmd::ViEndWord(self.num_args(), Word::BigWord), + KeyPress::Char('e') => Cmd::ForwardWord(self.num_args(), At::End, Word::ViWord), + KeyPress::Char('E') => Cmd::ForwardWord(self.num_args(), At::End, Word::BigWord), KeyPress::Char('i') => { // vi-insertion-mode self.insert = true; @@ -260,8 +265,8 @@ impl EditState { Cmd::KillWholeLine } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), Word::ViWord), // vi-next-word FIXME - KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), Word::BigWord), // vi-next-word FIXME + KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), At::Start, Word::ViWord), // vi-next-word + KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), At::Start, Word::BigWord), // vi-next-word KeyPress::Char('x') => Cmd::DeleteChar(self.num_args()), // vi-delete: TODO move backward if eol KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.num_args()), // vi-rubout // KeyPress::Char('y') => Cmd::???, // vi-yank-to @@ -329,8 +334,8 @@ impl EditState { KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. KeyPress::Char('b') => Cmd::BackwardKillWord(self.num_args(), Word::ViWord), KeyPress::Char('B') => Cmd::BackwardKillWord(self.num_args(), Word::BigWord), - KeyPress::Char('e') => Cmd::KillWord(self.num_args(), Word::ViWord), - KeyPress::Char('E') => Cmd::KillWord(self.num_args(), Word::BigWord), + KeyPress::Char('e') => Cmd::KillWord(self.num_args(), At::End, Word::ViWord), + KeyPress::Char('E') => Cmd::KillWord(self.num_args(), At::End, Word::BigWord), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { @@ -343,8 +348,8 @@ impl EditState { KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), KeyPress::Char('l') => Cmd::DeleteChar(self.num_args()), KeyPress::Char(' ') => Cmd::DeleteChar(self.num_args()), - KeyPress::Char('w') => Cmd::KillWord(self.num_args(), Word::ViWord), // FIXME - KeyPress::Char('W') => Cmd::KillWord(self.num_args(), Word::BigWord), // FIXME + KeyPress::Char('w') => Cmd::KillWord(self.num_args(), At::Start, Word::ViWord), + KeyPress::Char('W') => Cmd::KillWord(self.num_args(), At::Start, Word::BigWord), _ => Cmd::Unknown, }) } diff --git a/src/lib.rs b/src/lib.rs index ce835f0b9f..fb9c41a49c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ use tty::{RawMode, RawReader, Terminal, Term}; use completion::{Completer, longest_common_prefix}; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; -use keymap::{CharSearch, Cmd, EditState, Word}; +use keymap::{At, Cmd, EditState, Word}; use kill_ring::{Mode, KillRing}; pub use config::{CompletionType, Config, EditMode, HistoryDuplicates}; @@ -445,8 +445,8 @@ fn edit_delete_prev_word(s: &mut State, word_def: Word) -> Result } } -fn edit_move_to_next_word(s: &mut State, word_def: Word) -> Result<()> { - if s.line.move_to_next_word(word_def) { +fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word) -> Result<()> { + if s.line.move_to_next_word(at, word_def) { s.refresh_line() } else { Ok(()) @@ -454,8 +454,8 @@ fn edit_move_to_next_word(s: &mut State, word_def: Word) -> Result<()> { } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. -fn edit_delete_word(s: &mut State, word_def: Word) -> Result> { - if let Some(text) = s.line.delete_word(word_def) { +fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result> { + if let Some(text) = s.line.delete_word(at, word_def) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -969,16 +969,16 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - Cmd::KillWord(_, word_def) => { + Cmd::KillWord(_, at, word_def) => { // kill one word forward - if let Some(text) = try!(edit_delete_word(&mut s, word_def)) { + if let Some(text) = try!(edit_delete_word(&mut s, at, word_def)) { editor.kill_ring.kill(&text, Mode::Append) } } - Cmd::ForwardWord(_, word_def) => { + Cmd::ForwardWord(_, at, word_def) => { // move forwards one word editor.kill_ring.reset(); - try!(edit_move_to_next_word(&mut s, word_def)) + try!(edit_move_to_next_word(&mut s, at, word_def)) } Cmd::DowncaseWord => { // lowercase word after point diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 854fa49d55..3c9ecaabaa 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,6 +1,6 @@ //! Line buffer with current cursor position use std::ops::Deref; -use keymap::Word; +use keymap::{At, Word}; /// Maximum buffer size for the line read pub static MAX_LINE: usize = 4096; @@ -262,6 +262,7 @@ impl LineBuffer { true } + /// Go left until start of word fn prev_word_pos(&self, pos: usize, word_def: Word) -> Option { if pos == 0 { return None; @@ -309,8 +310,46 @@ impl LineBuffer { } } + fn next_pos(&self, pos: usize, at: At, word_def: Word) -> Option { + match at { + At::End => { + match self.next_word_pos(pos, word_def) { + Some((_, end)) => Some(end), + _ => None, + } + } + At::Start => self.next_start_of_word_pos(pos, word_def), + } + } + + /// Go right until start of word + fn next_start_of_word_pos(&self, pos: usize, word_def: Word) -> Option { + if pos < self.buf.len() { + let test = is_break_char(word_def); + let mut pos = pos; + // eat any non-spaces + pos += self.buf[pos..] + .chars() + .take_while(|ch| !test(ch)) + .map(char::len_utf8) + .sum(); + if pos < self.buf.len() { + // eat any spaces + pos += self.buf[pos..] + .chars() + .take_while(test) + .map(char::len_utf8) + .sum(); + } + Some(pos) + } else { + None + } + } + + /// Go right until end of word /// Returns the position (start, end) of the next word. - pub fn next_word_pos(&self, pos: usize, word_def: Word) -> Option<(usize, usize)> { + fn next_word_pos(&self, pos: usize, word_def: Word) -> Option<(usize, usize)> { if pos < self.buf.len() { let test = is_break_char(word_def); let mut pos = pos; @@ -336,22 +375,21 @@ impl LineBuffer { } /// Moves the cursor to the end of next word. - pub fn move_to_next_word(&mut self, word_def: Word) -> bool { - if let Some((_, end)) = self.next_word_pos(self.pos, word_def) { - self.pos = end; + pub fn move_to_next_word(&mut self, at: At, word_def: Word) -> bool { + if let Some(pos) = self.next_pos(self.pos, at, word_def) { + self.pos = pos; true } else { false } } - // TODO move_to_end_of_word (Vi mode) // TODO move_to (Vi mode: f|F|t|T) /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. - pub fn delete_word(&mut self, word_def: Word) -> Option { - if let Some((_, end)) = self.next_word_pos(self.pos, word_def) { - let word = self.buf.drain(self.pos..end).collect(); + pub fn delete_word(&mut self, at: At, word_def: Word) -> Option { + if let Some(pos) = self.next_pos(self.pos, at, word_def) { + let word = self.buf.drain(self.pos..pos).collect(); Some(word) } else { None @@ -490,7 +528,7 @@ fn is_whitespace(ch: &char) -> bool { #[cfg(test)] mod test { - use super::{LineBuffer, MAX_LINE, Word, WordAction}; + use super::{LineBuffer, MAX_LINE, At, Word, WordAction}; #[test] fn insert() { @@ -609,21 +647,39 @@ mod test { #[test] fn move_to_next_word() { let mut s = LineBuffer::init("a ß c", 1); - let ok = s.move_to_next_word(Word::Word); + let ok = s.move_to_next_word(At::End, Word::Word); assert_eq!("a ß c", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); } + #[test] + fn move_to_start_of_word() { + let mut s = LineBuffer::init("a ß c", 2); + let ok = s.move_to_next_word(At::Start, Word::Word); + assert_eq!("a ß c", s.buf); + assert_eq!(6, s.pos); + assert_eq!(true, ok); + } + #[test] fn delete_word() { let mut s = LineBuffer::init("a ß c", 1); - let text = s.delete_word(Word::Word); + let text = s.delete_word(At::End, Word::Word); assert_eq!("a c", s.buf); assert_eq!(1, s.pos); assert_eq!(Some(" ß".to_string()), text); } + #[test] + fn delete_til_start_of_word() { + let mut s = LineBuffer::init("a ß c", 2); + let text = s.delete_word(At::Start, Word::Word); + assert_eq!("a c", s.buf); + assert_eq!(2, s.pos); + assert_eq!(Some("ß ".to_string()), text); + } + #[test] fn edit_word() { let mut s = LineBuffer::init("a ßeta c", 1); From 61631dd1e2528fce1ff697304ce5669674dcf780 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 10 Dec 2016 19:05:15 +0100 Subject: [PATCH 0232/1201] First draft for vi mode support (#94) --- README.md | 3 + src/config.rs | 17 +++ src/keymap.rs | 338 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- src/line_buffer.rs | 11 ++ src/tty/unix.rs | 2 + src/tty/windows.rs | 4 + 7 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 src/keymap.rs diff --git a/README.md b/README.md index 05ea0dcea0..6c8f0e300a 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,9 @@ Meta-U | Upper-case the next word Meta-Y | See Ctrl-Y Meta-BackSpace | Kill from the start of the current word, or, if between words, to the start of the previous word +[Readline Emacs Editing Mode Cheat Sheet](http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf) +[Terminal codes (ANSI/VT100)](http://wiki.bash-hackers.org/scripting/terminalcodes) + ## ToDo - Undos diff --git a/src/config.rs b/src/config.rs index 2708527e8d..2585c3e33a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,6 +13,7 @@ pub struct Config { completion_prompt_limit: usize, /// Duration (milliseconds) Rustyline will wait for a character when reading an ambiguous key sequence. keyseq_timeout: i32, + edit_mode: EditMode, } impl Config { @@ -48,6 +49,10 @@ impl Config { pub fn keyseq_timeout(&self) -> i32 { self.keyseq_timeout } + + pub fn edit_mode(&self) -> EditMode { + self.edit_mode + } } impl Default for Config { @@ -59,6 +64,7 @@ impl Default for Config { completion_type: CompletionType::Circular, // TODO Validate completion_prompt_limit: 100, keyseq_timeout: 500, + edit_mode: EditMode::Emacs, } } } @@ -79,6 +85,12 @@ pub enum CompletionType { List, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum EditMode { + Emacs, + Vi, +} + #[derive(Debug)] pub struct Builder { p: Config, @@ -130,6 +142,11 @@ impl Builder { self } + pub fn edit_mode(mut self, edit_mode: EditMode) -> Builder { + self.p.edit_mode = edit_mode; + self + } + pub fn build(self) -> Config { self.p } diff --git a/src/keymap.rs b/src/keymap.rs new file mode 100644 index 0000000000..5acf4c1b19 --- /dev/null +++ b/src/keymap.rs @@ -0,0 +1,338 @@ +use super::Config; +use super::EditMode; +use super::KeyPress; +use super::RawReader; +use super::Result; + +//#[derive(Clone)] +pub enum Cmd { + Abort, // Miscellaneous Command + AcceptLine, // Command For History + BackwardChar, // Command For Moving + BackwardDeleteChar, // Command For Text + BackwardKillWord, // Command For Killing + BackwardWord, // Command For Moving + BeginningOfHistory, // Command For History + BeginningOfLine, // Command For Moving + CapitalizeWord, // Command For Text + CharacterSearch(bool), // Miscellaneous Command (TODO Move right to the next occurance of c) + CharacterSearchBackward(bool), /* Miscellaneous Command (TODO Move left to the previous occurance of c) */ + ClearScreen, // Command For Moving + Complete, // Command For Completion + DeleteChar, // Command For Text + DowncaseWord, // Command For Text + EndOfFile, // Command For Text + EndOfHistory, // Command For History + EndOfLine, // Command For Moving + ForwardChar, // Command For Moving + ForwardSearchHistory, // Command For History + ForwardWord, // Command For Moving + KillLine, // Command For Killing + KillWholeLine, // Command For Killing (TODO Delete current line) + KillWord, // Command For Killing + NextHistory, // Command For History + Noop, + PreviousHistory, // Command For History + QuotedInsert, // Command For Text + Replace, // TODO DeleteChar + SelfInsert + ReverseSearchHistory, // Command For History + SelfInsert, // Command For Text + TransposeChars, // Command For Text + TransposeWords, // Command For Text + Unknown, + UnixLikeDiscard, // Command For Killing + UnixWordRubout, // Command For Killing + UpcaseWord, // Command For Text + Yank, // Command For Killing + YankPop, // Command For Killing +} + +// TODO numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 +pub struct EditState { + mode: EditMode, + // TODO Validate Vi Command, Insert, Visual mode + insert: bool, // vi only ? +} + +impl EditState { + pub fn new(config: &Config) -> EditState { + EditState { + mode: config.edit_mode(), + insert: true, + } + } + + pub fn next_cmd(&mut self, + rdr: &mut R, + config: &Config) + -> Result<(KeyPress, Cmd)> { + match self.mode { + EditMode::Emacs => self.emacs(rdr, config), + EditMode::Vi if self.insert => self.vi_insert(rdr, config), + EditMode::Vi => self.vi_command(rdr, config), + } + } + + fn emacs(&mut self, rdr: &mut R, config: &Config) -> Result<(KeyPress, Cmd)> { + let key = try!(rdr.next_key(config.keyseq_timeout())); + let cmd = match key { + KeyPress::Char(_) => Cmd::SelfInsert, + KeyPress::Esc => Cmd::Abort, // TODO Validate + KeyPress::Ctrl('A') => Cmd::BeginningOfLine, + KeyPress::Home => Cmd::BeginningOfLine, + KeyPress::Ctrl('B') => Cmd::BackwardChar, + KeyPress::Left => Cmd::BackwardChar, + // KeyPress::Ctrl('D') if s.line.is_empty() => Cmd::EndOfFile, + KeyPress::Ctrl('D') => Cmd::DeleteChar, + KeyPress::Delete => Cmd::DeleteChar, + KeyPress::Ctrl('E') => Cmd::EndOfLine, + KeyPress::End => Cmd::EndOfLine, + KeyPress::Ctrl('F') => Cmd::ForwardChar, + KeyPress::Right => Cmd::ForwardChar, + KeyPress::Ctrl('G') => Cmd::Abort, + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, + KeyPress::Backspace => Cmd::BackwardDeleteChar, + KeyPress::Tab => Cmd::Complete, + KeyPress::Ctrl('J') => Cmd::AcceptLine, + KeyPress::Enter => Cmd::AcceptLine, + KeyPress::Ctrl('K') => Cmd::KillLine, + KeyPress::Ctrl('L') => Cmd::ClearScreen, + KeyPress::Ctrl('N') => Cmd::NextHistory, + KeyPress::Down => Cmd::NextHistory, + KeyPress::Ctrl('P') => Cmd::PreviousHistory, + KeyPress::Up => Cmd::PreviousHistory, + KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution + KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, + KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution + KeyPress::Ctrl('T') => Cmd::TransposeChars, + KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, + KeyPress::Ctrl('V') => Cmd::QuotedInsert, + KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Meta('\x08') => Cmd::BackwardKillWord, + KeyPress::Meta('\x7f') => Cmd::BackwardKillWord, + // KeyPress::Meta('-') => { // digit-argument + // } + // KeyPress::Meta('0'...'9') => { // digit-argument + // } + KeyPress::Meta('<') => Cmd::BeginningOfHistory, + KeyPress::Meta('>') => Cmd::EndOfHistory, + KeyPress::Meta('B') => Cmd::BackwardWord, + KeyPress::Meta('C') => Cmd::CapitalizeWord, + KeyPress::Meta('D') => Cmd::KillWord, + KeyPress::Meta('F') => Cmd::ForwardWord, + KeyPress::Meta('L') => Cmd::DowncaseWord, + KeyPress::Meta('T') => Cmd::TransposeWords, + KeyPress::Meta('U') => Cmd::UpcaseWord, + KeyPress::Meta('Y') => Cmd::YankPop, + _ => Cmd::Unknown, + }; + Ok((key, cmd)) + } + + fn vi_command(&mut self, + rdr: &mut R, + config: &Config) + -> Result<(KeyPress, Cmd)> { + let key = try!(rdr.next_key(config.keyseq_timeout())); + let cmd = match key { + KeyPress::Char('$') => Cmd::EndOfLine, + // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket + KeyPress::Char('0') => Cmd::BeginningOfLine, // vi-zero: Vi move to the beginning of line. + // KeyPress::Char('1'...'9') => Cmd::???, // vi-arg-digit + KeyPress::Char('^') => Cmd::BeginningOfLine, // TODO Move to the first non-blank character of line. + KeyPress::Char('a') => { + // vi-append-mode: Vi enter insert mode after the cursor. + self.insert = true; + Cmd::ForwardChar + } + KeyPress::Char('A') => { + // vi-append-eol: Vi enter insert mode at end of line. + self.insert = true; + Cmd::EndOfLine + } + KeyPress::Char('b') => Cmd::BackwardWord, + // TODO KeyPress::Char('B') => Cmd::???, Move one non-blank word left. + KeyPress::Char('c') => { + self.insert = true; + let mvt = try!(rdr.next_key(config.keyseq_timeout())); + match mvt { + KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. + KeyPress::Char('0') => Cmd::UnixLikeDiscard, + KeyPress::Char('c') => Cmd::KillWholeLine, + // TODO KeyPress::Char('f') => ???, + // TODO KeyPress::Char('F') => ???, + KeyPress::Char('h') => Cmd::BackwardDeleteChar, + KeyPress::Char('l') => Cmd::DeleteChar, + KeyPress::Char(' ') => Cmd::DeleteChar, + // TODO KeyPress::Char('t') => ???, + // TODO KeyPress::Char('T') => ???, + KeyPress::Char('w') => Cmd::KillWord, + _ => Cmd::Unknown, + } + } + KeyPress::Char('C') => { + self.insert = true; + Cmd::KillLine + } + KeyPress::Char('d') => { + let mvt = try!(rdr.next_key(config.keyseq_timeout())); + match mvt { + KeyPress::Char('$') => Cmd::KillLine, + KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. + KeyPress::Char('d') => Cmd::KillWholeLine, + // TODO KeyPress::Char('f') => ???, + // TODO KeyPress::Char('F') => ???, + KeyPress::Char('h') => Cmd::BackwardDeleteChar, // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Char('l') => Cmd::DeleteChar, + KeyPress::Char(' ') => Cmd::DeleteChar, + // TODO KeyPress::Char('t') => ???, + // TODO KeyPress::Char('T') => ???, + KeyPress::Char('w') => Cmd::KillWord, + _ => Cmd::Unknown, + } + } + KeyPress::Char('D') => Cmd::KillLine, + // TODO KeyPress::Char('e') => Cmd::???, vi-to-end-word: Vi move to the end of the current word. Move to the end of the current word. + // TODO KeyPress::Char('E') => Cmd::???, vi-end-word: Vi move to the end of the current space delimited word. Move to the end of the current non-blank word. + KeyPress::Char('i') => { + // vi-insert: Vi enter insert mode. + self.insert = true; + Cmd::Noop + } + KeyPress::Char('I') => { + // vi-insert-at-bol: Vi enter insert mode at the beginning of line. + self.insert = true; + Cmd::BeginningOfLine + } + KeyPress::Char('f') => { + // vi-next-char: Vi move to the character specified next. + let ch = try!(rdr.next_key(config.keyseq_timeout())); + match ch { + KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearch(false))), + _ => Cmd::Unknown, + } + } + KeyPress::Char('F') => { + // vi-prev-char: Vi move to the character specified previous. + let ch = try!(rdr.next_key(config.keyseq_timeout())); + match ch { + KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearchBackward(false))), + _ => Cmd::Unknown, + } + } + // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n + KeyPress::Char('p') => Cmd::Yank, // vi-paste-next: Vi paste previous deletion to the right of the cursor. + KeyPress::Char('P') => Cmd::Yank, // vi-paste-prev: Vi paste previous deletion to the left of the cursor. TODO Insert the yanked text before the cursor. + KeyPress::Char('r') => { + // vi-replace-char: Vi replace character under the cursor with the next character typed. + let ch = try!(rdr.next_key(config.keyseq_timeout())); + match ch { + KeyPress::Char(_) => return Ok((ch, Cmd::Replace)), + KeyPress::Esc => Cmd::Noop, + _ => Cmd::Unknown, + } + } + // TODO KeyPress::Char('R') => Cmd::???, vi-replace-mode: Vi enter replace mode. Replaces characters under the cursor. (overwrite-mode) + KeyPress::Char('s') => { + // vi-substitute-char: Vi replace character under the cursor and enter insert mode. + self.insert = true; + Cmd::DeleteChar + } + KeyPress::Char('S') => { + // vi-substitute-line: Vi substitute entire line. + self.insert = true; + Cmd::KillWholeLine + } + KeyPress::Char('t') => { + // vi-to-next-char: Vi move up to the character specified next. + let ch = try!(rdr.next_key(config.keyseq_timeout())); + match ch { + KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearchBackward(true))), + _ => Cmd::Unknown, + } + } + KeyPress::Char('T') => { + // vi-to-prev-char: Vi move up to the character specified previous. + let ch = try!(rdr.next_key(config.keyseq_timeout())); + match ch { + KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearch(true))), + _ => Cmd::Unknown, + } + } + // KeyPress::Char('U') => Cmd::???, // revert-line + KeyPress::Char('w') => Cmd::ForwardWord, // vi-next-word: Vi move to the next word. + // TODO KeyPress::Char('W') => Cmd::???, // vi-next-space-word: Vi move to the next space delimited word. Move one non-blank word right. + KeyPress::Char('x') => Cmd::DeleteChar, // vi-delete: TODO move backward if eol + KeyPress::Char('X') => Cmd::BackwardDeleteChar, + KeyPress::Home => Cmd::BeginningOfLine, + KeyPress::Char('h') => Cmd::BackwardChar, + KeyPress::Left => Cmd::BackwardChar, + KeyPress::Ctrl('D') => Cmd::EndOfFile, + KeyPress::Delete => Cmd::DeleteChar, + KeyPress::End => Cmd::EndOfLine, + KeyPress::Ctrl('G') => Cmd::Abort, + KeyPress::Ctrl('H') => Cmd::BackwardChar, + KeyPress::Char('l') => Cmd::ForwardChar, + KeyPress::Char(' ') => Cmd::ForwardChar, + KeyPress::Right => Cmd::ForwardChar, + KeyPress::Ctrl('L') => Cmd::ClearScreen, + KeyPress::Ctrl('J') => Cmd::AcceptLine, + KeyPress::Enter => Cmd::AcceptLine, + KeyPress::Char('+') => Cmd::NextHistory, + KeyPress::Char('j') => Cmd::NextHistory, + KeyPress::Ctrl('N') => Cmd::NextHistory, + KeyPress::Down => Cmd::NextHistory, + KeyPress::Char('-') => Cmd::PreviousHistory, + KeyPress::Char('k') => Cmd::PreviousHistory, + KeyPress::Ctrl('P') => Cmd::PreviousHistory, + KeyPress::Up => Cmd::PreviousHistory, + KeyPress::Ctrl('K') => Cmd::KillLine, + KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution + KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, + KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, + KeyPress::Ctrl('T') => Cmd::TransposeChars, + KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, + KeyPress::Ctrl('V') => Cmd::QuotedInsert, + KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Esc => Cmd::Noop, + _ => Cmd::Unknown, + }; + Ok((key, cmd)) + } + + fn vi_insert(&mut self, rdr: &mut R, config: &Config) -> Result<(KeyPress, Cmd)> { + let key = try!(rdr.next_key(config.keyseq_timeout())); + let cmd = match key { + KeyPress::Char(_) => Cmd::SelfInsert, + KeyPress::Home => Cmd::BeginningOfLine, + KeyPress::Left => Cmd::BackwardChar, + KeyPress::Ctrl('D') => Cmd::EndOfFile, // vi-eof-maybe + KeyPress::Delete => Cmd::DeleteChar, + KeyPress::End => Cmd::EndOfLine, + KeyPress::Right => Cmd::ForwardChar, + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, + KeyPress::Backspace => Cmd::BackwardDeleteChar, + KeyPress::Tab => Cmd::Complete, + KeyPress::Ctrl('J') => Cmd::AcceptLine, + KeyPress::Enter => Cmd::AcceptLine, + KeyPress::Down => Cmd::NextHistory, + KeyPress::Up => Cmd::PreviousHistory, + KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, + KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, + KeyPress::Ctrl('T') => Cmd::TransposeChars, + KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, + KeyPress::Ctrl('V') => Cmd::QuotedInsert, + KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Esc => { + // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). + self.insert = false; + Cmd::Noop + } + _ => Cmd::Unknown, + }; + Ok((key, cmd)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8143fc21bd..db51f4725b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,7 @@ pub mod completion; mod consts; pub mod error; pub mod history; +mod keymap; mod kill_ring; pub mod line_buffer; #[cfg(unix)] @@ -51,7 +52,7 @@ use consts::KeyPress; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; use kill_ring::{Mode, KillRing}; -pub use config::{CompletionType, Config, HistoryDuplicates}; +pub use config::{CompletionType, Config, EditMode, HistoryDuplicates}; /// The error type for I/O and Linux Syscalls (Errno) pub type Result = result::Result; diff --git a/src/line_buffer.rs b/src/line_buffer.rs index ddaddb72a6..5bae7cc516 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -183,6 +183,11 @@ impl LineBuffer { } } + /// Replace a single character under the cursor (Vi mode) + pub fn replace_char(&mut self, ch: char) -> Option { + if self.delete() { self.insert(ch) } else { None } + } + /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. pub fn delete(&mut self) -> bool { @@ -206,6 +211,12 @@ impl LineBuffer { } } + /// Kill all characters on the current line. + pub fn kill_whole_line(&mut self) -> Option { + self.move_home(); + self.kill_line() + } + /// Kill the text from point to the end of the line. pub fn kill_line(&mut self) -> Option { if !self.buf.is_empty() && self.pos < self.buf.len() { diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 90085c4e81..a7bc01392c 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -153,6 +153,8 @@ impl PosixRawReader { // TODO ESC-R (r): Undo all changes made to this line. match seq1 { '\x08' => Ok(KeyPress::Meta('\x08')), // Backspace + '-' => return Ok(KeyPress::Meta('-')), + '0'...'9' => return Ok(KeyPress::Meta(seq1)), '<' => Ok(KeyPress::Meta('<')), '>' => Ok(KeyPress::Meta('>')), 'b' | 'B' => Ok(KeyPress::Meta('B')), diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 4b2133835d..1b3fdeea6d 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -149,6 +149,10 @@ impl RawReader for ConsoleRawReader { let c = try!(orc.unwrap()); if meta { match c { + '-' => return Ok(KeyPress::Meta('-')), + '0'...'9' => return Ok(KeyPress::Meta(c)), + '<' => Ok(KeyPress::Meta('<')), + '>' => Ok(KeyPress::Meta('>')), 'b' | 'B' => return Ok(KeyPress::Meta('B')), 'c' | 'C' => return Ok(KeyPress::Meta('C')), 'd' | 'D' => return Ok(KeyPress::Meta('D')), From 17c8d49d4ca766e07b33a9b6832e84c1fb2f2531 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 11 Dec 2016 11:33:14 +0100 Subject: [PATCH 0233/1201] Second draft for vi mode support (#94) --- src/keymap.rs | 208 +++++++++++++++++++++++++++----------------------- 1 file changed, 114 insertions(+), 94 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 5acf4c1b19..e2a44e5e2d 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -10,13 +10,11 @@ pub enum Cmd { AcceptLine, // Command For History BackwardChar, // Command For Moving BackwardDeleteChar, // Command For Text - BackwardKillWord, // Command For Killing - BackwardWord, // Command For Moving + BackwardKillWord(Word), // Command For Killing + BackwardWord(Word), // Command For Moving BeginningOfHistory, // Command For History BeginningOfLine, // Command For Moving CapitalizeWord, // Command For Text - CharacterSearch(bool), // Miscellaneous Command (TODO Move right to the next occurance of c) - CharacterSearchBackward(bool), /* Miscellaneous Command (TODO Move left to the previous occurance of c) */ ClearScreen, // Command For Moving Complete, // Command For Completion DeleteChar, // Command For Text @@ -26,12 +24,12 @@ pub enum Cmd { EndOfLine, // Command For Moving ForwardChar, // Command For Moving ForwardSearchHistory, // Command For History - ForwardWord, // Command For Moving + ForwardWord(Word), // Command For Moving KillLine, // Command For Killing KillWholeLine, // Command For Killing (TODO Delete current line) - KillWord, // Command For Killing + KillWord(Word), // Command For Killing NextHistory, // Command For History - Noop, + Noop, // TODO PreviousHistory, // Command For History QuotedInsert, // Command For Text Replace, // TODO DeleteChar + SelfInsert @@ -41,16 +39,37 @@ pub enum Cmd { TransposeWords, // Command For Text Unknown, UnixLikeDiscard, // Command For Killing - UnixWordRubout, // Command For Killing + // UnixWordRubout, // = KillWord(Word::BigWord) Command For Killing UpcaseWord, // Command For Text + ViCharSearch(CharSearch), // TODO + ViEndWord(Word), // TODO + ViKillTo(CharSearch), // TODO Yank, // Command For Killing YankPop, // Command For Killing } +pub enum Word { + // non-blanks characters + BigWord, + // alphanumeric characters + Word, + // alphanumeric (and '_') characters + ViWord, +} + +pub enum CharSearch { + Forward(char), + // until + ForwardBefore(char), + Backward(char), + // until + BackwardAfter(char), +} + // TODO numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 pub struct EditState { mode: EditMode, - // TODO Validate Vi Command, Insert, Visual mode + // Vi Command/Alternate, Insert/Input mode insert: bool, // vi only ? } @@ -107,20 +126,20 @@ impl EditState { KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, - KeyPress::Meta('\x08') => Cmd::BackwardKillWord, - KeyPress::Meta('\x7f') => Cmd::BackwardKillWord, + KeyPress::Meta('\x08') => Cmd::BackwardKillWord(Word::Word), + KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(Word::Word), // KeyPress::Meta('-') => { // digit-argument // } // KeyPress::Meta('0'...'9') => { // digit-argument // } KeyPress::Meta('<') => Cmd::BeginningOfHistory, KeyPress::Meta('>') => Cmd::EndOfHistory, - KeyPress::Meta('B') => Cmd::BackwardWord, + KeyPress::Meta('B') => Cmd::BackwardWord(Word::Word), KeyPress::Meta('C') => Cmd::CapitalizeWord, - KeyPress::Meta('D') => Cmd::KillWord, - KeyPress::Meta('F') => Cmd::ForwardWord, + KeyPress::Meta('D') => Cmd::KillWord(Word::Word), + KeyPress::Meta('F') => Cmd::ForwardWord(Word::Word), KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, @@ -137,10 +156,12 @@ impl EditState { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { KeyPress::Char('$') => Cmd::EndOfLine, + KeyPress::End => Cmd::EndOfLine, // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket KeyPress::Char('0') => Cmd::BeginningOfLine, // vi-zero: Vi move to the beginning of line. + KeyPress::Home => Cmd::BeginningOfLine, // KeyPress::Char('1'...'9') => Cmd::???, // vi-arg-digit - KeyPress::Char('^') => Cmd::BeginningOfLine, // TODO Move to the first non-blank character of line. + KeyPress::Char('^') => Cmd::BeginningOfLine, // vi-first-print TODO Move to the first non-blank character of line. KeyPress::Char('a') => { // vi-append-mode: Vi enter insert mode after the cursor. self.insert = true; @@ -151,79 +172,41 @@ impl EditState { self.insert = true; Cmd::EndOfLine } - KeyPress::Char('b') => Cmd::BackwardWord, - // TODO KeyPress::Char('B') => Cmd::???, Move one non-blank word left. + KeyPress::Char('b') => Cmd::BackwardWord(Word::ViWord), // vi-prev-word + KeyPress::Char('B') => Cmd::BackwardWord(Word::BigWord), KeyPress::Char('c') => { self.insert = true; - let mvt = try!(rdr.next_key(config.keyseq_timeout())); - match mvt { - KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. - KeyPress::Char('0') => Cmd::UnixLikeDiscard, - KeyPress::Char('c') => Cmd::KillWholeLine, - // TODO KeyPress::Char('f') => ???, - // TODO KeyPress::Char('F') => ???, - KeyPress::Char('h') => Cmd::BackwardDeleteChar, - KeyPress::Char('l') => Cmd::DeleteChar, - KeyPress::Char(' ') => Cmd::DeleteChar, - // TODO KeyPress::Char('t') => ???, - // TODO KeyPress::Char('T') => ???, - KeyPress::Char('w') => Cmd::KillWord, - _ => Cmd::Unknown, - } + try!(self.vi_delete_motion(rdr, config, key)) } KeyPress::Char('C') => { self.insert = true; Cmd::KillLine } - KeyPress::Char('d') => { - let mvt = try!(rdr.next_key(config.keyseq_timeout())); - match mvt { - KeyPress::Char('$') => Cmd::KillLine, - KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. - KeyPress::Char('d') => Cmd::KillWholeLine, - // TODO KeyPress::Char('f') => ???, - // TODO KeyPress::Char('F') => ???, - KeyPress::Char('h') => Cmd::BackwardDeleteChar, // vi-delete-prev-char: Vi move to previous character (backspace). - KeyPress::Char('l') => Cmd::DeleteChar, - KeyPress::Char(' ') => Cmd::DeleteChar, - // TODO KeyPress::Char('t') => ???, - // TODO KeyPress::Char('T') => ???, - KeyPress::Char('w') => Cmd::KillWord, - _ => Cmd::Unknown, - } - } + KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), KeyPress::Char('D') => Cmd::KillLine, - // TODO KeyPress::Char('e') => Cmd::???, vi-to-end-word: Vi move to the end of the current word. Move to the end of the current word. - // TODO KeyPress::Char('E') => Cmd::???, vi-end-word: Vi move to the end of the current space delimited word. Move to the end of the current non-blank word. + KeyPress::Char('e') => Cmd::ViEndWord(Word::ViWord), + KeyPress::Char('E') => Cmd::ViEndWord(Word::BigWord), KeyPress::Char('i') => { - // vi-insert: Vi enter insert mode. + // vi-insertion-mode self.insert = true; Cmd::Noop } KeyPress::Char('I') => { - // vi-insert-at-bol: Vi enter insert mode at the beginning of line. + // vi-insert-beg self.insert = true; Cmd::BeginningOfLine } - KeyPress::Char('f') => { - // vi-next-char: Vi move to the character specified next. - let ch = try!(rdr.next_key(config.keyseq_timeout())); - match ch { - KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearch(false))), - _ => Cmd::Unknown, - } - } - KeyPress::Char('F') => { - // vi-prev-char: Vi move to the character specified previous. - let ch = try!(rdr.next_key(config.keyseq_timeout())); - match ch { - KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearchBackward(false))), - _ => Cmd::Unknown, + KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { + // vi-char-search + let cs = try!(self.vi_char_search(rdr, config, c)); + match cs { + Some(cs) => Cmd::ViCharSearch(cs), + None => Cmd::Unknown, } } // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank, // vi-paste-next: Vi paste previous deletion to the right of the cursor. - KeyPress::Char('P') => Cmd::Yank, // vi-paste-prev: Vi paste previous deletion to the left of the cursor. TODO Insert the yanked text before the cursor. + KeyPress::Char('p') => Cmd::Yank, // vi-put + KeyPress::Char('P') => Cmd::Yank, // vi-put TODO Insert the yanked text before the cursor. KeyPress::Char('r') => { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); @@ -244,35 +227,20 @@ impl EditState { self.insert = true; Cmd::KillWholeLine } - KeyPress::Char('t') => { - // vi-to-next-char: Vi move up to the character specified next. - let ch = try!(rdr.next_key(config.keyseq_timeout())); - match ch { - KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearchBackward(true))), - _ => Cmd::Unknown, - } - } - KeyPress::Char('T') => { - // vi-to-prev-char: Vi move up to the character specified previous. - let ch = try!(rdr.next_key(config.keyseq_timeout())); - match ch { - KeyPress::Char(_) => return Ok((ch, Cmd::CharacterSearch(true))), - _ => Cmd::Unknown, - } - } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord, // vi-next-word: Vi move to the next word. - // TODO KeyPress::Char('W') => Cmd::???, // vi-next-space-word: Vi move to the next space delimited word. Move one non-blank word right. + KeyPress::Char('w') => Cmd::ForwardWord(Word::ViWord), // vi-next-word + KeyPress::Char('W') => Cmd::ForwardWord(Word::BigWord), // vi-next-word KeyPress::Char('x') => Cmd::DeleteChar, // vi-delete: TODO move backward if eol - KeyPress::Char('X') => Cmd::BackwardDeleteChar, - KeyPress::Home => Cmd::BeginningOfLine, + KeyPress::Char('X') => Cmd::BackwardDeleteChar, // vi-rubout + // KeyPress::Char('y') => Cmd::???, // vi-yank-to + // KeyPress::Char('Y') => Cmd::???, // vi-yank-to KeyPress::Char('h') => Cmd::BackwardChar, + KeyPress::Ctrl('H') => Cmd::BackwardChar, + KeyPress::Backspace => Cmd::BackwardChar, // TODO Validate KeyPress::Left => Cmd::BackwardChar, KeyPress::Ctrl('D') => Cmd::EndOfFile, KeyPress::Delete => Cmd::DeleteChar, - KeyPress::End => Cmd::EndOfLine, KeyPress::Ctrl('G') => Cmd::Abort, - KeyPress::Ctrl('H') => Cmd::BackwardChar, KeyPress::Char('l') => Cmd::ForwardChar, KeyPress::Char(' ') => Cmd::ForwardChar, KeyPress::Right => Cmd::ForwardChar, @@ -294,7 +262,7 @@ impl EditState { KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, KeyPress::Esc => Cmd::Noop, _ => Cmd::Unknown, @@ -324,7 +292,7 @@ impl EditState { KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::UnixWordRubout, + KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, KeyPress::Esc => { // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). @@ -335,4 +303,56 @@ impl EditState { }; Ok((key, cmd)) } + + fn vi_delete_motion(&mut self, + rdr: &mut R, + config: &Config, + key: KeyPress) + -> Result { + let mvt = try!(rdr.next_key(config.keyseq_timeout())); + Ok(match mvt { + KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. + KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. + KeyPress::Char('b') => Cmd::BackwardKillWord(Word::ViWord), + KeyPress::Char('B') => Cmd::BackwardKillWord(Word::BigWord), + x if x == key => Cmd::KillWholeLine, + KeyPress::Char('e') => Cmd::KillWord(Word::ViWord), + KeyPress::Char('E') => Cmd::KillWord(Word::BigWord), + KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { + let cs = try!(self.vi_char_search(rdr, config, c)); + match cs { + Some(cs) => Cmd::ViKillTo(cs), + None => Cmd::Unknown, + } + } + KeyPress::Char('h') => Cmd::BackwardDeleteChar, // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, + KeyPress::Backspace => Cmd::BackwardDeleteChar, + KeyPress::Char('l') => Cmd::DeleteChar, + KeyPress::Char(' ') => Cmd::DeleteChar, + KeyPress::Char('w') => Cmd::KillWord(Word::ViWord), + KeyPress::Char('W') => Cmd::KillWord(Word::BigWord), + _ => Cmd::Unknown, + }) + } + + fn vi_char_search(&mut self, + rdr: &mut R, + config: &Config, + cmd: char) + -> Result> { + let ch = try!(rdr.next_key(config.keyseq_timeout())); + Ok(match ch { + KeyPress::Char(ch) => { + Some(match cmd { + 'f' => CharSearch::Forward(ch), + 't' => CharSearch::ForwardBefore(ch), + 'F' => CharSearch::Backward(ch), + 'T' => CharSearch::BackwardAfter(ch), + _ => unreachable!(), + }) + } + _ => None, + }) + } } From afdd1f8f47fc3526548859db2bb220ec56e330b2 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 11 Dec 2016 17:15:21 +0100 Subject: [PATCH 0234/1201] Vi mode support (#94) --- src/keymap.rs | 69 ++++++++++------ src/lib.rs | 213 +++++++++++++++++++++++++------------------------- 2 files changed, 152 insertions(+), 130 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index e2a44e5e2d..7f2e7dbb38 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -4,7 +4,7 @@ use super::KeyPress; use super::RawReader; use super::Result; -//#[derive(Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Cmd { Abort, // Miscellaneous Command AcceptLine, // Command For History @@ -25,16 +25,18 @@ pub enum Cmd { ForwardChar, // Command For Moving ForwardSearchHistory, // Command For History ForwardWord(Word), // Command For Moving + Interrupt, KillLine, // Command For Killing KillWholeLine, // Command For Killing (TODO Delete current line) KillWord(Word), // Command For Killing NextHistory, // Command For History - Noop, // TODO + Noop, PreviousHistory, // Command For History QuotedInsert, // Command For Text - Replace, // TODO DeleteChar + SelfInsert + Replace(char), // TODO DeleteChar + SelfInsert ReverseSearchHistory, // Command For History - SelfInsert, // Command For Text + SelfInsert(char), // Command For Text + Suspend, TransposeChars, // Command For Text TransposeWords, // Command For Text Unknown, @@ -48,6 +50,7 @@ pub enum Cmd { YankPop, // Command For Killing } +#[derive(Debug, Clone, PartialEq)] pub enum Word { // non-blanks characters BigWord, @@ -57,6 +60,7 @@ pub enum Word { ViWord, } +#[derive(Debug, Clone, PartialEq)] pub enum CharSearch { Forward(char), // until @@ -81,10 +85,11 @@ impl EditState { } } - pub fn next_cmd(&mut self, - rdr: &mut R, - config: &Config) - -> Result<(KeyPress, Cmd)> { + pub fn is_emacs_mode(&self) -> bool { + self.mode == EditMode::Emacs + } + + pub fn next_cmd(&mut self, rdr: &mut R, config: &Config) -> Result { match self.mode { EditMode::Emacs => self.emacs(rdr, config), EditMode::Vi if self.insert => self.vi_insert(rdr, config), @@ -92,17 +97,18 @@ impl EditState { } } - fn emacs(&mut self, rdr: &mut R, config: &Config) -> Result<(KeyPress, Cmd)> { + fn emacs(&mut self, rdr: &mut R, config: &Config) -> Result { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { - KeyPress::Char(_) => Cmd::SelfInsert, + KeyPress::Char(c) => Cmd::SelfInsert(c), KeyPress::Esc => Cmd::Abort, // TODO Validate KeyPress::Ctrl('A') => Cmd::BeginningOfLine, KeyPress::Home => Cmd::BeginningOfLine, KeyPress::Ctrl('B') => Cmd::BackwardChar, KeyPress::Left => Cmd::BackwardChar, - // KeyPress::Ctrl('D') if s.line.is_empty() => Cmd::EndOfFile, - KeyPress::Ctrl('D') => Cmd::DeleteChar, + KeyPress::Ctrl('C') => Cmd::Interrupt, + KeyPress::Ctrl('D') => Cmd::EndOfFile, + // KeyPress::Ctrl('D') => Cmd::DeleteChar, KeyPress::Delete => Cmd::DeleteChar, KeyPress::Ctrl('E') => Cmd::EndOfLine, KeyPress::End => Cmd::EndOfLine, @@ -128,6 +134,7 @@ impl EditState { KeyPress::Ctrl('V') => Cmd::QuotedInsert, KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Meta('\x08') => Cmd::BackwardKillWord(Word::Word), KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(Word::Word), // KeyPress::Meta('-') => { // digit-argument @@ -144,15 +151,13 @@ impl EditState { KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, KeyPress::Meta('Y') => Cmd::YankPop, + KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, }; - Ok((key, cmd)) + Ok(cmd) } - fn vi_command(&mut self, - rdr: &mut R, - config: &Config) - -> Result<(KeyPress, Cmd)> { + fn vi_command(&mut self, rdr: &mut R, config: &Config) -> Result { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { KeyPress::Char('$') => Cmd::EndOfLine, @@ -211,7 +216,7 @@ impl EditState { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); match ch { - KeyPress::Char(_) => return Ok((ch, Cmd::Replace)), + KeyPress::Char(c) => Cmd::Replace(c), KeyPress::Esc => Cmd::Noop, _ => Cmd::Unknown, } @@ -238,6 +243,7 @@ impl EditState { KeyPress::Ctrl('H') => Cmd::BackwardChar, KeyPress::Backspace => Cmd::BackwardChar, // TODO Validate KeyPress::Left => Cmd::BackwardChar, + KeyPress::Ctrl('C') => Cmd::Interrupt, KeyPress::Ctrl('D') => Cmd::EndOfFile, KeyPress::Delete => Cmd::DeleteChar, KeyPress::Ctrl('G') => Cmd::Abort, @@ -257,25 +263,34 @@ impl EditState { KeyPress::Up => Cmd::PreviousHistory, KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution - KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, - KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, + KeyPress::Ctrl('R') => { + self.insert = true; // TODO Validate + Cmd::ReverseSearchHistory + } + KeyPress::Ctrl('S') => { + self.insert = true; // TODO Validate + Cmd::ForwardSearchHistory + } KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Esc => Cmd::Noop, + KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, }; - Ok((key, cmd)) + Ok(cmd) } - fn vi_insert(&mut self, rdr: &mut R, config: &Config) -> Result<(KeyPress, Cmd)> { + fn vi_insert(&mut self, rdr: &mut R, config: &Config) -> Result { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { - KeyPress::Char(_) => Cmd::SelfInsert, + KeyPress::Char(c) => Cmd::SelfInsert(c), KeyPress::Home => Cmd::BeginningOfLine, KeyPress::Left => Cmd::BackwardChar, + KeyPress::Ctrl('C') => Cmd::Interrupt, KeyPress::Ctrl('D') => Cmd::EndOfFile, // vi-eof-maybe KeyPress::Delete => Cmd::DeleteChar, KeyPress::End => Cmd::EndOfLine, @@ -294,14 +309,16 @@ impl EditState { KeyPress::Ctrl('V') => Cmd::QuotedInsert, KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Esc => { // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). self.insert = false; Cmd::Noop } + KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, }; - Ok((key, cmd)) + Ok(cmd) } fn vi_delete_motion(&mut self, @@ -310,12 +327,14 @@ impl EditState { key: KeyPress) -> Result { let mvt = try!(rdr.next_key(config.keyseq_timeout())); + if mvt == key { + return Ok(Cmd::KillWholeLine); + } Ok(match mvt { KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. KeyPress::Char('b') => Cmd::BackwardKillWord(Word::ViWord), KeyPress::Char('B') => Cmd::BackwardKillWord(Word::BigWord), - x if x == key => Cmd::KillWholeLine, KeyPress::Char('e') => Cmd::KillWord(Word::ViWord), KeyPress::Char('E') => Cmd::KillWord(Word::BigWord), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { diff --git a/src/lib.rs b/src/lib.rs index db51f4725b..3996231d1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,7 @@ use completion::{Completer, longest_common_prefix}; use consts::KeyPress; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; +use keymap::{CharSearch, Cmd, EditState, Word}; use kill_ring::{Mode, KillRing}; pub use config::{CompletionType, Config, EditMode, HistoryDuplicates}; @@ -69,6 +70,7 @@ struct State<'out, 'prompt> { history_index: usize, // The history index we are currently editing snapshot: LineBuffer, // Current edited line before history browsing/completion term: Terminal, // terminal + edit_state: EditState, } #[derive(Copy, Clone, Debug, Default)] @@ -80,6 +82,7 @@ struct Position { impl<'out, 'prompt> State<'out, 'prompt> { fn new(out: &'out mut Write, term: Terminal, + config: &Config, prompt: &'prompt str, history_index: usize) -> State<'out, 'prompt> { @@ -97,6 +100,19 @@ impl<'out, 'prompt> State<'out, 'prompt> { history_index: history_index, snapshot: LineBuffer::with_capacity(capacity), term: term, + edit_state: EditState::new(config), + } + } + + fn next_cmd(&mut self, rdr: &mut R, config: &Config) -> Result { + loop { + let rc = self.edit_state.next_cmd(rdr, config); + if rc.is_err() && self.term.sigwinch() { + self.update_columns(); + try!(self.refresh_line()); + continue; + } + return rc; } } @@ -527,7 +543,7 @@ fn complete_line(rdr: &mut R, s: &mut State, completer: &Completer, config: &Config) - -> Result> { + -> Result> { // get a list of completions let (start, candidates) = try!(completer.complete(&s.line, s.line.pos())); // if no completions, we are done @@ -537,7 +553,7 @@ fn complete_line(rdr: &mut R, } else if CompletionType::Circular == config.completion_type() { // Save the current edited line before to overwrite it s.backup(); - let mut key; + let mut cmd; let mut i = 0; loop { // Show completion or original buffer @@ -551,15 +567,15 @@ fn complete_line(rdr: &mut R, s.snapshot(); } - key = try!(rdr.next_key(config.keyseq_timeout())); - match key { - KeyPress::Tab => { + cmd = try!(s.next_cmd(rdr, config)); + match cmd { + Cmd::Complete => { i = (i + 1) % (candidates.len() + 1); // Circular if i == candidates.len() { try!(beep()); } } - KeyPress::Esc => { + Cmd::Abort => { // Re-show original buffer s.snapshot(); if i < candidates.len() { @@ -575,7 +591,7 @@ fn complete_line(rdr: &mut R, } } } - Ok(Some(key)) + Ok(Some(cmd)) } else if CompletionType::List == config.completion_type() { // beep if ambiguous if candidates.len() > 1 { @@ -590,10 +606,10 @@ fn complete_line(rdr: &mut R, } } // we can't complete any further, wait for second tab - let mut key = try!(rdr.next_key(config.keyseq_timeout())); + let mut cmd = try!(s.next_cmd(rdr, config)); // if any character other than tab, pass it to the main loop - if key != KeyPress::Tab { - return Ok(Some(key)); + if cmd != Cmd::Complete { + return Ok(Some(cmd)); } // move cursor to EOL to avoid overwriting the command line let save_pos = s.line.pos(); @@ -605,14 +621,14 @@ fn complete_line(rdr: &mut R, let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len()); try!(write_and_flush(s.out, msg.as_bytes())); s.old_rows += 1; - while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && - key != KeyPress::Char('n') && key != KeyPress::Char('N') && - key != KeyPress::Backspace { - key = try!(rdr.next_key(config.keyseq_timeout())); + while cmd != Cmd::SelfInsert('y') && cmd != Cmd::SelfInsert('Y') && + cmd != Cmd::SelfInsert('n') && cmd != Cmd::SelfInsert('N') && + cmd != Cmd::BackwardDeleteChar { + cmd = try!(s.next_cmd(rdr, config)); } - show_completions = match key { - KeyPress::Char('y') | - KeyPress::Char('Y') => true, + show_completions = match cmd { + Cmd::SelfInsert('y') | + Cmd::SelfInsert('Y') => true, _ => false, }; } @@ -631,7 +647,7 @@ fn page_completions(rdr: &mut R, s: &mut State, config: &Config, candidates: &[String]) - -> Result> { + -> Result> { use std::cmp; use unicode_width::UnicodeWidthStr; @@ -699,7 +715,7 @@ fn reverse_incremental_search(rdr: &mut R, s: &mut State, history: &History, config: &Config) - -> Result> { + -> Result> { if history.is_empty() { return Ok(None); } @@ -711,7 +727,7 @@ fn reverse_incremental_search(rdr: &mut R, let mut direction = Direction::Reverse; let mut success = true; - let mut key; + let mut cmd; // Display the reverse-i-search prompt and process chars loop { let prompt = if success { @@ -721,17 +737,16 @@ fn reverse_incremental_search(rdr: &mut R, }; try!(s.refresh_prompt_and_line(&prompt)); - key = try!(rdr.next_key(config.keyseq_timeout())); - if let KeyPress::Char(c) = key { + cmd = try!(s.next_cmd(rdr, config)); + if let Cmd::SelfInsert(c) = cmd { search_buf.push(c); } else { - match key { - KeyPress::Ctrl('H') | - KeyPress::Backspace => { + match cmd { + Cmd::BackwardDeleteChar => { search_buf.pop(); continue; } - KeyPress::Ctrl('R') => { + Cmd::ReverseSearchHistory => { direction = Direction::Reverse; if history_idx > 0 { history_idx -= 1; @@ -740,7 +755,7 @@ fn reverse_incremental_search(rdr: &mut R, continue; } } - KeyPress::Ctrl('S') => { + Cmd::ForwardSearchHistory => { direction = Direction::Forward; if history_idx < history.len() - 1 { history_idx += 1; @@ -749,7 +764,7 @@ fn reverse_incremental_search(rdr: &mut R, continue; } } - KeyPress::Ctrl('G') => { + Cmd::Abort => { // Restore current edited line (before search) s.snapshot(); try!(s.refresh_line()); @@ -769,7 +784,7 @@ fn reverse_incremental_search(rdr: &mut R, _ => false, }; } - Ok(Some(key)) + Ok(Some(cmd)) } /// Handles reading and editting the readline buffer. @@ -787,6 +802,7 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); let mut s = State::new(&mut stdout, editor.term.clone(), + &editor.config, prompt, editor.history.len()); try!(s.refresh_line()); @@ -794,159 +810,135 @@ fn readline_edit(prompt: &str, let mut rdr = try!(s.term.create_reader()); loop { - let rk = rdr.next_key(editor.config.keyseq_timeout()); - if rk.is_err() && s.term.sigwinch() { - s.update_columns(); - try!(s.refresh_line()); - continue; - } - let mut key = try!(rk); - if let KeyPress::Char(c) = key { - editor.kill_ring.reset(); - try!(edit_insert(&mut s, c)); - continue; - } + let rc = s.next_cmd(&mut rdr, &editor.config); + let mut cmd = try!(rc); // autocomplete - if key == KeyPress::Tab && completer.is_some() { + if cmd == Cmd::Complete && completer.is_some() { let next = try!(complete_line(&mut rdr, &mut s, completer.unwrap(), &editor.config)); if next.is_some() { editor.kill_ring.reset(); - key = next.unwrap(); - if let KeyPress::Char(c) = key { - try!(edit_insert(&mut s, c)); - continue; - } + cmd = next.unwrap(); } else { continue; } - } else if key == KeyPress::Ctrl('R') { + } + + if let Cmd::SelfInsert(c) = cmd { + editor.kill_ring.reset(); + try!(edit_insert(&mut s, c)); + continue; + } + + if cmd == Cmd::ReverseSearchHistory { // Search history backward let next = try!(reverse_incremental_search(&mut rdr, &mut s, &editor.history, &editor.config)); if next.is_some() { - key = next.unwrap(); + cmd = next.unwrap(); } else { continue; } - } else if key == KeyPress::UnknownEscSeq { - continue; } - match key { - KeyPress::Ctrl('A') | - KeyPress::Home => { + match cmd { + Cmd::BeginningOfLine => { editor.kill_ring.reset(); // Move to the beginning of line. try!(edit_move_home(&mut s)) } - KeyPress::Ctrl('B') | - KeyPress::Left => { + Cmd::BackwardChar => { editor.kill_ring.reset(); // Move back a character. try!(edit_move_left(&mut s)) } - KeyPress::Ctrl('C') => { + Cmd::DeleteChar => { editor.kill_ring.reset(); - return Err(error::ReadlineError::Interrupted); + // Delete (forward) one character at point. + try!(edit_delete(&mut s)) } - KeyPress::Ctrl('D') => { + Cmd::EndOfFile => { editor.kill_ring.reset(); - if s.line.is_empty() { + if !s.edit_state.is_emacs_mode() || s.line.is_empty() { return Err(error::ReadlineError::Eof); } else { - // Delete (forward) one character at point. try!(edit_delete(&mut s)) } } - KeyPress::Ctrl('E') | - KeyPress::End => { + Cmd::EndOfLine => { editor.kill_ring.reset(); // Move to the end of line. try!(edit_move_end(&mut s)) } - KeyPress::Ctrl('F') | - KeyPress::Right => { + Cmd::ForwardChar => { editor.kill_ring.reset(); // Move forward a character. try!(edit_move_right(&mut s)) } - KeyPress::Ctrl('H') | - KeyPress::Backspace => { + Cmd::BackwardDeleteChar => { editor.kill_ring.reset(); // Delete one character backward. try!(edit_backspace(&mut s)) } - KeyPress::Ctrl('K') => { + Cmd::KillLine => { // Kill the text from point to the end of the line. if let Some(text) = try!(edit_kill_line(&mut s)) { editor.kill_ring.kill(&text, Mode::Append) } } - KeyPress::Ctrl('L') => { + Cmd::ClearScreen => { // Clear the screen leaving the current line at the top of the screen. try!(s.term.clear_screen(&mut s.out)); try!(s.refresh_line()) } - KeyPress::Ctrl('N') | - KeyPress::Down => { + Cmd::NextHistory => { editor.kill_ring.reset(); // Fetch the next command from the history list. try!(edit_history_next(&mut s, &editor.history, false)) } - KeyPress::Ctrl('P') | - KeyPress::Up => { + Cmd::PreviousHistory => { editor.kill_ring.reset(); // Fetch the previous command from the history list. try!(edit_history_next(&mut s, &editor.history, true)) } - KeyPress::Ctrl('T') => { + Cmd::TransposeChars => { editor.kill_ring.reset(); // Exchange the char before cursor with the character at cursor. try!(edit_transpose_chars(&mut s)) } - KeyPress::Ctrl('U') => { + Cmd::UnixLikeDiscard => { // Kill backward from point to the beginning of the line. if let Some(text) = try!(edit_discard_line(&mut s)) { editor.kill_ring.kill(&text, Mode::Prepend) } } #[cfg(unix)] - KeyPress::Ctrl('V') => { + Cmd::QuotedInsert => { // Quoted insert editor.kill_ring.reset(); let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c)) // FIXME } - KeyPress::Ctrl('W') => { + Cmd::KillWord(Word::BigWord) => { // Kill the word behind point, using white space as a word boundary if let Some(text) = try!(edit_delete_prev_word(&mut s, char::is_whitespace)) { editor.kill_ring.kill(&text, Mode::Prepend) } } - KeyPress::Ctrl('Y') => { + Cmd::Yank => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { try!(edit_yank(&mut s, text)) } } - #[cfg(unix)] - KeyPress::Ctrl('Z') => { - try!(original_mode.disable_raw_mode()); - try!(tty::suspend()); - try!(s.term.enable_raw_mode()); // TODO original_mode may have changed - try!(s.refresh_line()) - } // TODO CTRL-_ // undo - KeyPress::Enter | - KeyPress::Ctrl('J') => { + Cmd::AcceptLine => { // Accept the line regardless of where the cursor is. editor.kill_ring.reset(); try!(edit_move_end(&mut s)); break; } - KeyPress::Meta('\x08') | - KeyPress::Meta('\x7f') => { + Cmd::BackwardKillWord(Word::Word) => { // kill one word backward // Kill from the cursor to the start of the current word, or, if between words, to the start of the previous word. if let Some(text) = try!(edit_delete_prev_word(&mut s, @@ -954,62 +946,71 @@ fn readline_edit(prompt: &str, editor.kill_ring.kill(&text, Mode::Prepend) } } - KeyPress::Meta('<') => { + Cmd::BeginningOfHistory => { // move to first entry in history editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, true)) } - KeyPress::Meta('>') => { + Cmd::EndOfHistory => { // move to last entry in history editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, false)) } - KeyPress::Meta('B') => { + Cmd::BackwardWord(Word::Word) => { // move backwards one word editor.kill_ring.reset(); try!(edit_move_to_prev_word(&mut s)) } - KeyPress::Meta('C') => { + Cmd::CapitalizeWord => { // capitalize word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - KeyPress::Meta('D') => { + Cmd::KillWord(Word::Word) => { // kill one word forward if let Some(text) = try!(edit_delete_word(&mut s)) { editor.kill_ring.kill(&text, Mode::Append) } } - KeyPress::Meta('F') => { + Cmd::ForwardWord(Word::Word) => { // move forwards one word editor.kill_ring.reset(); try!(edit_move_to_next_word(&mut s)) } - KeyPress::Meta('L') => { + Cmd::DowncaseWord => { // lowercase word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::LOWERCASE)) } - KeyPress::Meta('T') => { + Cmd::TransposeWords => { // transpose words editor.kill_ring.reset(); try!(edit_transpose_words(&mut s)) } - KeyPress::Meta('U') => { + Cmd::UpcaseWord => { // uppercase word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::UPPERCASE)) } - KeyPress::Meta('Y') => { + Cmd::YankPop => { // yank-pop if let Some((yank_size, text)) = editor.kill_ring.yank_pop() { try!(edit_yank_pop(&mut s, yank_size, text)) } } - KeyPress::Delete => { + Cmd::Interrupt => { editor.kill_ring.reset(); - try!(edit_delete(&mut s)) + return Err(error::ReadlineError::Interrupted); + } + #[cfg(unix)] + Cmd::Suspend => { + try!(original_mode.disable_raw_mode()); + try!(tty::suspend()); + try!(s.term.enable_raw_mode()); // TODO original_mode may have changed + try!(s.refresh_line()); + continue; } + Cmd::Noop => {} _ => { editor.kill_ring.reset(); // Ignore the character typed. @@ -1178,8 +1179,8 @@ mod test { use completion::Completer; use config::Config; use consts::KeyPress; - use {Position, State}; - use super::{Editor, Result}; + use keymap::{Cmd, EditState}; + use super::{Editor, Position, Result, State}; use tty::{Terminal, Term}; fn init_state<'out>(out: &'out mut Write, @@ -1188,6 +1189,7 @@ mod test { cols: usize) -> State<'out, 'static> { let term = Terminal::new(); + let config = Config::default(); State { out: out, prompt: "", @@ -1199,6 +1201,7 @@ mod test { history_index: 0, snapshot: LineBuffer::with_capacity(100), term: term, + edit_state : EditState::new(&config), } } @@ -1260,8 +1263,8 @@ mod test { let keys = &[KeyPress::Enter]; let mut rdr = keys.iter(); let completer = SimpleCompleter; - let key = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); - assert_eq!(Some(KeyPress::Enter), key); + let cmd = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); + assert_eq!(Some(Cmd::AcceptLine), cmd); assert_eq!("rust", s.line.as_str()); assert_eq!(4, s.line.pos()); } From f6cb2a1d7af93a2b3d112195b43162e57bff7e65 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 11 Dec 2016 20:39:48 +0100 Subject: [PATCH 0235/1201] Handle more Vi cmds (#94) Replace char ('r') KillWholeLine ('S', 'dd', 'cc') --- examples/example.rs | 3 ++- src/keymap.rs | 4 ++-- src/lib.rs | 40 ++++++++++++++++++++++++++-------------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index e97650cedc..689c1d557b 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -2,7 +2,7 @@ extern crate rustyline; use rustyline::completion::FilenameCompleter; use rustyline::error::ReadlineError; -use rustyline::{Config, CompletionType, Editor}; +use rustyline::{Config, CompletionType, Editor, EditMode}; // On unix platforms you can use ANSI escape sequences #[cfg(unix)] @@ -17,6 +17,7 @@ fn main() { let config = Config::builder() .history_ignore_space(true) .completion_type(CompletionType::List) + .edit_mode(EditMode::Emacs) .build(); let c = FilenameCompleter::new(); let mut rl = Editor::with_config(config); diff --git a/src/keymap.rs b/src/keymap.rs index 7f2e7dbb38..726f807f1d 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -27,7 +27,7 @@ pub enum Cmd { ForwardWord(Word), // Command For Moving Interrupt, KillLine, // Command For Killing - KillWholeLine, // Command For Killing (TODO Delete current line) + KillWholeLine, // Command For Killing KillWord(Word), // Command For Killing NextHistory, // Command For History Noop, @@ -313,7 +313,7 @@ impl EditState { KeyPress::Esc => { // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). self.insert = false; - Cmd::Noop + Cmd::BackwardChar } KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, diff --git a/src/lib.rs b/src/lib.rs index 3996231d1a..90a6bc2d2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -665,22 +665,22 @@ fn page_completions(rdr: &mut R, for row in 0..num_rows { if row == pause_row { try!(write_and_flush(s.out, b"\n--More--")); - let mut key = KeyPress::Null; - while key != KeyPress::Char('y') && key != KeyPress::Char('Y') && - key != KeyPress::Char('n') && key != KeyPress::Char('N') && - key != KeyPress::Char('q') && - key != KeyPress::Char('Q') && - key != KeyPress::Char(' ') && - key != KeyPress::Backspace && key != KeyPress::Enter { - key = try!(rdr.next_key(config.keyseq_timeout())); + let mut cmd = Cmd::Noop; + while cmd != Cmd::SelfInsert('y') && cmd != Cmd::SelfInsert('Y') && + cmd != Cmd::SelfInsert('n') && cmd != Cmd::SelfInsert('N') && + cmd != Cmd::SelfInsert('q') && + cmd != Cmd::SelfInsert('Q') && + cmd != Cmd::SelfInsert(' ') && + cmd != Cmd::BackwardDeleteChar && cmd != Cmd::AcceptLine { + cmd = try!(s.next_cmd(rdr, config)); } - match key { - KeyPress::Char('y') | - KeyPress::Char('Y') | - KeyPress::Char(' ') => { + match cmd { + Cmd::SelfInsert('y') | + Cmd::SelfInsert('Y') | + Cmd::SelfInsert(' ') => { pause_row += s.term.get_rows() - 1; } - KeyPress::Enter => { + Cmd::AcceptLine => { pause_row += 1; } _ => break, @@ -857,6 +857,12 @@ fn readline_edit(prompt: &str, // Delete (forward) one character at point. try!(edit_delete(&mut s)) } + Cmd::Replace(c) => { + editor.kill_ring.reset(); + try!(edit_delete(&mut s)); + try!(edit_insert(&mut s, c)); + try!(edit_move_left(&mut s)) + } Cmd::EndOfFile => { editor.kill_ring.reset(); if !s.edit_state.is_emacs_mode() || s.line.is_empty() { @@ -886,6 +892,12 @@ fn readline_edit(prompt: &str, editor.kill_ring.kill(&text, Mode::Append) } } + Cmd::KillWholeLine => { + try!(edit_move_home(&mut s)); + if let Some(text) = try!(edit_kill_line(&mut s)) { + editor.kill_ring.kill(&text, Mode::Append) + } + } Cmd::ClearScreen => { // Clear the screen leaving the current line at the top of the screen. try!(s.term.clear_screen(&mut s.out)); @@ -1201,7 +1213,7 @@ mod test { history_index: 0, snapshot: LineBuffer::with_capacity(100), term: term, - edit_state : EditState::new(&config), + edit_state: EditState::new(&config), } } From 04ca98867209bbe59b54260bdf858159c3156851 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 12 Dec 2016 21:09:57 +0100 Subject: [PATCH 0236/1201] Handle Vi specific word movements (#94) --- src/keymap.rs | 14 ++++---- src/lib.rs | 43 +++++++++------------- src/line_buffer.rs | 89 +++++++++++++++++++++++++++------------------ src/tty/unix.rs | 90 +++++++++++++++++++++++----------------------- src/tty/windows.rs | 30 ++++++++-------- 5 files changed, 138 insertions(+), 128 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 726f807f1d..0f8421cdfa 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -1,7 +1,7 @@ -use super::Config; -use super::EditMode; -use super::KeyPress; -use super::RawReader; +use config::Config; +use config::EditMode; +use consts::KeyPress; +use tty::RawReader; use super::Result; #[derive(Debug, Clone, PartialEq)] @@ -41,7 +41,7 @@ pub enum Cmd { TransposeWords, // Command For Text Unknown, UnixLikeDiscard, // Command For Killing - // UnixWordRubout, // = KillWord(Word::BigWord) Command For Killing + // UnixWordRubout, // = BackwardKillWord(Word::BigWord) Command For Killing UpcaseWord, // Command For Text ViCharSearch(CharSearch), // TODO ViEndWord(Word), // TODO @@ -50,7 +50,7 @@ pub enum Cmd { YankPop, // Command For Killing } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub enum Word { // non-blanks characters BigWord, @@ -132,7 +132,7 @@ impl EditState { KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), + KeyPress::Ctrl('W') => Cmd::BackwardKillWord(Word::BigWord), KeyPress::Ctrl('Y') => Cmd::Yank, KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Meta('\x08') => Cmd::BackwardKillWord(Word::Word), diff --git a/src/lib.rs b/src/lib.rs index 90a6bc2d2c..e66304b442 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,6 @@ use tty::{RawMode, RawReader, Terminal, Term}; use encode_unicode::CharExt; use completion::{Completer, longest_common_prefix}; -use consts::KeyPress; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; use keymap::{CharSearch, Cmd, EditState, Word}; @@ -425,8 +424,8 @@ fn edit_transpose_chars(s: &mut State) -> Result<()> { } } -fn edit_move_to_prev_word(s: &mut State) -> Result<()> { - if s.line.move_to_prev_word() { +fn edit_move_to_prev_word(s: &mut State, word_def: Word) -> Result<()> { + if s.line.move_to_prev_word(word_def) { s.refresh_line() } else { Ok(()) @@ -435,10 +434,8 @@ fn edit_move_to_prev_word(s: &mut State) -> Result<()> { /// Delete the previous word, maintaining the cursor at the start of the /// current word. -fn edit_delete_prev_word(s: &mut State, test: F) -> Result> - where F: Fn(char) -> bool -{ - if let Some(text) = s.line.delete_prev_word(test) { +fn edit_delete_prev_word(s: &mut State, word_def: Word) -> Result> { + if let Some(text) = s.line.delete_prev_word(word_def) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -446,8 +443,8 @@ fn edit_delete_prev_word(s: &mut State, test: F) -> Result> } } -fn edit_move_to_next_word(s: &mut State) -> Result<()> { - if s.line.move_to_next_word() { +fn edit_move_to_next_word(s: &mut State, word_def: Word) -> Result<()> { + if s.line.move_to_next_word(word_def) { s.refresh_line() } else { Ok(()) @@ -455,8 +452,8 @@ fn edit_move_to_next_word(s: &mut State) -> Result<()> { } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. -fn edit_delete_word(s: &mut State) -> Result> { - if let Some(text) = s.line.delete_word() { +fn edit_delete_word(s: &mut State, word_def: Word) -> Result> { + if let Some(text) = s.line.delete_word(word_def) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -931,12 +928,6 @@ fn readline_edit(prompt: &str, let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c)) // FIXME } - Cmd::KillWord(Word::BigWord) => { - // Kill the word behind point, using white space as a word boundary - if let Some(text) = try!(edit_delete_prev_word(&mut s, char::is_whitespace)) { - editor.kill_ring.kill(&text, Mode::Prepend) - } - } Cmd::Yank => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { @@ -950,11 +941,9 @@ fn readline_edit(prompt: &str, try!(edit_move_end(&mut s)); break; } - Cmd::BackwardKillWord(Word::Word) => { + Cmd::BackwardKillWord(word_def) => { // kill one word backward - // Kill from the cursor to the start of the current word, or, if between words, to the start of the previous word. - if let Some(text) = try!(edit_delete_prev_word(&mut s, - |ch| !ch.is_alphanumeric())) { + if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def)) { editor.kill_ring.kill(&text, Mode::Prepend) } } @@ -968,26 +957,26 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, false)) } - Cmd::BackwardWord(Word::Word) => { + Cmd::BackwardWord(word_def) => { // move backwards one word editor.kill_ring.reset(); - try!(edit_move_to_prev_word(&mut s)) + try!(edit_move_to_prev_word(&mut s, word_def)) } Cmd::CapitalizeWord => { // capitalize word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - Cmd::KillWord(Word::Word) => { + Cmd::KillWord(word_def) => { // kill one word forward - if let Some(text) = try!(edit_delete_word(&mut s)) { + if let Some(text) = try!(edit_delete_word(&mut s, word_def)) { editor.kill_ring.kill(&text, Mode::Append) } } - Cmd::ForwardWord(Word::Word) => { + Cmd::ForwardWord(word_def) => { // move forwards one word editor.kill_ring.reset(); - try!(edit_move_to_next_word(&mut s)) + try!(edit_move_to_next_word(&mut s, word_def)) } Cmd::DowncaseWord => { // lowercase word after point diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 5bae7cc516..854fa49d55 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,5 +1,6 @@ //! Line buffer with current cursor position -use std::ops::{Add, Deref}; +use std::ops::Deref; +use keymap::Word; /// Maximum buffer size for the line read pub static MAX_LINE: usize = 4096; @@ -261,35 +262,34 @@ impl LineBuffer { true } - fn prev_word_pos(&self, pos: usize, test: F) -> Option - where F: Fn(char) -> bool - { + fn prev_word_pos(&self, pos: usize, word_def: Word) -> Option { if pos == 0 { return None; } + let test = is_break_char(word_def); let mut pos = pos; // eat any spaces on the left pos -= self.buf[..pos] .chars() .rev() - .take_while(|ch| test(*ch)) + .take_while(|ch| test(ch)) .map(char::len_utf8) - .fold(0, Add::add); + .sum(); if pos > 0 { // eat any non-spaces on the left pos -= self.buf[..pos] .chars() .rev() - .take_while(|ch| !test(*ch)) + .take_while(|ch| !test(ch)) .map(char::len_utf8) - .fold(0, Add::add); + .sum(); } Some(pos) } /// Moves the cursor to the beginning of previous word. - pub fn move_to_prev_word(&mut self) -> bool { - if let Some(pos) = self.prev_word_pos(self.pos, |ch| !ch.is_alphanumeric()) { + pub fn move_to_prev_word(&mut self, word_def: Word) -> bool { + if let Some(pos) = self.prev_word_pos(self.pos, word_def) { self.pos = pos; true } else { @@ -299,10 +299,8 @@ impl LineBuffer { /// Delete the previous word, maintaining the cursor at the start of the /// current word. - pub fn delete_prev_word(&mut self, test: F) -> Option - where F: Fn(char) -> bool - { - if let Some(pos) = self.prev_word_pos(self.pos, test) { + pub fn delete_prev_word(&mut self, word_def: Word) -> Option { + if let Some(pos) = self.prev_word_pos(self.pos, word_def) { let word = self.buf.drain(pos..self.pos).collect(); self.pos = pos; Some(word) @@ -312,23 +310,24 @@ impl LineBuffer { } /// Returns the position (start, end) of the next word. - pub fn next_word_pos(&self, pos: usize) -> Option<(usize, usize)> { + pub fn next_word_pos(&self, pos: usize, word_def: Word) -> Option<(usize, usize)> { if pos < self.buf.len() { + let test = is_break_char(word_def); let mut pos = pos; // eat any spaces pos += self.buf[pos..] .chars() - .take_while(|ch| !ch.is_alphanumeric()) + .take_while(test) .map(char::len_utf8) - .fold(0, Add::add); + .sum(); let start = pos; if pos < self.buf.len() { // eat any non-spaces pos += self.buf[pos..] .chars() - .take_while(|ch| ch.is_alphanumeric()) + .take_while(|ch| !test(ch)) .map(char::len_utf8) - .fold(0, Add::add); + .sum(); } Some((start, pos)) } else { @@ -337,8 +336,8 @@ impl LineBuffer { } /// Moves the cursor to the end of next word. - pub fn move_to_next_word(&mut self) -> bool { - if let Some((_, end)) = self.next_word_pos(self.pos) { + pub fn move_to_next_word(&mut self, word_def: Word) -> bool { + if let Some((_, end)) = self.next_word_pos(self.pos, word_def) { self.pos = end; true } else { @@ -346,9 +345,12 @@ impl LineBuffer { } } + // TODO move_to_end_of_word (Vi mode) + // TODO move_to (Vi mode: f|F|t|T) + /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. - pub fn delete_word(&mut self) -> Option { - if let Some((_, end)) = self.next_word_pos(self.pos) { + pub fn delete_word(&mut self, word_def: Word) -> Option { + if let Some((_, end)) = self.next_word_pos(self.pos, word_def) { let word = self.buf.drain(self.pos..end).collect(); Some(word) } else { @@ -358,7 +360,7 @@ impl LineBuffer { /// Alter the next word. pub fn edit_word(&mut self, a: WordAction) -> bool { - if let Some((start, end)) = self.next_word_pos(self.pos) { + if let Some((start, end)) = self.next_word_pos(self.pos, Word::Word) { if start == end { return false; } @@ -388,16 +390,17 @@ impl LineBuffer { // prevword___oneword__ // ^ ^ ^ // prev_start start self.pos/end - if let Some(start) = self.prev_word_pos(self.pos, |ch| !ch.is_alphanumeric()) { - if let Some(prev_start) = self.prev_word_pos(start, |ch| !ch.is_alphanumeric()) { - let (_, prev_end) = self.next_word_pos(prev_start).unwrap(); + let word_def = Word::Word; + if let Some(start) = self.prev_word_pos(self.pos, word_def) { + if let Some(prev_start) = self.prev_word_pos(start, word_def) { + let (_, prev_end) = self.next_word_pos(prev_start, word_def).unwrap(); if prev_end >= start { return false; } - let (_, mut end) = self.next_word_pos(start).unwrap(); + let (_, mut end) = self.next_word_pos(start, word_def).unwrap(); if end < self.pos { if self.pos < self.buf.len() { - let (s, _) = self.next_word_pos(self.pos).unwrap(); + let (s, _) = self.next_word_pos(self.pos, word_def).unwrap(); end = s; } else { end = self.pos; @@ -467,9 +470,27 @@ fn insert_str(buf: &mut String, idx: usize, s: &str) { } } +fn is_break_char(word_def: Word) -> fn(&char) -> bool { + match word_def { + Word::Word => is_not_alphanumeric, + Word::ViWord => is_not_alphanumeric_and_underscore, + Word::BigWord => is_whitespace, + } +} + +fn is_not_alphanumeric(ch: &char) -> bool { + !ch.is_alphanumeric() +} +fn is_not_alphanumeric_and_underscore(ch: &char) -> bool { + !ch.is_alphanumeric() && *ch != '_' +} +fn is_whitespace(ch: &char) -> bool { + ch.is_whitespace() +} + #[cfg(test)] mod test { - use super::{LineBuffer, MAX_LINE, WordAction}; + use super::{LineBuffer, MAX_LINE, Word, WordAction}; #[test] fn insert() { @@ -570,7 +591,7 @@ mod test { #[test] fn move_to_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let ok = s.move_to_prev_word(); + let ok = s.move_to_prev_word(Word::Word); assert_eq!("a ß c", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); @@ -579,7 +600,7 @@ mod test { #[test] fn delete_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let text = s.delete_prev_word(char::is_whitespace); + let text = s.delete_prev_word(Word::BigWord); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); @@ -588,7 +609,7 @@ mod test { #[test] fn move_to_next_word() { let mut s = LineBuffer::init("a ß c", 1); - let ok = s.move_to_next_word(); + let ok = s.move_to_next_word(Word::Word); assert_eq!("a ß c", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); @@ -597,7 +618,7 @@ mod test { #[test] fn delete_word() { let mut s = LineBuffer::init("a ß c", 1); - let text = s.delete_word(); + let text = s.delete_word(Word::Word); assert_eq!("a c", s.buf); assert_eq!(1, s.pos); assert_eq!(Some(" ß".to_string()), text); diff --git a/src/tty/unix.rs b/src/tty/unix.rs index a7bc01392c..e9e8fe42c9 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -111,66 +111,66 @@ impl PosixRawReader { // Extended escape, read additional byte. let seq3 = try!(self.next_char()); if seq3 == '~' { - match seq2 { - '1' => Ok(KeyPress::Home), // xterm - '3' => Ok(KeyPress::Delete), - '4' => Ok(KeyPress::End), // xterm - '5' => Ok(KeyPress::PageUp), - '6' => Ok(KeyPress::PageDown), - '7' => Ok(KeyPress::Home), - '8' => Ok(KeyPress::End), - _ => Ok(KeyPress::UnknownEscSeq), - } + Ok(match seq2 { + '1' => KeyPress::Home, // xterm + '3' => KeyPress::Delete, + '4' => KeyPress::End, // xterm + '5' => KeyPress::PageUp, + '6' => KeyPress::PageDown, + '7' => KeyPress::Home, + '8' => KeyPress::End, + _ => KeyPress::UnknownEscSeq, + }) } else { Ok(KeyPress::UnknownEscSeq) } } else { - match seq2 { - 'A' => Ok(KeyPress::Up), // ANSI - 'B' => Ok(KeyPress::Down), - 'C' => Ok(KeyPress::Right), - 'D' => Ok(KeyPress::Left), - 'F' => Ok(KeyPress::End), - 'H' => Ok(KeyPress::Home), - _ => Ok(KeyPress::UnknownEscSeq), - } + Ok(match seq2 { + 'A' => KeyPress::Up, // ANSI + 'B' => KeyPress::Down, + 'C' => KeyPress::Right, + 'D' => KeyPress::Left, + 'F' => KeyPress::End, + 'H' => KeyPress::Home, + _ => KeyPress::UnknownEscSeq, + }) } } else if seq1 == 'O' { // ESC O sequences. let seq2 = try!(self.next_char()); - match seq2 { - 'A' => Ok(KeyPress::Up), - 'B' => Ok(KeyPress::Down), - 'C' => Ok(KeyPress::Right), - 'D' => Ok(KeyPress::Left), - 'F' => Ok(KeyPress::End), - 'H' => Ok(KeyPress::Home), - _ => Ok(KeyPress::UnknownEscSeq), - } + Ok(match seq2 { + 'A' => KeyPress::Up, + 'B' => KeyPress::Down, + 'C' => KeyPress::Right, + 'D' => KeyPress::Left, + 'F' => KeyPress::End, + 'H' => KeyPress::Home, + _ => KeyPress::UnknownEscSeq, + }) } else { // TODO ESC-N (n): search history forward not interactively // TODO ESC-P (p): search history backward not interactively // TODO ESC-R (r): Undo all changes made to this line. - match seq1 { - '\x08' => Ok(KeyPress::Meta('\x08')), // Backspace - '-' => return Ok(KeyPress::Meta('-')), - '0'...'9' => return Ok(KeyPress::Meta(seq1)), - '<' => Ok(KeyPress::Meta('<')), - '>' => Ok(KeyPress::Meta('>')), - 'b' | 'B' => Ok(KeyPress::Meta('B')), - 'c' | 'C' => Ok(KeyPress::Meta('C')), - 'd' | 'D' => Ok(KeyPress::Meta('D')), - 'f' | 'F' => Ok(KeyPress::Meta('F')), - 'l' | 'L' => Ok(KeyPress::Meta('L')), - 't' | 'T' => Ok(KeyPress::Meta('T')), - 'u' | 'U' => Ok(KeyPress::Meta('U')), - 'y' | 'Y' => Ok(KeyPress::Meta('Y')), - '\x7f' => Ok(KeyPress::Meta('\x7f')), // Delete + Ok(match seq1 { + '\x08' => KeyPress::Meta('\x08'), // Backspace + '-' => KeyPress::Meta('-'), + '0'...'9' => KeyPress::Meta(seq1), + '<' => KeyPress::Meta('<'), + '>' => KeyPress::Meta('>'), + 'b' | 'B' => KeyPress::Meta('B'), + 'c' | 'C' => KeyPress::Meta('C'), + 'd' | 'D' => KeyPress::Meta('D'), + 'f' | 'F' => KeyPress::Meta('F'), + 'l' | 'L' => KeyPress::Meta('L'), + 't' | 'T' => KeyPress::Meta('T'), + 'u' | 'U' => KeyPress::Meta('U'), + 'y' | 'Y' => KeyPress::Meta('Y'), + '\x7f' => KeyPress::Meta('\x7f'), // Delete _ => { // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::Esc, seq1).unwrap(); - Ok(KeyPress::UnknownEscSeq) + KeyPress::UnknownEscSeq } - } + }) } } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 1b3fdeea6d..cbf1f8a229 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -148,21 +148,21 @@ impl RawReader for ConsoleRawReader { } let c = try!(orc.unwrap()); if meta { - match c { - '-' => return Ok(KeyPress::Meta('-')), - '0'...'9' => return Ok(KeyPress::Meta(c)), - '<' => Ok(KeyPress::Meta('<')), - '>' => Ok(KeyPress::Meta('>')), - 'b' | 'B' => return Ok(KeyPress::Meta('B')), - 'c' | 'C' => return Ok(KeyPress::Meta('C')), - 'd' | 'D' => return Ok(KeyPress::Meta('D')), - 'f' | 'F' => return Ok(KeyPress::Meta('F')), - 'l' | 'L' => return Ok(KeyPress::Meta('L')), - 't' | 'T' => return Ok(KeyPress::Meta('T')), - 'u' | 'U' => return Ok(KeyPress::Meta('U')), - 'y' | 'Y' => return Ok(KeyPress::Meta('Y')), - _ => return Ok(KeyPress::UnknownEscSeq), - } + return Ok(match c { + '-' => KeyPress::Meta('-'), + '0'...'9' => KeyPress::Meta(c), + '<' => KeyPress::Meta('<'), + '>' => KeyPress::Meta('>'), + 'b' | 'B' => KeyPress::Meta('B'), + 'c' | 'C' => KeyPress::Meta('C'), + 'd' | 'D' => KeyPress::Meta('D'), + 'f' | 'F' => KeyPress::Meta('F'), + 'l' | 'L' => KeyPress::Meta('L'), + 't' | 'T' => KeyPress::Meta('T'), + 'u' | 'U' => KeyPress::Meta('U'), + 'y' | 'Y' => KeyPress::Meta('Y'), + _ => KeyPress::UnknownEscSeq, + }); } else { return Ok(consts::char_to_key_press(c)); } From 4aae52554b1fa86619ee9026bb45d3ea4f924256 Mon Sep 17 00:00:00 2001 From: gwenn Date: Fri, 16 Dec 2016 19:07:15 +0100 Subject: [PATCH 0237/1201] Factorize common commands between emacs and vi --- src/keymap.rs | 93 +++++++++++++++------------------------------- src/tty/windows.rs | 6 +-- 2 files changed, 32 insertions(+), 67 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 0f8421cdfa..9325528633 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -103,38 +103,17 @@ impl EditState { KeyPress::Char(c) => Cmd::SelfInsert(c), KeyPress::Esc => Cmd::Abort, // TODO Validate KeyPress::Ctrl('A') => Cmd::BeginningOfLine, - KeyPress::Home => Cmd::BeginningOfLine, KeyPress::Ctrl('B') => Cmd::BackwardChar, - KeyPress::Left => Cmd::BackwardChar, - KeyPress::Ctrl('C') => Cmd::Interrupt, - KeyPress::Ctrl('D') => Cmd::EndOfFile, - // KeyPress::Ctrl('D') => Cmd::DeleteChar, - KeyPress::Delete => Cmd::DeleteChar, KeyPress::Ctrl('E') => Cmd::EndOfLine, - KeyPress::End => Cmd::EndOfLine, KeyPress::Ctrl('F') => Cmd::ForwardChar, - KeyPress::Right => Cmd::ForwardChar, KeyPress::Ctrl('G') => Cmd::Abort, KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, KeyPress::Backspace => Cmd::BackwardDeleteChar, KeyPress::Tab => Cmd::Complete, - KeyPress::Ctrl('J') => Cmd::AcceptLine, - KeyPress::Enter => Cmd::AcceptLine, KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Ctrl('N') => Cmd::NextHistory, - KeyPress::Down => Cmd::NextHistory, KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Up => Cmd::PreviousHistory, - KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution - KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, - KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution - KeyPress::Ctrl('T') => Cmd::TransposeChars, - KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, - KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::BackwardKillWord(Word::BigWord), - KeyPress::Ctrl('Y') => Cmd::Yank, - KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Meta('\x08') => Cmd::BackwardKillWord(Word::Word), KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(Word::Word), // KeyPress::Meta('-') => { // digit-argument @@ -151,8 +130,7 @@ impl EditState { KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, KeyPress::Meta('Y') => Cmd::YankPop, - KeyPress::UnknownEscSeq => Cmd::Noop, - _ => Cmd::Unknown, + _ => self.common(key), }; Ok(cmd) } @@ -164,7 +142,6 @@ impl EditState { KeyPress::End => Cmd::EndOfLine, // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket KeyPress::Char('0') => Cmd::BeginningOfLine, // vi-zero: Vi move to the beginning of line. - KeyPress::Home => Cmd::BeginningOfLine, // KeyPress::Char('1'...'9') => Cmd::???, // vi-arg-digit KeyPress::Char('^') => Cmd::BeginningOfLine, // vi-first-print TODO Move to the first non-blank character of line. KeyPress::Char('a') => { @@ -242,27 +219,17 @@ impl EditState { KeyPress::Char('h') => Cmd::BackwardChar, KeyPress::Ctrl('H') => Cmd::BackwardChar, KeyPress::Backspace => Cmd::BackwardChar, // TODO Validate - KeyPress::Left => Cmd::BackwardChar, - KeyPress::Ctrl('C') => Cmd::Interrupt, - KeyPress::Ctrl('D') => Cmd::EndOfFile, - KeyPress::Delete => Cmd::DeleteChar, KeyPress::Ctrl('G') => Cmd::Abort, KeyPress::Char('l') => Cmd::ForwardChar, KeyPress::Char(' ') => Cmd::ForwardChar, - KeyPress::Right => Cmd::ForwardChar, KeyPress::Ctrl('L') => Cmd::ClearScreen, - KeyPress::Ctrl('J') => Cmd::AcceptLine, - KeyPress::Enter => Cmd::AcceptLine, KeyPress::Char('+') => Cmd::NextHistory, KeyPress::Char('j') => Cmd::NextHistory, KeyPress::Ctrl('N') => Cmd::NextHistory, - KeyPress::Down => Cmd::NextHistory, KeyPress::Char('-') => Cmd::PreviousHistory, KeyPress::Char('k') => Cmd::PreviousHistory, KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Up => Cmd::PreviousHistory, KeyPress::Ctrl('K') => Cmd::KillLine, - KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('R') => { self.insert = true; // TODO Validate Cmd::ReverseSearchHistory @@ -271,15 +238,8 @@ impl EditState { self.insert = true; // TODO Validate Cmd::ForwardSearchHistory } - KeyPress::Ctrl('T') => Cmd::TransposeChars, - KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, - KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), - KeyPress::Ctrl('Y') => Cmd::Yank, - KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Esc => Cmd::Noop, - KeyPress::UnknownEscSeq => Cmd::Noop, - _ => Cmd::Unknown, + _ => self.common(key), }; Ok(cmd) } @@ -288,35 +248,15 @@ impl EditState { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), - KeyPress::Home => Cmd::BeginningOfLine, - KeyPress::Left => Cmd::BackwardChar, - KeyPress::Ctrl('C') => Cmd::Interrupt, - KeyPress::Ctrl('D') => Cmd::EndOfFile, // vi-eof-maybe - KeyPress::Delete => Cmd::DeleteChar, - KeyPress::End => Cmd::EndOfLine, - KeyPress::Right => Cmd::ForwardChar, KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, KeyPress::Backspace => Cmd::BackwardDeleteChar, KeyPress::Tab => Cmd::Complete, - KeyPress::Ctrl('J') => Cmd::AcceptLine, - KeyPress::Enter => Cmd::AcceptLine, - KeyPress::Down => Cmd::NextHistory, - KeyPress::Up => Cmd::PreviousHistory, - KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, - KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, - KeyPress::Ctrl('T') => Cmd::TransposeChars, - KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, - KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::KillWord(Word::BigWord), - KeyPress::Ctrl('Y') => Cmd::Yank, - KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::Esc => { // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). self.insert = false; Cmd::BackwardChar } - KeyPress::UnknownEscSeq => Cmd::Noop, - _ => Cmd::Unknown, + _ => self.common(key), }; Ok(cmd) } @@ -374,4 +314,31 @@ impl EditState { _ => None, }) } + + fn common(&mut self, key: KeyPress) -> Cmd { + match key { + KeyPress::Home => Cmd::BeginningOfLine, + KeyPress::Left => Cmd::BackwardChar, + KeyPress::Ctrl('C') => Cmd::Interrupt, + KeyPress::Ctrl('D') => Cmd::EndOfFile, + KeyPress::Delete => Cmd::DeleteChar, + KeyPress::End => Cmd::EndOfLine, + KeyPress::Right => Cmd::ForwardChar, + KeyPress::Ctrl('J') => Cmd::AcceptLine, + KeyPress::Enter => Cmd::AcceptLine, + KeyPress::Down => Cmd::NextHistory, + KeyPress::Up => Cmd::PreviousHistory, + KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution + KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, + KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution + KeyPress::Ctrl('T') => Cmd::TransposeChars, + KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, + KeyPress::Ctrl('V') => Cmd::QuotedInsert, + KeyPress::Ctrl('W') => Cmd::BackwardKillWord(Word::BigWord), + KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Ctrl('Z') => Cmd::Suspend, + KeyPress::UnknownEscSeq => Cmd::Noop, + _ => Cmd::Unknown, + } + } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index cbf1f8a229..c279e0d3f4 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -93,7 +93,6 @@ impl RawReader for ConsoleRawReader { let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() }; let mut count = 0; - let mut esc_seen = false; loop { // TODO GetNumberOfConsoleInputEvents check!(kernel32::ReadConsoleInputW(self.handle, @@ -120,7 +119,7 @@ impl RawReader for ConsoleRawReader { (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); // let ctrl = key_event.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) == // (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); - let meta = alt || esc_seen; + let meta = alt; let utf16 = key_event.UnicodeChar; if utf16 == 0 { @@ -137,8 +136,7 @@ impl RawReader for ConsoleRawReader { _ => continue, }; } else if utf16 == 27 { - esc_seen = true; - continue; + return Ok(KeyPress::Esc); } else { // TODO How to support surrogate pair ? self.buf = Some(utf16); From 51f5519d7be63b683cad5b8d5cf78b3bc934a10f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 17 Dec 2016 09:59:32 +0100 Subject: [PATCH 0238/1201] Numeric arguments But commands are not repeated --- README.md | 9 +++ src/keymap.rs | 199 +++++++++++++++++++++++++++++++++----------------- src/lib.rs | 26 +++---- 3 files changed, 153 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 6c8f0e300a..5787912e6b 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,15 @@ $ wineconsole --backend=curses target/x86_64-pc-windows-gnu/debug/examples/examp ... ``` +## Terminal checks + +```sh +$ # current settings of all terminal attributes: +$ stty -a +$ # key bindings: +$ bind -p +``` + ## Similar projects - [copperline](https://github.com/srijs/rust-copperline) (Rust) diff --git a/src/keymap.rs b/src/keymap.rs index 9325528633..1f4f013b41 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -8,32 +8,32 @@ use super::Result; pub enum Cmd { Abort, // Miscellaneous Command AcceptLine, // Command For History - BackwardChar, // Command For Moving - BackwardDeleteChar, // Command For Text - BackwardKillWord(Word), // Command For Killing - BackwardWord(Word), // Command For Moving + BackwardChar(i32), // Command For Moving + BackwardDeleteChar(i32), // Command For Text + BackwardKillWord(i32, Word), // Command For Killing + BackwardWord(i32, Word), // Command For Moving BeginningOfHistory, // Command For History BeginningOfLine, // Command For Moving CapitalizeWord, // Command For Text ClearScreen, // Command For Moving Complete, // Command For Completion - DeleteChar, // Command For Text + DeleteChar(i32), // Command For Text DowncaseWord, // Command For Text EndOfFile, // Command For Text EndOfHistory, // Command For History EndOfLine, // Command For Moving - ForwardChar, // Command For Moving + ForwardChar(i32), // Command For Moving ForwardSearchHistory, // Command For History - ForwardWord(Word), // Command For Moving + ForwardWord(i32, Word), // Command For Moving Interrupt, KillLine, // Command For Killing KillWholeLine, // Command For Killing - KillWord(Word), // Command For Killing + KillWord(i32, Word), // Command For Killing NextHistory, // Command For History Noop, PreviousHistory, // Command For History QuotedInsert, // Command For Text - Replace(char), // TODO DeleteChar + SelfInsert + Replace(i32, char), // TODO DeleteChar + SelfInsert ReverseSearchHistory, // Command For History SelfInsert(char), // Command For Text Suspend, @@ -44,9 +44,9 @@ pub enum Cmd { // UnixWordRubout, // = BackwardKillWord(Word::BigWord) Command For Killing UpcaseWord, // Command For Text ViCharSearch(CharSearch), // TODO - ViEndWord(Word), // TODO - ViKillTo(CharSearch), // TODO - Yank, // Command For Killing + ViEndWord(i32, Word), // TODO + ViKillTo(i32, CharSearch), // TODO + Yank(i32), // Command For Killing YankPop, // Command For Killing } @@ -70,11 +70,12 @@ pub enum CharSearch { BackwardAfter(char), } -// TODO numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 pub struct EditState { mode: EditMode, // Vi Command/Alternate, Insert/Input mode insert: bool, // vi only ? + // numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 + num_args: i32, } impl EditState { @@ -82,6 +83,7 @@ impl EditState { EditState { mode: config.edit_mode(), insert: true, + num_args: 0, } } @@ -97,35 +99,64 @@ impl EditState { } } + fn digit_argument(&mut self, + rdr: &mut R, + config: &Config, + digit: char) + -> Result { + match digit { + '0'...'9' => { + self.num_args = digit.to_digit(10).unwrap() as i32; + } + '-' => { + self.num_args = -1; + } + _ => unreachable!(), + } + loop { + let key = try!(rdr.next_key(config.keyseq_timeout())); + match key { + KeyPress::Char(digit @ '0'...'9') => { + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + } + KeyPress::Meta(digit @ '0'...'9') => { + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + } + _ => return Ok(key), + }; + } + } + fn emacs(&mut self, rdr: &mut R, config: &Config) -> Result { - let key = try!(rdr.next_key(config.keyseq_timeout())); + let mut key = try!(rdr.next_key(config.keyseq_timeout())); + if let KeyPress::Meta(digit @ '-') = key { + key = try!(self.digit_argument(rdr, config, digit)); + } else if let KeyPress::Meta(digit @ '0'...'9') = key { + key = try!(self.digit_argument(rdr, config, digit)); + } let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), KeyPress::Esc => Cmd::Abort, // TODO Validate KeyPress::Ctrl('A') => Cmd::BeginningOfLine, - KeyPress::Ctrl('B') => Cmd::BackwardChar, + KeyPress::Ctrl('B') => Cmd::BackwardChar(self.num_args()), KeyPress::Ctrl('E') => Cmd::EndOfLine, - KeyPress::Ctrl('F') => Cmd::ForwardChar, + KeyPress::Ctrl('F') => Cmd::ForwardChar(self.num_args()), KeyPress::Ctrl('G') => Cmd::Abort, - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, - KeyPress::Backspace => Cmd::BackwardDeleteChar, + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(self.num_args()), + KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), KeyPress::Tab => Cmd::Complete, KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Ctrl('N') => Cmd::NextHistory, KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Meta('\x08') => Cmd::BackwardKillWord(Word::Word), - KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(Word::Word), - // KeyPress::Meta('-') => { // digit-argument - // } - // KeyPress::Meta('0'...'9') => { // digit-argument - // } + KeyPress::Meta('\x08') => Cmd::BackwardKillWord(self.num_args(), Word::Word), + KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(self.num_args(), Word::Word), KeyPress::Meta('<') => Cmd::BeginningOfHistory, KeyPress::Meta('>') => Cmd::EndOfHistory, - KeyPress::Meta('B') => Cmd::BackwardWord(Word::Word), + KeyPress::Meta('B') => Cmd::BackwardWord(self.num_args(), Word::Word), KeyPress::Meta('C') => Cmd::CapitalizeWord, - KeyPress::Meta('D') => Cmd::KillWord(Word::Word), - KeyPress::Meta('F') => Cmd::ForwardWord(Word::Word), + KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), Word::Word), + KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), Word::Word), KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, @@ -135,27 +166,46 @@ impl EditState { Ok(cmd) } + fn vi_arg_digit(&mut self, + rdr: &mut R, + config: &Config, + digit: char) + -> Result { + self.num_args = digit.to_digit(10).unwrap() as i32; + loop { + let key = try!(rdr.next_key(config.keyseq_timeout())); + match key { + KeyPress::Char(digit @ '0'...'9') => { + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + } + _ => return Ok(key), + }; + } + } + fn vi_command(&mut self, rdr: &mut R, config: &Config) -> Result { - let key = try!(rdr.next_key(config.keyseq_timeout())); + let mut key = try!(rdr.next_key(config.keyseq_timeout())); + if let KeyPress::Char(digit @ '1'...'9') = key { + key = try!(self.vi_arg_digit(rdr, config, digit)); + } let cmd = match key { KeyPress::Char('$') => Cmd::EndOfLine, KeyPress::End => Cmd::EndOfLine, // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket KeyPress::Char('0') => Cmd::BeginningOfLine, // vi-zero: Vi move to the beginning of line. - // KeyPress::Char('1'...'9') => Cmd::???, // vi-arg-digit KeyPress::Char('^') => Cmd::BeginningOfLine, // vi-first-print TODO Move to the first non-blank character of line. KeyPress::Char('a') => { // vi-append-mode: Vi enter insert mode after the cursor. self.insert = true; - Cmd::ForwardChar + Cmd::ForwardChar(self.num_args()) } KeyPress::Char('A') => { // vi-append-eol: Vi enter insert mode at end of line. self.insert = true; Cmd::EndOfLine } - KeyPress::Char('b') => Cmd::BackwardWord(Word::ViWord), // vi-prev-word - KeyPress::Char('B') => Cmd::BackwardWord(Word::BigWord), + KeyPress::Char('b') => Cmd::BackwardWord(self.num_args(), Word::ViWord), // vi-prev-word + KeyPress::Char('B') => Cmd::BackwardWord(self.num_args(), Word::BigWord), KeyPress::Char('c') => { self.insert = true; try!(self.vi_delete_motion(rdr, config, key)) @@ -166,8 +216,8 @@ impl EditState { } KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), KeyPress::Char('D') => Cmd::KillLine, - KeyPress::Char('e') => Cmd::ViEndWord(Word::ViWord), - KeyPress::Char('E') => Cmd::ViEndWord(Word::BigWord), + KeyPress::Char('e') => Cmd::ViEndWord(self.num_args(), Word::ViWord), + KeyPress::Char('E') => Cmd::ViEndWord(self.num_args(), Word::BigWord), KeyPress::Char('i') => { // vi-insertion-mode self.insert = true; @@ -187,13 +237,13 @@ impl EditState { } } // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank, // vi-put - KeyPress::Char('P') => Cmd::Yank, // vi-put TODO Insert the yanked text before the cursor. + KeyPress::Char('p') => Cmd::Yank(self.num_args()), // vi-put FIXME cursor at end + KeyPress::Char('P') => Cmd::Yank(self.num_args()), // vi-put TODO Insert the yanked text before the cursor. KeyPress::Char('r') => { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); match ch { - KeyPress::Char(c) => Cmd::Replace(c), + KeyPress::Char(c) => Cmd::Replace(self.num_args(), c), KeyPress::Esc => Cmd::Noop, _ => Cmd::Unknown, } @@ -202,7 +252,7 @@ impl EditState { KeyPress::Char('s') => { // vi-substitute-char: Vi replace character under the cursor and enter insert mode. self.insert = true; - Cmd::DeleteChar + Cmd::DeleteChar(self.num_args()) } KeyPress::Char('S') => { // vi-substitute-line: Vi substitute entire line. @@ -210,18 +260,18 @@ impl EditState { Cmd::KillWholeLine } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord(Word::ViWord), // vi-next-word - KeyPress::Char('W') => Cmd::ForwardWord(Word::BigWord), // vi-next-word - KeyPress::Char('x') => Cmd::DeleteChar, // vi-delete: TODO move backward if eol - KeyPress::Char('X') => Cmd::BackwardDeleteChar, // vi-rubout + KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), Word::ViWord), // vi-next-word FIXME + KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), Word::BigWord), // vi-next-word FIXME + KeyPress::Char('x') => Cmd::DeleteChar(self.num_args()), // vi-delete: TODO move backward if eol + KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.num_args()), // vi-rubout // KeyPress::Char('y') => Cmd::???, // vi-yank-to // KeyPress::Char('Y') => Cmd::???, // vi-yank-to - KeyPress::Char('h') => Cmd::BackwardChar, - KeyPress::Ctrl('H') => Cmd::BackwardChar, - KeyPress::Backspace => Cmd::BackwardChar, // TODO Validate + KeyPress::Char('h') => Cmd::BackwardChar(self.num_args()), + KeyPress::Ctrl('H') => Cmd::BackwardChar(self.num_args()), + KeyPress::Backspace => Cmd::BackwardChar(self.num_args()), // TODO Validate KeyPress::Ctrl('G') => Cmd::Abort, - KeyPress::Char('l') => Cmd::ForwardChar, - KeyPress::Char(' ') => Cmd::ForwardChar, + KeyPress::Char('l') => Cmd::ForwardChar(self.num_args()), + KeyPress::Char(' ') => Cmd::ForwardChar(self.num_args()), KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Char('+') => Cmd::NextHistory, KeyPress::Char('j') => Cmd::NextHistory, @@ -248,13 +298,13 @@ impl EditState { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, - KeyPress::Backspace => Cmd::BackwardDeleteChar, + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(1), + KeyPress::Backspace => Cmd::BackwardDeleteChar(1), KeyPress::Tab => Cmd::Complete, KeyPress::Esc => { // vi-movement-mode/vi-command-mode: Vi enter command mode (use alternative key bindings). self.insert = false; - Cmd::BackwardChar + Cmd::BackwardChar(1) } _ => self.common(key), }; @@ -266,31 +316,35 @@ impl EditState { config: &Config, key: KeyPress) -> Result { - let mvt = try!(rdr.next_key(config.keyseq_timeout())); + let mut mvt = try!(rdr.next_key(config.keyseq_timeout())); if mvt == key { return Ok(Cmd::KillWholeLine); } + if let KeyPress::Char(digit @ '1'...'9') = mvt { + // vi-arg-digit + mvt = try!(self.vi_arg_digit(rdr, config, digit)); + } Ok(match mvt { KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. - KeyPress::Char('b') => Cmd::BackwardKillWord(Word::ViWord), - KeyPress::Char('B') => Cmd::BackwardKillWord(Word::BigWord), - KeyPress::Char('e') => Cmd::KillWord(Word::ViWord), - KeyPress::Char('E') => Cmd::KillWord(Word::BigWord), + KeyPress::Char('b') => Cmd::BackwardKillWord(self.num_args(), Word::ViWord), + KeyPress::Char('B') => Cmd::BackwardKillWord(self.num_args(), Word::BigWord), + KeyPress::Char('e') => Cmd::KillWord(self.num_args(), Word::ViWord), + KeyPress::Char('E') => Cmd::KillWord(self.num_args(), Word::BigWord), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViKillTo(cs), + Some(cs) => Cmd::ViKillTo(self.num_args(), cs), None => Cmd::Unknown, } } - KeyPress::Char('h') => Cmd::BackwardDeleteChar, // vi-delete-prev-char: Vi move to previous character (backspace). - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar, - KeyPress::Backspace => Cmd::BackwardDeleteChar, - KeyPress::Char('l') => Cmd::DeleteChar, - KeyPress::Char(' ') => Cmd::DeleteChar, - KeyPress::Char('w') => Cmd::KillWord(Word::ViWord), - KeyPress::Char('W') => Cmd::KillWord(Word::BigWord), + KeyPress::Char('h') => Cmd::BackwardDeleteChar(self.num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(self.num_args()), + KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), + KeyPress::Char('l') => Cmd::DeleteChar(self.num_args()), + KeyPress::Char(' ') => Cmd::DeleteChar(self.num_args()), + KeyPress::Char('w') => Cmd::KillWord(self.num_args(), Word::ViWord), // FIXME + KeyPress::Char('W') => Cmd::KillWord(self.num_args(), Word::BigWord), // FIXME _ => Cmd::Unknown, }) } @@ -318,12 +372,12 @@ impl EditState { fn common(&mut self, key: KeyPress) -> Cmd { match key { KeyPress::Home => Cmd::BeginningOfLine, - KeyPress::Left => Cmd::BackwardChar, + KeyPress::Left => Cmd::BackwardChar(self.num_args()), KeyPress::Ctrl('C') => Cmd::Interrupt, KeyPress::Ctrl('D') => Cmd::EndOfFile, - KeyPress::Delete => Cmd::DeleteChar, + KeyPress::Delete => Cmd::DeleteChar(self.num_args()), KeyPress::End => Cmd::EndOfLine, - KeyPress::Right => Cmd::ForwardChar, + KeyPress::Right => Cmd::ForwardChar(self.num_args()), KeyPress::Ctrl('J') => Cmd::AcceptLine, KeyPress::Enter => Cmd::AcceptLine, KeyPress::Down => Cmd::NextHistory, @@ -334,11 +388,20 @@ impl EditState { KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::BackwardKillWord(Word::BigWord), - KeyPress::Ctrl('Y') => Cmd::Yank, + KeyPress::Ctrl('W') => Cmd::BackwardKillWord(self.num_args(), Word::BigWord), + KeyPress::Ctrl('Y') => Cmd::Yank(self.num_args()), KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, } } + + fn num_args(&mut self) -> i32 { + let num_args = match self.num_args { + 0 => 1, + _ => self.num_args, + }; + self.num_args = 0; + num_args + } } diff --git a/src/lib.rs b/src/lib.rs index e66304b442..63053002e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -620,7 +620,7 @@ fn complete_line(rdr: &mut R, s.old_rows += 1; while cmd != Cmd::SelfInsert('y') && cmd != Cmd::SelfInsert('Y') && cmd != Cmd::SelfInsert('n') && cmd != Cmd::SelfInsert('N') && - cmd != Cmd::BackwardDeleteChar { + cmd != Cmd::BackwardDeleteChar(1) { cmd = try!(s.next_cmd(rdr, config)); } show_completions = match cmd { @@ -668,7 +668,7 @@ fn page_completions(rdr: &mut R, cmd != Cmd::SelfInsert('q') && cmd != Cmd::SelfInsert('Q') && cmd != Cmd::SelfInsert(' ') && - cmd != Cmd::BackwardDeleteChar && cmd != Cmd::AcceptLine { + cmd != Cmd::BackwardDeleteChar(1) && cmd != Cmd::AcceptLine { cmd = try!(s.next_cmd(rdr, config)); } match cmd { @@ -739,7 +739,7 @@ fn reverse_incremental_search(rdr: &mut R, search_buf.push(c); } else { match cmd { - Cmd::BackwardDeleteChar => { + Cmd::BackwardDeleteChar(_) => { search_buf.pop(); continue; } @@ -844,17 +844,17 @@ fn readline_edit(prompt: &str, // Move to the beginning of line. try!(edit_move_home(&mut s)) } - Cmd::BackwardChar => { + Cmd::BackwardChar(_) => { editor.kill_ring.reset(); // Move back a character. try!(edit_move_left(&mut s)) } - Cmd::DeleteChar => { + Cmd::DeleteChar(_) => { editor.kill_ring.reset(); // Delete (forward) one character at point. try!(edit_delete(&mut s)) } - Cmd::Replace(c) => { + Cmd::Replace(_, c) => { editor.kill_ring.reset(); try!(edit_delete(&mut s)); try!(edit_insert(&mut s, c)); @@ -873,12 +873,12 @@ fn readline_edit(prompt: &str, // Move to the end of line. try!(edit_move_end(&mut s)) } - Cmd::ForwardChar => { + Cmd::ForwardChar(_) => { editor.kill_ring.reset(); // Move forward a character. try!(edit_move_right(&mut s)) } - Cmd::BackwardDeleteChar => { + Cmd::BackwardDeleteChar(_) => { editor.kill_ring.reset(); // Delete one character backward. try!(edit_backspace(&mut s)) @@ -928,7 +928,7 @@ fn readline_edit(prompt: &str, let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c)) // FIXME } - Cmd::Yank => { + Cmd::Yank(_) => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { try!(edit_yank(&mut s, text)) @@ -941,7 +941,7 @@ fn readline_edit(prompt: &str, try!(edit_move_end(&mut s)); break; } - Cmd::BackwardKillWord(word_def) => { + Cmd::BackwardKillWord(_, word_def) => { // kill one word backward if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def)) { editor.kill_ring.kill(&text, Mode::Prepend) @@ -957,7 +957,7 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, false)) } - Cmd::BackwardWord(word_def) => { + Cmd::BackwardWord(_, word_def) => { // move backwards one word editor.kill_ring.reset(); try!(edit_move_to_prev_word(&mut s, word_def)) @@ -967,13 +967,13 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - Cmd::KillWord(word_def) => { + Cmd::KillWord(_, word_def) => { // kill one word forward if let Some(text) = try!(edit_delete_word(&mut s, word_def)) { editor.kill_ring.kill(&text, Mode::Append) } } - Cmd::ForwardWord(word_def) => { + Cmd::ForwardWord(_, word_def) => { // move forwards one word editor.kill_ring.reset(); try!(edit_move_to_next_word(&mut s, word_def)) From c4ce47f799e54017c70861c241d247872efdc22c Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 17 Dec 2016 16:25:57 +0100 Subject: [PATCH 0239/1201] Fix Vi word actions --- src/keymap.rs | 95 ++++++++++++++++++++++++---------------------- src/lib.rs | 18 ++++----- src/line_buffer.rs | 80 ++++++++++++++++++++++++++++++++------ 3 files changed, 127 insertions(+), 66 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 1f4f013b41..54cb071f3d 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -7,47 +7,46 @@ use super::Result; #[derive(Debug, Clone, PartialEq)] pub enum Cmd { Abort, // Miscellaneous Command - AcceptLine, // Command For History - BackwardChar(i32), // Command For Moving - BackwardDeleteChar(i32), // Command For Text - BackwardKillWord(i32, Word), // Command For Killing - BackwardWord(i32, Word), // Command For Moving - BeginningOfHistory, // Command For History - BeginningOfLine, // Command For Moving - CapitalizeWord, // Command For Text - ClearScreen, // Command For Moving - Complete, // Command For Completion - DeleteChar(i32), // Command For Text - DowncaseWord, // Command For Text - EndOfFile, // Command For Text - EndOfHistory, // Command For History - EndOfLine, // Command For Moving - ForwardChar(i32), // Command For Moving - ForwardSearchHistory, // Command For History - ForwardWord(i32, Word), // Command For Moving + AcceptLine, + BackwardChar(i32), + BackwardDeleteChar(i32), + BackwardKillWord(i32, Word), // Backward until start of word + BackwardWord(i32, Word), // Backward until start of word + BeginningOfHistory, + BeginningOfLine, + CapitalizeWord, + ClearScreen, + Complete, + DeleteChar(i32), + DowncaseWord, + EndOfFile, + EndOfHistory, + EndOfLine, + ForwardChar(i32), + ForwardSearchHistory, + ForwardWord(i32, At, Word), // Forward until start/end of word Interrupt, - KillLine, // Command For Killing - KillWholeLine, // Command For Killing - KillWord(i32, Word), // Command For Killing - NextHistory, // Command For History + KillLine, + KillWholeLine, + KillWord(i32, At, Word), // Forward until start/end of word + NextHistory, Noop, - PreviousHistory, // Command For History - QuotedInsert, // Command For Text + PreviousHistory, + QuotedInsert, Replace(i32, char), // TODO DeleteChar + SelfInsert - ReverseSearchHistory, // Command For History - SelfInsert(char), // Command For Text + ReverseSearchHistory, + SelfInsert(char), Suspend, - TransposeChars, // Command For Text - TransposeWords, // Command For Text + TransposeChars, + TransposeWords, Unknown, - UnixLikeDiscard, // Command For Killing - // UnixWordRubout, // = BackwardKillWord(Word::BigWord) Command For Killing - UpcaseWord, // Command For Text + UnixLikeDiscard, + // UnixWordRubout, // = BackwardKillWord(Word::BigWord) + UpcaseWord, ViCharSearch(CharSearch), // TODO - ViEndWord(i32, Word), // TODO ViKillTo(i32, CharSearch), // TODO - Yank(i32), // Command For Killing - YankPop, // Command For Killing + Yank(i32), + YankPop, } #[derive(Debug, Clone, PartialEq, Copy)] @@ -60,6 +59,12 @@ pub enum Word { ViWord, } +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum At { + Start, + End, +} + #[derive(Debug, Clone, PartialEq)] pub enum CharSearch { Forward(char), @@ -107,7 +112,7 @@ impl EditState { match digit { '0'...'9' => { self.num_args = digit.to_digit(10).unwrap() as i32; - } + } '-' => { self.num_args = -1; } @@ -155,8 +160,8 @@ impl EditState { KeyPress::Meta('>') => Cmd::EndOfHistory, KeyPress::Meta('B') => Cmd::BackwardWord(self.num_args(), Word::Word), KeyPress::Meta('C') => Cmd::CapitalizeWord, - KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), Word::Word), - KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), Word::Word), + KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), At::End, Word::Word), + KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), At::End, Word::Word), KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, @@ -216,8 +221,8 @@ impl EditState { } KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), KeyPress::Char('D') => Cmd::KillLine, - KeyPress::Char('e') => Cmd::ViEndWord(self.num_args(), Word::ViWord), - KeyPress::Char('E') => Cmd::ViEndWord(self.num_args(), Word::BigWord), + KeyPress::Char('e') => Cmd::ForwardWord(self.num_args(), At::End, Word::ViWord), + KeyPress::Char('E') => Cmd::ForwardWord(self.num_args(), At::End, Word::BigWord), KeyPress::Char('i') => { // vi-insertion-mode self.insert = true; @@ -260,8 +265,8 @@ impl EditState { Cmd::KillWholeLine } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), Word::ViWord), // vi-next-word FIXME - KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), Word::BigWord), // vi-next-word FIXME + KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), At::Start, Word::ViWord), // vi-next-word + KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), At::Start, Word::BigWord), // vi-next-word KeyPress::Char('x') => Cmd::DeleteChar(self.num_args()), // vi-delete: TODO move backward if eol KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.num_args()), // vi-rubout // KeyPress::Char('y') => Cmd::???, // vi-yank-to @@ -329,8 +334,8 @@ impl EditState { KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. KeyPress::Char('b') => Cmd::BackwardKillWord(self.num_args(), Word::ViWord), KeyPress::Char('B') => Cmd::BackwardKillWord(self.num_args(), Word::BigWord), - KeyPress::Char('e') => Cmd::KillWord(self.num_args(), Word::ViWord), - KeyPress::Char('E') => Cmd::KillWord(self.num_args(), Word::BigWord), + KeyPress::Char('e') => Cmd::KillWord(self.num_args(), At::End, Word::ViWord), + KeyPress::Char('E') => Cmd::KillWord(self.num_args(), At::End, Word::BigWord), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { @@ -343,8 +348,8 @@ impl EditState { KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), KeyPress::Char('l') => Cmd::DeleteChar(self.num_args()), KeyPress::Char(' ') => Cmd::DeleteChar(self.num_args()), - KeyPress::Char('w') => Cmd::KillWord(self.num_args(), Word::ViWord), // FIXME - KeyPress::Char('W') => Cmd::KillWord(self.num_args(), Word::BigWord), // FIXME + KeyPress::Char('w') => Cmd::KillWord(self.num_args(), At::Start, Word::ViWord), + KeyPress::Char('W') => Cmd::KillWord(self.num_args(), At::Start, Word::BigWord), _ => Cmd::Unknown, }) } diff --git a/src/lib.rs b/src/lib.rs index 63053002e1..4385fd504b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ use encode_unicode::CharExt; use completion::{Completer, longest_common_prefix}; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; -use keymap::{CharSearch, Cmd, EditState, Word}; +use keymap::{At, Cmd, EditState, Word}; use kill_ring::{Mode, KillRing}; pub use config::{CompletionType, Config, EditMode, HistoryDuplicates}; @@ -443,8 +443,8 @@ fn edit_delete_prev_word(s: &mut State, word_def: Word) -> Result } } -fn edit_move_to_next_word(s: &mut State, word_def: Word) -> Result<()> { - if s.line.move_to_next_word(word_def) { +fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word) -> Result<()> { + if s.line.move_to_next_word(at, word_def) { s.refresh_line() } else { Ok(()) @@ -452,8 +452,8 @@ fn edit_move_to_next_word(s: &mut State, word_def: Word) -> Result<()> { } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. -fn edit_delete_word(s: &mut State, word_def: Word) -> Result> { - if let Some(text) = s.line.delete_word(word_def) { +fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result> { + if let Some(text) = s.line.delete_word(at, word_def) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -967,16 +967,16 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - Cmd::KillWord(_, word_def) => { + Cmd::KillWord(_, at, word_def) => { // kill one word forward - if let Some(text) = try!(edit_delete_word(&mut s, word_def)) { + if let Some(text) = try!(edit_delete_word(&mut s, at, word_def)) { editor.kill_ring.kill(&text, Mode::Append) } } - Cmd::ForwardWord(_, word_def) => { + Cmd::ForwardWord(_, at, word_def) => { // move forwards one word editor.kill_ring.reset(); - try!(edit_move_to_next_word(&mut s, word_def)) + try!(edit_move_to_next_word(&mut s, at, word_def)) } Cmd::DowncaseWord => { // lowercase word after point diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 854fa49d55..3c9ecaabaa 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,6 +1,6 @@ //! Line buffer with current cursor position use std::ops::Deref; -use keymap::Word; +use keymap::{At, Word}; /// Maximum buffer size for the line read pub static MAX_LINE: usize = 4096; @@ -262,6 +262,7 @@ impl LineBuffer { true } + /// Go left until start of word fn prev_word_pos(&self, pos: usize, word_def: Word) -> Option { if pos == 0 { return None; @@ -309,8 +310,46 @@ impl LineBuffer { } } + fn next_pos(&self, pos: usize, at: At, word_def: Word) -> Option { + match at { + At::End => { + match self.next_word_pos(pos, word_def) { + Some((_, end)) => Some(end), + _ => None, + } + } + At::Start => self.next_start_of_word_pos(pos, word_def), + } + } + + /// Go right until start of word + fn next_start_of_word_pos(&self, pos: usize, word_def: Word) -> Option { + if pos < self.buf.len() { + let test = is_break_char(word_def); + let mut pos = pos; + // eat any non-spaces + pos += self.buf[pos..] + .chars() + .take_while(|ch| !test(ch)) + .map(char::len_utf8) + .sum(); + if pos < self.buf.len() { + // eat any spaces + pos += self.buf[pos..] + .chars() + .take_while(test) + .map(char::len_utf8) + .sum(); + } + Some(pos) + } else { + None + } + } + + /// Go right until end of word /// Returns the position (start, end) of the next word. - pub fn next_word_pos(&self, pos: usize, word_def: Word) -> Option<(usize, usize)> { + fn next_word_pos(&self, pos: usize, word_def: Word) -> Option<(usize, usize)> { if pos < self.buf.len() { let test = is_break_char(word_def); let mut pos = pos; @@ -336,22 +375,21 @@ impl LineBuffer { } /// Moves the cursor to the end of next word. - pub fn move_to_next_word(&mut self, word_def: Word) -> bool { - if let Some((_, end)) = self.next_word_pos(self.pos, word_def) { - self.pos = end; + pub fn move_to_next_word(&mut self, at: At, word_def: Word) -> bool { + if let Some(pos) = self.next_pos(self.pos, at, word_def) { + self.pos = pos; true } else { false } } - // TODO move_to_end_of_word (Vi mode) // TODO move_to (Vi mode: f|F|t|T) /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. - pub fn delete_word(&mut self, word_def: Word) -> Option { - if let Some((_, end)) = self.next_word_pos(self.pos, word_def) { - let word = self.buf.drain(self.pos..end).collect(); + pub fn delete_word(&mut self, at: At, word_def: Word) -> Option { + if let Some(pos) = self.next_pos(self.pos, at, word_def) { + let word = self.buf.drain(self.pos..pos).collect(); Some(word) } else { None @@ -490,7 +528,7 @@ fn is_whitespace(ch: &char) -> bool { #[cfg(test)] mod test { - use super::{LineBuffer, MAX_LINE, Word, WordAction}; + use super::{LineBuffer, MAX_LINE, At, Word, WordAction}; #[test] fn insert() { @@ -609,21 +647,39 @@ mod test { #[test] fn move_to_next_word() { let mut s = LineBuffer::init("a ß c", 1); - let ok = s.move_to_next_word(Word::Word); + let ok = s.move_to_next_word(At::End, Word::Word); assert_eq!("a ß c", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); } + #[test] + fn move_to_start_of_word() { + let mut s = LineBuffer::init("a ß c", 2); + let ok = s.move_to_next_word(At::Start, Word::Word); + assert_eq!("a ß c", s.buf); + assert_eq!(6, s.pos); + assert_eq!(true, ok); + } + #[test] fn delete_word() { let mut s = LineBuffer::init("a ß c", 1); - let text = s.delete_word(Word::Word); + let text = s.delete_word(At::End, Word::Word); assert_eq!("a c", s.buf); assert_eq!(1, s.pos); assert_eq!(Some(" ß".to_string()), text); } + #[test] + fn delete_til_start_of_word() { + let mut s = LineBuffer::init("a ß c", 2); + let text = s.delete_word(At::Start, Word::Word); + assert_eq!("a c", s.buf); + assert_eq!(2, s.pos); + assert_eq!(Some("ß ".to_string()), text); + } + #[test] fn edit_word() { let mut s = LineBuffer::init("a ßeta c", 1); From c9fe4f00e148b5f62feee1abc43ffb00cc12d037 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 17 Dec 2016 17:16:20 +0100 Subject: [PATCH 0240/1201] Fix test on stable --- src/line_buffer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 3c9ecaabaa..c0430f565d 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -528,7 +528,8 @@ fn is_whitespace(ch: &char) -> bool { #[cfg(test)] mod test { - use super::{LineBuffer, MAX_LINE, At, Word, WordAction}; + use keymap::{At, Word}; + use super::{LineBuffer, MAX_LINE, WordAction}; #[test] fn insert() { From 1a7b1df595c833c35f8fd23ac48bfee69a2345db Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 17 Dec 2016 17:16:20 +0100 Subject: [PATCH 0241/1201] Fix test on stable --- src/line_buffer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 3c9ecaabaa..c0430f565d 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -528,7 +528,8 @@ fn is_whitespace(ch: &char) -> bool { #[cfg(test)] mod test { - use super::{LineBuffer, MAX_LINE, At, Word, WordAction}; + use keymap::{At, Word}; + use super::{LineBuffer, MAX_LINE, WordAction}; #[test] fn insert() { From 06c3ad5476023357fcc6b4859813838b76679d06 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 17 Dec 2016 19:00:00 +0100 Subject: [PATCH 0242/1201] Fix clippy warnings --- src/keymap.rs | 87 ++++++++++++++++++++++------------------------ src/line_buffer.rs | 22 ++++++------ src/tty/unix.rs | 6 ++-- 3 files changed, 55 insertions(+), 60 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 54cb071f3d..bd67f4b00d 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -41,7 +41,7 @@ pub enum Cmd { TransposeWords, Unknown, UnixLikeDiscard, - // UnixWordRubout, // = BackwardKillWord(Word::BigWord) + // UnixWordRubout, // = BackwardKillWord(Word::Big) UpcaseWord, ViCharSearch(CharSearch), // TODO ViKillTo(i32, CharSearch), // TODO @@ -52,11 +52,11 @@ pub enum Cmd { #[derive(Debug, Clone, PartialEq, Copy)] pub enum Word { // non-blanks characters - BigWord, + Big, // alphanumeric characters - Word, + Emacs, // alphanumeric (and '_') characters - ViWord, + Vi, } #[derive(Debug, Clone, PartialEq, Copy)] @@ -121,9 +121,7 @@ impl EditState { loop { let key = try!(rdr.next_key(config.keyseq_timeout())); match key { - KeyPress::Char(digit @ '0'...'9') => { - self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; - } + KeyPress::Char(digit @ '0'...'9') | KeyPress::Meta(digit @ '0'...'9') => { self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; } @@ -141,27 +139,27 @@ impl EditState { } let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), - KeyPress::Esc => Cmd::Abort, // TODO Validate KeyPress::Ctrl('A') => Cmd::BeginningOfLine, KeyPress::Ctrl('B') => Cmd::BackwardChar(self.num_args()), KeyPress::Ctrl('E') => Cmd::EndOfLine, KeyPress::Ctrl('F') => Cmd::ForwardChar(self.num_args()), - KeyPress::Ctrl('G') => Cmd::Abort, - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(self.num_args()), + KeyPress::Ctrl('G') | + KeyPress::Esc => Cmd::Abort, + KeyPress::Ctrl('H') | KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), KeyPress::Tab => Cmd::Complete, KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Ctrl('N') => Cmd::NextHistory, KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Meta('\x08') => Cmd::BackwardKillWord(self.num_args(), Word::Word), - KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(self.num_args(), Word::Word), + KeyPress::Meta('\x08') | + KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(self.num_args(), Word::Emacs), KeyPress::Meta('<') => Cmd::BeginningOfHistory, KeyPress::Meta('>') => Cmd::EndOfHistory, - KeyPress::Meta('B') => Cmd::BackwardWord(self.num_args(), Word::Word), + KeyPress::Meta('B') => Cmd::BackwardWord(self.num_args(), Word::Emacs), KeyPress::Meta('C') => Cmd::CapitalizeWord, - KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), At::End, Word::Word), - KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), At::End, Word::Word), + KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), At::End, Word::Emacs), + KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), At::End, Word::Emacs), KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, @@ -194,7 +192,7 @@ impl EditState { key = try!(self.vi_arg_digit(rdr, config, digit)); } let cmd = match key { - KeyPress::Char('$') => Cmd::EndOfLine, + KeyPress::Char('$') | KeyPress::End => Cmd::EndOfLine, // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket KeyPress::Char('0') => Cmd::BeginningOfLine, // vi-zero: Vi move to the beginning of line. @@ -209,8 +207,8 @@ impl EditState { self.insert = true; Cmd::EndOfLine } - KeyPress::Char('b') => Cmd::BackwardWord(self.num_args(), Word::ViWord), // vi-prev-word - KeyPress::Char('B') => Cmd::BackwardWord(self.num_args(), Word::BigWord), + KeyPress::Char('b') => Cmd::BackwardWord(self.num_args(), Word::Vi), // vi-prev-word + KeyPress::Char('B') => Cmd::BackwardWord(self.num_args(), Word::Big), KeyPress::Char('c') => { self.insert = true; try!(self.vi_delete_motion(rdr, config, key)) @@ -220,9 +218,9 @@ impl EditState { Cmd::KillLine } KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), - KeyPress::Char('D') => Cmd::KillLine, - KeyPress::Char('e') => Cmd::ForwardWord(self.num_args(), At::End, Word::ViWord), - KeyPress::Char('E') => Cmd::ForwardWord(self.num_args(), At::End, Word::BigWord), + KeyPress::Char('D') | KeyPress::Ctrl('K') => Cmd::KillLine, + KeyPress::Char('e') => Cmd::ForwardWord(self.num_args(), At::End, Word::Vi), + KeyPress::Char('E') => Cmd::ForwardWord(self.num_args(), At::End, Word::Big), KeyPress::Char('i') => { // vi-insertion-mode self.insert = true; @@ -265,26 +263,25 @@ impl EditState { Cmd::KillWholeLine } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), At::Start, Word::ViWord), // vi-next-word - KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), At::Start, Word::BigWord), // vi-next-word + KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), At::Start, Word::Vi), // vi-next-word + KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), At::Start, Word::Big), // vi-next-word KeyPress::Char('x') => Cmd::DeleteChar(self.num_args()), // vi-delete: TODO move backward if eol KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.num_args()), // vi-rubout // KeyPress::Char('y') => Cmd::???, // vi-yank-to // KeyPress::Char('Y') => Cmd::???, // vi-yank-to - KeyPress::Char('h') => Cmd::BackwardChar(self.num_args()), - KeyPress::Ctrl('H') => Cmd::BackwardChar(self.num_args()), + KeyPress::Char('h') | + KeyPress::Ctrl('H') | KeyPress::Backspace => Cmd::BackwardChar(self.num_args()), // TODO Validate KeyPress::Ctrl('G') => Cmd::Abort, - KeyPress::Char('l') => Cmd::ForwardChar(self.num_args()), + KeyPress::Char('l') | KeyPress::Char(' ') => Cmd::ForwardChar(self.num_args()), KeyPress::Ctrl('L') => Cmd::ClearScreen, - KeyPress::Char('+') => Cmd::NextHistory, - KeyPress::Char('j') => Cmd::NextHistory, + KeyPress::Char('+') | + KeyPress::Char('j') | KeyPress::Ctrl('N') => Cmd::NextHistory, - KeyPress::Char('-') => Cmd::PreviousHistory, - KeyPress::Char('k') => Cmd::PreviousHistory, + KeyPress::Char('-') | + KeyPress::Char('k') | KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('R') => { self.insert = true; // TODO Validate Cmd::ReverseSearchHistory @@ -303,7 +300,7 @@ impl EditState { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(1), + KeyPress::Ctrl('H') | KeyPress::Backspace => Cmd::BackwardDeleteChar(1), KeyPress::Tab => Cmd::Complete, KeyPress::Esc => { @@ -332,10 +329,10 @@ impl EditState { Ok(match mvt { KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. - KeyPress::Char('b') => Cmd::BackwardKillWord(self.num_args(), Word::ViWord), - KeyPress::Char('B') => Cmd::BackwardKillWord(self.num_args(), Word::BigWord), - KeyPress::Char('e') => Cmd::KillWord(self.num_args(), At::End, Word::ViWord), - KeyPress::Char('E') => Cmd::KillWord(self.num_args(), At::End, Word::BigWord), + KeyPress::Char('b') => Cmd::BackwardKillWord(self.num_args(), Word::Vi), + KeyPress::Char('B') => Cmd::BackwardKillWord(self.num_args(), Word::Big), + KeyPress::Char('e') => Cmd::KillWord(self.num_args(), At::End, Word::Vi), + KeyPress::Char('E') => Cmd::KillWord(self.num_args(), At::End, Word::Big), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { @@ -343,13 +340,13 @@ impl EditState { None => Cmd::Unknown, } } - KeyPress::Char('h') => Cmd::BackwardDeleteChar(self.num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(self.num_args()), - KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), - KeyPress::Char('l') => Cmd::DeleteChar(self.num_args()), + KeyPress::Char('h') | + KeyPress::Ctrl('H') | + KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Char('l') | KeyPress::Char(' ') => Cmd::DeleteChar(self.num_args()), - KeyPress::Char('w') => Cmd::KillWord(self.num_args(), At::Start, Word::ViWord), - KeyPress::Char('W') => Cmd::KillWord(self.num_args(), At::Start, Word::BigWord), + KeyPress::Char('w') => Cmd::KillWord(self.num_args(), At::Start, Word::Vi), + KeyPress::Char('W') => Cmd::KillWord(self.num_args(), At::Start, Word::Big), _ => Cmd::Unknown, }) } @@ -383,17 +380,17 @@ impl EditState { KeyPress::Delete => Cmd::DeleteChar(self.num_args()), KeyPress::End => Cmd::EndOfLine, KeyPress::Right => Cmd::ForwardChar(self.num_args()), - KeyPress::Ctrl('J') => Cmd::AcceptLine, + KeyPress::Ctrl('J') | KeyPress::Enter => Cmd::AcceptLine, KeyPress::Down => Cmd::NextHistory, KeyPress::Up => Cmd::PreviousHistory, - KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, + KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::BackwardKillWord(self.num_args(), Word::BigWord), + KeyPress::Ctrl('W') => Cmd::BackwardKillWord(self.num_args(), Word::Big), KeyPress::Ctrl('Y') => Cmd::Yank(self.num_args()), KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::UnknownEscSeq => Cmd::Noop, diff --git a/src/line_buffer.rs b/src/line_buffer.rs index c0430f565d..2ae61d9064 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -398,7 +398,7 @@ impl LineBuffer { /// Alter the next word. pub fn edit_word(&mut self, a: WordAction) -> bool { - if let Some((start, end)) = self.next_word_pos(self.pos, Word::Word) { + if let Some((start, end)) = self.next_word_pos(self.pos, Word::Emacs) { if start == end { return false; } @@ -428,7 +428,7 @@ impl LineBuffer { // prevword___oneword__ // ^ ^ ^ // prev_start start self.pos/end - let word_def = Word::Word; + let word_def = Word::Emacs; if let Some(start) = self.prev_word_pos(self.pos, word_def) { if let Some(prev_start) = self.prev_word_pos(start, word_def) { let (_, prev_end) = self.next_word_pos(prev_start, word_def).unwrap(); @@ -510,9 +510,9 @@ fn insert_str(buf: &mut String, idx: usize, s: &str) { fn is_break_char(word_def: Word) -> fn(&char) -> bool { match word_def { - Word::Word => is_not_alphanumeric, - Word::ViWord => is_not_alphanumeric_and_underscore, - Word::BigWord => is_whitespace, + Word::Emacs => is_not_alphanumeric, + Word::Vi => is_not_alphanumeric_and_underscore, + Word::Big => is_whitespace, } } @@ -630,7 +630,7 @@ mod test { #[test] fn move_to_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let ok = s.move_to_prev_word(Word::Word); + let ok = s.move_to_prev_word(Word::Emacs); assert_eq!("a ß c", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); @@ -639,7 +639,7 @@ mod test { #[test] fn delete_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let text = s.delete_prev_word(Word::BigWord); + let text = s.delete_prev_word(Word::Big); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); @@ -648,7 +648,7 @@ mod test { #[test] fn move_to_next_word() { let mut s = LineBuffer::init("a ß c", 1); - let ok = s.move_to_next_word(At::End, Word::Word); + let ok = s.move_to_next_word(At::End, Word::Emacs); assert_eq!("a ß c", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); @@ -657,7 +657,7 @@ mod test { #[test] fn move_to_start_of_word() { let mut s = LineBuffer::init("a ß c", 2); - let ok = s.move_to_next_word(At::Start, Word::Word); + let ok = s.move_to_next_word(At::Start, Word::Emacs); assert_eq!("a ß c", s.buf); assert_eq!(6, s.pos); assert_eq!(true, ok); @@ -666,7 +666,7 @@ mod test { #[test] fn delete_word() { let mut s = LineBuffer::init("a ß c", 1); - let text = s.delete_word(At::End, Word::Word); + let text = s.delete_word(At::End, Word::Emacs); assert_eq!("a c", s.buf); assert_eq!(1, s.pos); assert_eq!(Some(" ß".to_string()), text); @@ -675,7 +675,7 @@ mod test { #[test] fn delete_til_start_of_word() { let mut s = LineBuffer::init("a ß c", 2); - let text = s.delete_word(At::Start, Word::Word); + let text = s.delete_word(At::Start, Word::Emacs); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 8436116dbd..183ad2c54a 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -111,13 +111,11 @@ impl PosixRawReader { let seq3 = try!(self.next_char()); if seq3 == '~' { Ok(match seq2 { - '1' => KeyPress::Home, // xterm + '1' | '7' => KeyPress::Home, // '1': xterm '3' => KeyPress::Delete, - '4' => KeyPress::End, // xterm + '4' | '8' => KeyPress::End, // '4': xterm '5' => KeyPress::PageUp, '6' => KeyPress::PageDown, - '7' => KeyPress::Home, - '8' => KeyPress::End, _ => KeyPress::UnknownEscSeq, }) } else { From 5c1079df4d2d44be9335ac7f80d0d0e9dd19a7c2 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 17 Dec 2016 19:00:00 +0100 Subject: [PATCH 0243/1201] Fix clippy warnings --- src/keymap.rs | 87 ++++++++++++++++++++++------------------------ src/line_buffer.rs | 22 ++++++------ src/tty/unix.rs | 6 ++-- 3 files changed, 55 insertions(+), 60 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 54cb071f3d..bd67f4b00d 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -41,7 +41,7 @@ pub enum Cmd { TransposeWords, Unknown, UnixLikeDiscard, - // UnixWordRubout, // = BackwardKillWord(Word::BigWord) + // UnixWordRubout, // = BackwardKillWord(Word::Big) UpcaseWord, ViCharSearch(CharSearch), // TODO ViKillTo(i32, CharSearch), // TODO @@ -52,11 +52,11 @@ pub enum Cmd { #[derive(Debug, Clone, PartialEq, Copy)] pub enum Word { // non-blanks characters - BigWord, + Big, // alphanumeric characters - Word, + Emacs, // alphanumeric (and '_') characters - ViWord, + Vi, } #[derive(Debug, Clone, PartialEq, Copy)] @@ -121,9 +121,7 @@ impl EditState { loop { let key = try!(rdr.next_key(config.keyseq_timeout())); match key { - KeyPress::Char(digit @ '0'...'9') => { - self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; - } + KeyPress::Char(digit @ '0'...'9') | KeyPress::Meta(digit @ '0'...'9') => { self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; } @@ -141,27 +139,27 @@ impl EditState { } let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), - KeyPress::Esc => Cmd::Abort, // TODO Validate KeyPress::Ctrl('A') => Cmd::BeginningOfLine, KeyPress::Ctrl('B') => Cmd::BackwardChar(self.num_args()), KeyPress::Ctrl('E') => Cmd::EndOfLine, KeyPress::Ctrl('F') => Cmd::ForwardChar(self.num_args()), - KeyPress::Ctrl('G') => Cmd::Abort, - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(self.num_args()), + KeyPress::Ctrl('G') | + KeyPress::Esc => Cmd::Abort, + KeyPress::Ctrl('H') | KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), KeyPress::Tab => Cmd::Complete, KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Ctrl('N') => Cmd::NextHistory, KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Meta('\x08') => Cmd::BackwardKillWord(self.num_args(), Word::Word), - KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(self.num_args(), Word::Word), + KeyPress::Meta('\x08') | + KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(self.num_args(), Word::Emacs), KeyPress::Meta('<') => Cmd::BeginningOfHistory, KeyPress::Meta('>') => Cmd::EndOfHistory, - KeyPress::Meta('B') => Cmd::BackwardWord(self.num_args(), Word::Word), + KeyPress::Meta('B') => Cmd::BackwardWord(self.num_args(), Word::Emacs), KeyPress::Meta('C') => Cmd::CapitalizeWord, - KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), At::End, Word::Word), - KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), At::End, Word::Word), + KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), At::End, Word::Emacs), + KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), At::End, Word::Emacs), KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, @@ -194,7 +192,7 @@ impl EditState { key = try!(self.vi_arg_digit(rdr, config, digit)); } let cmd = match key { - KeyPress::Char('$') => Cmd::EndOfLine, + KeyPress::Char('$') | KeyPress::End => Cmd::EndOfLine, // TODO KeyPress::Char('%') => Cmd::???, Move to the corresponding opening/closing bracket KeyPress::Char('0') => Cmd::BeginningOfLine, // vi-zero: Vi move to the beginning of line. @@ -209,8 +207,8 @@ impl EditState { self.insert = true; Cmd::EndOfLine } - KeyPress::Char('b') => Cmd::BackwardWord(self.num_args(), Word::ViWord), // vi-prev-word - KeyPress::Char('B') => Cmd::BackwardWord(self.num_args(), Word::BigWord), + KeyPress::Char('b') => Cmd::BackwardWord(self.num_args(), Word::Vi), // vi-prev-word + KeyPress::Char('B') => Cmd::BackwardWord(self.num_args(), Word::Big), KeyPress::Char('c') => { self.insert = true; try!(self.vi_delete_motion(rdr, config, key)) @@ -220,9 +218,9 @@ impl EditState { Cmd::KillLine } KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), - KeyPress::Char('D') => Cmd::KillLine, - KeyPress::Char('e') => Cmd::ForwardWord(self.num_args(), At::End, Word::ViWord), - KeyPress::Char('E') => Cmd::ForwardWord(self.num_args(), At::End, Word::BigWord), + KeyPress::Char('D') | KeyPress::Ctrl('K') => Cmd::KillLine, + KeyPress::Char('e') => Cmd::ForwardWord(self.num_args(), At::End, Word::Vi), + KeyPress::Char('E') => Cmd::ForwardWord(self.num_args(), At::End, Word::Big), KeyPress::Char('i') => { // vi-insertion-mode self.insert = true; @@ -265,26 +263,25 @@ impl EditState { Cmd::KillWholeLine } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), At::Start, Word::ViWord), // vi-next-word - KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), At::Start, Word::BigWord), // vi-next-word + KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), At::Start, Word::Vi), // vi-next-word + KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), At::Start, Word::Big), // vi-next-word KeyPress::Char('x') => Cmd::DeleteChar(self.num_args()), // vi-delete: TODO move backward if eol KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.num_args()), // vi-rubout // KeyPress::Char('y') => Cmd::???, // vi-yank-to // KeyPress::Char('Y') => Cmd::???, // vi-yank-to - KeyPress::Char('h') => Cmd::BackwardChar(self.num_args()), - KeyPress::Ctrl('H') => Cmd::BackwardChar(self.num_args()), + KeyPress::Char('h') | + KeyPress::Ctrl('H') | KeyPress::Backspace => Cmd::BackwardChar(self.num_args()), // TODO Validate KeyPress::Ctrl('G') => Cmd::Abort, - KeyPress::Char('l') => Cmd::ForwardChar(self.num_args()), + KeyPress::Char('l') | KeyPress::Char(' ') => Cmd::ForwardChar(self.num_args()), KeyPress::Ctrl('L') => Cmd::ClearScreen, - KeyPress::Char('+') => Cmd::NextHistory, - KeyPress::Char('j') => Cmd::NextHistory, + KeyPress::Char('+') | + KeyPress::Char('j') | KeyPress::Ctrl('N') => Cmd::NextHistory, - KeyPress::Char('-') => Cmd::PreviousHistory, - KeyPress::Char('k') => Cmd::PreviousHistory, + KeyPress::Char('-') | + KeyPress::Char('k') | KeyPress::Ctrl('P') => Cmd::PreviousHistory, - KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('R') => { self.insert = true; // TODO Validate Cmd::ReverseSearchHistory @@ -303,7 +300,7 @@ impl EditState { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(1), + KeyPress::Ctrl('H') | KeyPress::Backspace => Cmd::BackwardDeleteChar(1), KeyPress::Tab => Cmd::Complete, KeyPress::Esc => { @@ -332,10 +329,10 @@ impl EditState { Ok(match mvt { KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. - KeyPress::Char('b') => Cmd::BackwardKillWord(self.num_args(), Word::ViWord), - KeyPress::Char('B') => Cmd::BackwardKillWord(self.num_args(), Word::BigWord), - KeyPress::Char('e') => Cmd::KillWord(self.num_args(), At::End, Word::ViWord), - KeyPress::Char('E') => Cmd::KillWord(self.num_args(), At::End, Word::BigWord), + KeyPress::Char('b') => Cmd::BackwardKillWord(self.num_args(), Word::Vi), + KeyPress::Char('B') => Cmd::BackwardKillWord(self.num_args(), Word::Big), + KeyPress::Char('e') => Cmd::KillWord(self.num_args(), At::End, Word::Vi), + KeyPress::Char('E') => Cmd::KillWord(self.num_args(), At::End, Word::Big), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { @@ -343,13 +340,13 @@ impl EditState { None => Cmd::Unknown, } } - KeyPress::Char('h') => Cmd::BackwardDeleteChar(self.num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). - KeyPress::Ctrl('H') => Cmd::BackwardDeleteChar(self.num_args()), - KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), - KeyPress::Char('l') => Cmd::DeleteChar(self.num_args()), + KeyPress::Char('h') | + KeyPress::Ctrl('H') | + KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Char('l') | KeyPress::Char(' ') => Cmd::DeleteChar(self.num_args()), - KeyPress::Char('w') => Cmd::KillWord(self.num_args(), At::Start, Word::ViWord), - KeyPress::Char('W') => Cmd::KillWord(self.num_args(), At::Start, Word::BigWord), + KeyPress::Char('w') => Cmd::KillWord(self.num_args(), At::Start, Word::Vi), + KeyPress::Char('W') => Cmd::KillWord(self.num_args(), At::Start, Word::Big), _ => Cmd::Unknown, }) } @@ -383,17 +380,17 @@ impl EditState { KeyPress::Delete => Cmd::DeleteChar(self.num_args()), KeyPress::End => Cmd::EndOfLine, KeyPress::Right => Cmd::ForwardChar(self.num_args()), - KeyPress::Ctrl('J') => Cmd::AcceptLine, + KeyPress::Ctrl('J') | KeyPress::Enter => Cmd::AcceptLine, KeyPress::Down => Cmd::NextHistory, KeyPress::Up => Cmd::PreviousHistory, - KeyPress::Ctrl('Q') => Cmd::QuotedInsert, // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('R') => Cmd::ReverseSearchHistory, KeyPress::Ctrl('S') => Cmd::ForwardSearchHistory, // most terminals override Ctrl+S to suspend execution KeyPress::Ctrl('T') => Cmd::TransposeChars, KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, + KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::BackwardKillWord(self.num_args(), Word::BigWord), + KeyPress::Ctrl('W') => Cmd::BackwardKillWord(self.num_args(), Word::Big), KeyPress::Ctrl('Y') => Cmd::Yank(self.num_args()), KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::UnknownEscSeq => Cmd::Noop, diff --git a/src/line_buffer.rs b/src/line_buffer.rs index c0430f565d..2ae61d9064 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -398,7 +398,7 @@ impl LineBuffer { /// Alter the next word. pub fn edit_word(&mut self, a: WordAction) -> bool { - if let Some((start, end)) = self.next_word_pos(self.pos, Word::Word) { + if let Some((start, end)) = self.next_word_pos(self.pos, Word::Emacs) { if start == end { return false; } @@ -428,7 +428,7 @@ impl LineBuffer { // prevword___oneword__ // ^ ^ ^ // prev_start start self.pos/end - let word_def = Word::Word; + let word_def = Word::Emacs; if let Some(start) = self.prev_word_pos(self.pos, word_def) { if let Some(prev_start) = self.prev_word_pos(start, word_def) { let (_, prev_end) = self.next_word_pos(prev_start, word_def).unwrap(); @@ -510,9 +510,9 @@ fn insert_str(buf: &mut String, idx: usize, s: &str) { fn is_break_char(word_def: Word) -> fn(&char) -> bool { match word_def { - Word::Word => is_not_alphanumeric, - Word::ViWord => is_not_alphanumeric_and_underscore, - Word::BigWord => is_whitespace, + Word::Emacs => is_not_alphanumeric, + Word::Vi => is_not_alphanumeric_and_underscore, + Word::Big => is_whitespace, } } @@ -630,7 +630,7 @@ mod test { #[test] fn move_to_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let ok = s.move_to_prev_word(Word::Word); + let ok = s.move_to_prev_word(Word::Emacs); assert_eq!("a ß c", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); @@ -639,7 +639,7 @@ mod test { #[test] fn delete_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let text = s.delete_prev_word(Word::BigWord); + let text = s.delete_prev_word(Word::Big); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); @@ -648,7 +648,7 @@ mod test { #[test] fn move_to_next_word() { let mut s = LineBuffer::init("a ß c", 1); - let ok = s.move_to_next_word(At::End, Word::Word); + let ok = s.move_to_next_word(At::End, Word::Emacs); assert_eq!("a ß c", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); @@ -657,7 +657,7 @@ mod test { #[test] fn move_to_start_of_word() { let mut s = LineBuffer::init("a ß c", 2); - let ok = s.move_to_next_word(At::Start, Word::Word); + let ok = s.move_to_next_word(At::Start, Word::Emacs); assert_eq!("a ß c", s.buf); assert_eq!(6, s.pos); assert_eq!(true, ok); @@ -666,7 +666,7 @@ mod test { #[test] fn delete_word() { let mut s = LineBuffer::init("a ß c", 1); - let text = s.delete_word(At::End, Word::Word); + let text = s.delete_word(At::End, Word::Emacs); assert_eq!("a c", s.buf); assert_eq!(1, s.pos); assert_eq!(Some(" ß".to_string()), text); @@ -675,7 +675,7 @@ mod test { #[test] fn delete_til_start_of_word() { let mut s = LineBuffer::init("a ß c", 2); - let text = s.delete_word(At::Start, Word::Word); + let text = s.delete_word(At::Start, Word::Emacs); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); diff --git a/src/tty/unix.rs b/src/tty/unix.rs index e9e8fe42c9..e28f771fee 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -112,13 +112,11 @@ impl PosixRawReader { let seq3 = try!(self.next_char()); if seq3 == '~' { Ok(match seq2 { - '1' => KeyPress::Home, // xterm + '1' | '7' => KeyPress::Home, // '1': xterm '3' => KeyPress::Delete, - '4' => KeyPress::End, // xterm + '4' | '8' => KeyPress::End, // '4': xterm '5' => KeyPress::PageUp, '6' => KeyPress::PageDown, - '7' => KeyPress::Home, - '8' => KeyPress::End, _ => KeyPress::UnknownEscSeq, }) } else { From 1f712e725eb451a63c47bd70306ba69647cfd75f Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 19 Dec 2016 20:53:56 +0100 Subject: [PATCH 0244/1201] Vi char search and delete to --- src/keymap.rs | 11 +++---- src/lib.rs | 28 +++++++++++++++++- src/line_buffer.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 104 insertions(+), 8 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index bd67f4b00d..ab9cc1aa81 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -43,8 +43,8 @@ pub enum Cmd { UnixLikeDiscard, // UnixWordRubout, // = BackwardKillWord(Word::Big) UpcaseWord, - ViCharSearch(CharSearch), // TODO - ViKillTo(i32, CharSearch), // TODO + ViCharSearch(i32, CharSearch), + ViDeleteTo(i32, CharSearch), Yank(i32), YankPop, } @@ -218,7 +218,8 @@ impl EditState { Cmd::KillLine } KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), - KeyPress::Char('D') | KeyPress::Ctrl('K') => Cmd::KillLine, + KeyPress::Char('D') | + KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Char('e') => Cmd::ForwardWord(self.num_args(), At::End, Word::Vi), KeyPress::Char('E') => Cmd::ForwardWord(self.num_args(), At::End, Word::Big), KeyPress::Char('i') => { @@ -235,7 +236,7 @@ impl EditState { // vi-char-search let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViCharSearch(cs), + Some(cs) => Cmd::ViCharSearch(self.num_args(), cs), None => Cmd::Unknown, } } @@ -336,7 +337,7 @@ impl EditState { KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViKillTo(self.num_args(), cs), + Some(cs) => Cmd::ViDeleteTo(self.num_args(), cs), None => Cmd::Unknown, } } diff --git a/src/lib.rs b/src/lib.rs index fb9c41a49c..1a9dbebed0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ use tty::{RawMode, RawReader, Terminal, Term}; use completion::{Completer, longest_common_prefix}; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; -use keymap::{At, Cmd, EditState, Word}; +use keymap::{At, CharSearch, Cmd, EditState, Word}; use kill_ring::{Mode, KillRing}; pub use config::{CompletionType, Config, EditMode, HistoryDuplicates}; @@ -453,6 +453,14 @@ fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word) -> Result<()> { } } +fn edit_move_to(s: &mut State, cs: CharSearch) -> Result<()> { + if s.line.move_to(cs) { + s.refresh_line() + } else { + Ok(()) + } +} + /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result> { if let Some(text) = s.line.delete_word(at, word_def) { @@ -463,6 +471,15 @@ fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result Result> { + if let Some(text) = s.line.delete_to(cs) { + try!(s.refresh_line()); + Ok(Some(text)) + } else { + Ok(None) + } +} + fn edit_word(s: &mut State, a: WordAction) -> Result<()> { if s.line.edit_word(a) { s.refresh_line() @@ -1001,6 +1018,15 @@ fn readline_edit(prompt: &str, try!(edit_yank_pop(&mut s, yank_size, text)) } } + Cmd::ViCharSearch(_, cs) => { + editor.kill_ring.reset(); + try!(edit_move_to(&mut s, cs)) + } + Cmd::ViDeleteTo(_, cs) => { + if let Some(text) = try!(edit_delete_to(&mut s, cs)) { + editor.kill_ring.kill(&text, Mode::Append) + } + } Cmd::Interrupt => { editor.kill_ring.reset(); return Err(error::ReadlineError::Interrupted); diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 2ae61d9064..29435d2d74 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,6 +1,6 @@ //! Line buffer with current cursor position use std::ops::Deref; -use keymap::{At, Word}; +use keymap::{At, CharSearch, Word}; /// Maximum buffer size for the line read pub static MAX_LINE: usize = 4096; @@ -384,7 +384,54 @@ impl LineBuffer { } } - // TODO move_to (Vi mode: f|F|t|T) + fn search_char_pos(&mut self, cs: &CharSearch) -> Option { + let mut shift = 0; + let search_result = match *cs { + CharSearch::Backward(c) | + CharSearch::BackwardAfter(c) => { + self.buf[..self.pos].rfind(c) + // if let Some(pc) = self.char_before_cursor() { + // self.buf[..self.pos - pc.len_utf8()].rfind(c) + // } else { + // None + // } + } + CharSearch::Forward(c) | + CharSearch::ForwardBefore(c) => { + if let Some(cc) = self.char_at_cursor() { + shift = self.pos + cc.len_utf8(); + if shift < self.buf.len() { + self.buf[shift..].find(c) + } else { + None + } + } else { + None + } + } + }; + if let Some(pos) = search_result { + Some(match *cs { + CharSearch::Backward(_) => pos, + CharSearch::BackwardAfter(c) => pos + c.len_utf8(), + CharSearch::Forward(_) => shift + pos, + CharSearch::ForwardBefore(_) => { + shift + pos - self.buf[..shift + pos].chars().next_back().unwrap().len_utf8() + } + }) + } else { + None + } + } + + pub fn move_to(&mut self, cs: CharSearch) -> bool { + if let Some(pos) = self.search_char_pos(&cs) { + self.pos = pos; + true + } else { + false + } + } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. pub fn delete_word(&mut self, at: At, word_def: Word) -> Option { @@ -396,6 +443,28 @@ impl LineBuffer { } } + pub fn delete_to(&mut self, cs: CharSearch) -> Option { + let search_result = match cs { + CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c)), + _ => self.search_char_pos(&cs), + }; + if let Some(pos) = search_result { + let chunk = match cs { + CharSearch::Backward(_) | + CharSearch::BackwardAfter(_) => { + let end = self.pos; + self.pos = pos; + self.buf.drain(pos..end).collect() + } + CharSearch::ForwardBefore(_) => self.buf.drain(self.pos..pos).collect(), + CharSearch::Forward(c) => self.buf.drain(self.pos..pos + c.len_utf8()).collect(), + }; + Some(chunk) + } else { + None + } + } + /// Alter the next word. pub fn edit_word(&mut self, a: WordAction) -> bool { if let Some((start, end)) = self.next_word_pos(self.pos, Word::Emacs) { From de72f719d67e23bc0dd34ccf62abe2d66ea233a2 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 19 Dec 2016 20:53:56 +0100 Subject: [PATCH 0245/1201] Vi char search and delete to --- src/keymap.rs | 11 +++---- src/lib.rs | 28 +++++++++++++++++- src/line_buffer.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 104 insertions(+), 8 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index bd67f4b00d..ab9cc1aa81 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -43,8 +43,8 @@ pub enum Cmd { UnixLikeDiscard, // UnixWordRubout, // = BackwardKillWord(Word::Big) UpcaseWord, - ViCharSearch(CharSearch), // TODO - ViKillTo(i32, CharSearch), // TODO + ViCharSearch(i32, CharSearch), + ViDeleteTo(i32, CharSearch), Yank(i32), YankPop, } @@ -218,7 +218,8 @@ impl EditState { Cmd::KillLine } KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), - KeyPress::Char('D') | KeyPress::Ctrl('K') => Cmd::KillLine, + KeyPress::Char('D') | + KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Char('e') => Cmd::ForwardWord(self.num_args(), At::End, Word::Vi), KeyPress::Char('E') => Cmd::ForwardWord(self.num_args(), At::End, Word::Big), KeyPress::Char('i') => { @@ -235,7 +236,7 @@ impl EditState { // vi-char-search let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViCharSearch(cs), + Some(cs) => Cmd::ViCharSearch(self.num_args(), cs), None => Cmd::Unknown, } } @@ -336,7 +337,7 @@ impl EditState { KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViKillTo(self.num_args(), cs), + Some(cs) => Cmd::ViDeleteTo(self.num_args(), cs), None => Cmd::Unknown, } } diff --git a/src/lib.rs b/src/lib.rs index 4385fd504b..843c17865b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ use encode_unicode::CharExt; use completion::{Completer, longest_common_prefix}; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; -use keymap::{At, Cmd, EditState, Word}; +use keymap::{At, CharSearch, Cmd, EditState, Word}; use kill_ring::{Mode, KillRing}; pub use config::{CompletionType, Config, EditMode, HistoryDuplicates}; @@ -451,6 +451,14 @@ fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word) -> Result<()> { } } +fn edit_move_to(s: &mut State, cs: CharSearch) -> Result<()> { + if s.line.move_to(cs) { + s.refresh_line() + } else { + Ok(()) + } +} + /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result> { if let Some(text) = s.line.delete_word(at, word_def) { @@ -461,6 +469,15 @@ fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result Result> { + if let Some(text) = s.line.delete_to(cs) { + try!(s.refresh_line()); + Ok(Some(text)) + } else { + Ok(None) + } +} + fn edit_word(s: &mut State, a: WordAction) -> Result<()> { if s.line.edit_word(a) { s.refresh_line() @@ -999,6 +1016,15 @@ fn readline_edit(prompt: &str, try!(edit_yank_pop(&mut s, yank_size, text)) } } + Cmd::ViCharSearch(_, cs) => { + editor.kill_ring.reset(); + try!(edit_move_to(&mut s, cs)) + } + Cmd::ViDeleteTo(_, cs) => { + if let Some(text) = try!(edit_delete_to(&mut s, cs)) { + editor.kill_ring.kill(&text, Mode::Append) + } + } Cmd::Interrupt => { editor.kill_ring.reset(); return Err(error::ReadlineError::Interrupted); diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 2ae61d9064..29435d2d74 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,6 +1,6 @@ //! Line buffer with current cursor position use std::ops::Deref; -use keymap::{At, Word}; +use keymap::{At, CharSearch, Word}; /// Maximum buffer size for the line read pub static MAX_LINE: usize = 4096; @@ -384,7 +384,54 @@ impl LineBuffer { } } - // TODO move_to (Vi mode: f|F|t|T) + fn search_char_pos(&mut self, cs: &CharSearch) -> Option { + let mut shift = 0; + let search_result = match *cs { + CharSearch::Backward(c) | + CharSearch::BackwardAfter(c) => { + self.buf[..self.pos].rfind(c) + // if let Some(pc) = self.char_before_cursor() { + // self.buf[..self.pos - pc.len_utf8()].rfind(c) + // } else { + // None + // } + } + CharSearch::Forward(c) | + CharSearch::ForwardBefore(c) => { + if let Some(cc) = self.char_at_cursor() { + shift = self.pos + cc.len_utf8(); + if shift < self.buf.len() { + self.buf[shift..].find(c) + } else { + None + } + } else { + None + } + } + }; + if let Some(pos) = search_result { + Some(match *cs { + CharSearch::Backward(_) => pos, + CharSearch::BackwardAfter(c) => pos + c.len_utf8(), + CharSearch::Forward(_) => shift + pos, + CharSearch::ForwardBefore(_) => { + shift + pos - self.buf[..shift + pos].chars().next_back().unwrap().len_utf8() + } + }) + } else { + None + } + } + + pub fn move_to(&mut self, cs: CharSearch) -> bool { + if let Some(pos) = self.search_char_pos(&cs) { + self.pos = pos; + true + } else { + false + } + } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. pub fn delete_word(&mut self, at: At, word_def: Word) -> Option { @@ -396,6 +443,28 @@ impl LineBuffer { } } + pub fn delete_to(&mut self, cs: CharSearch) -> Option { + let search_result = match cs { + CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c)), + _ => self.search_char_pos(&cs), + }; + if let Some(pos) = search_result { + let chunk = match cs { + CharSearch::Backward(_) | + CharSearch::BackwardAfter(_) => { + let end = self.pos; + self.pos = pos; + self.buf.drain(pos..end).collect() + } + CharSearch::ForwardBefore(_) => self.buf.drain(self.pos..pos).collect(), + CharSearch::Forward(c) => self.buf.drain(self.pos..pos + c.len_utf8()).collect(), + }; + Some(chunk) + } else { + None + } + } + /// Alter the next word. pub fn edit_word(&mut self, a: WordAction) -> bool { if let Some((start, end)) = self.next_word_pos(self.pos, Word::Emacs) { From 12d59575dd444aa6df7350856fcbf2aaa4d4d3b8 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 20 Dec 2016 20:10:38 +0100 Subject: [PATCH 0246/1201] Vi put before/after --- src/keymap.rs | 14 ++++++++++---- src/lib.rs | 12 ++++++------ src/line_buffer.rs | 9 ++++++--- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index ab9cc1aa81..8909e7d35c 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -45,7 +45,7 @@ pub enum Cmd { UpcaseWord, ViCharSearch(i32, CharSearch), ViDeleteTo(i32, CharSearch), - Yank(i32), + Yank(i32, Anchor), YankPop, } @@ -65,6 +65,12 @@ pub enum At { End, } +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum Anchor { + After, + Before, +} + #[derive(Debug, Clone, PartialEq)] pub enum CharSearch { Forward(char), @@ -241,8 +247,8 @@ impl EditState { } } // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank(self.num_args()), // vi-put FIXME cursor at end - KeyPress::Char('P') => Cmd::Yank(self.num_args()), // vi-put TODO Insert the yanked text before the cursor. + KeyPress::Char('p') => Cmd::Yank(self.num_args(), Anchor::After), // vi-put + KeyPress::Char('P') => Cmd::Yank(self.num_args(), Anchor::Before), // vi-put KeyPress::Char('r') => { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); @@ -392,7 +398,7 @@ impl EditState { KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('V') => Cmd::QuotedInsert, KeyPress::Ctrl('W') => Cmd::BackwardKillWord(self.num_args(), Word::Big), - KeyPress::Ctrl('Y') => Cmd::Yank(self.num_args()), + KeyPress::Ctrl('Y') => Cmd::Yank(self.num_args(), Anchor::Before), KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, diff --git a/src/lib.rs b/src/lib.rs index 1a9dbebed0..78d8a78db8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ use tty::{RawMode, RawReader, Terminal, Term}; use completion::{Completer, longest_common_prefix}; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; -use keymap::{At, CharSearch, Cmd, EditState, Word}; +use keymap::{Anchor, At, CharSearch, Cmd, EditState, Word}; use kill_ring::{Mode, KillRing}; pub use config::{CompletionType, Config, EditMode, HistoryDuplicates}; @@ -328,8 +328,8 @@ fn edit_insert(s: &mut State, ch: char) -> Result<()> { } // Yank/paste `text` at current position. -fn edit_yank(s: &mut State, text: &str) -> Result<()> { - if s.line.yank(text).is_some() { +fn edit_yank(s: &mut State, text: &str, anchor: Anchor) -> Result<()> { + if s.line.yank(text, anchor).is_some() { s.refresh_line() } else { Ok(()) @@ -339,7 +339,7 @@ fn edit_yank(s: &mut State, text: &str) -> Result<()> { // Delete previously yanked text and yank/paste `text` at current position. fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> { s.line.yank_pop(yank_size, text); - edit_yank(s, text) + edit_yank(s, text, Anchor::Before) } /// Move cursor on the left. @@ -947,10 +947,10 @@ fn readline_edit(prompt: &str, let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c)) // FIXME } - Cmd::Yank(_) => { + Cmd::Yank(_, anchor) => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { - try!(edit_yank(&mut s, text)) + try!(edit_yank(&mut s, text, anchor)) } } // TODO CTRL-_ // undo diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 29435d2d74..ce080cabfa 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,6 +1,6 @@ //! Line buffer with current cursor position use std::ops::Deref; -use keymap::{At, CharSearch, Word}; +use keymap::{Anchor, At, CharSearch, Word}; /// Maximum buffer size for the line read pub static MAX_LINE: usize = 4096; @@ -126,11 +126,14 @@ impl LineBuffer { /// Yank/paste `text` at current position. /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. - pub fn yank(&mut self, text: &str) -> Option { + pub fn yank(&mut self, text: &str, anchor: Anchor) -> Option { let shift = text.len(); if text.is_empty() || (self.buf.len() + shift) > self.buf.capacity() { return None; } + if let Anchor::After = anchor { + self.move_right(); + } let pos = self.pos; let push = self.insert_str(pos, text); self.pos += shift; @@ -141,7 +144,7 @@ impl LineBuffer { pub fn yank_pop(&mut self, yank_size: usize, text: &str) -> Option { self.buf.drain((self.pos - yank_size)..self.pos); self.pos -= yank_size; - self.yank(text) + self.yank(text, Anchor::Before) } /// Move cursor on the left. From cb1bd8bdf50b597184e3780b6ac861063ab4495a Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 20 Dec 2016 20:10:38 +0100 Subject: [PATCH 0247/1201] Vi put before/after --- src/keymap.rs | 14 ++++++++++---- src/lib.rs | 12 ++++++------ src/line_buffer.rs | 9 ++++++--- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index ab9cc1aa81..8909e7d35c 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -45,7 +45,7 @@ pub enum Cmd { UpcaseWord, ViCharSearch(i32, CharSearch), ViDeleteTo(i32, CharSearch), - Yank(i32), + Yank(i32, Anchor), YankPop, } @@ -65,6 +65,12 @@ pub enum At { End, } +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum Anchor { + After, + Before, +} + #[derive(Debug, Clone, PartialEq)] pub enum CharSearch { Forward(char), @@ -241,8 +247,8 @@ impl EditState { } } // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank(self.num_args()), // vi-put FIXME cursor at end - KeyPress::Char('P') => Cmd::Yank(self.num_args()), // vi-put TODO Insert the yanked text before the cursor. + KeyPress::Char('p') => Cmd::Yank(self.num_args(), Anchor::After), // vi-put + KeyPress::Char('P') => Cmd::Yank(self.num_args(), Anchor::Before), // vi-put KeyPress::Char('r') => { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); @@ -392,7 +398,7 @@ impl EditState { KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('V') => Cmd::QuotedInsert, KeyPress::Ctrl('W') => Cmd::BackwardKillWord(self.num_args(), Word::Big), - KeyPress::Ctrl('Y') => Cmd::Yank(self.num_args()), + KeyPress::Ctrl('Y') => Cmd::Yank(self.num_args(), Anchor::Before), KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, diff --git a/src/lib.rs b/src/lib.rs index 843c17865b..8955ff57de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ use encode_unicode::CharExt; use completion::{Completer, longest_common_prefix}; use history::{Direction, History}; use line_buffer::{LineBuffer, MAX_LINE, WordAction}; -use keymap::{At, CharSearch, Cmd, EditState, Word}; +use keymap::{Anchor, At, CharSearch, Cmd, EditState, Word}; use kill_ring::{Mode, KillRing}; pub use config::{CompletionType, Config, EditMode, HistoryDuplicates}; @@ -326,8 +326,8 @@ fn edit_insert(s: &mut State, ch: char) -> Result<()> { } // Yank/paste `text` at current position. -fn edit_yank(s: &mut State, text: &str) -> Result<()> { - if s.line.yank(text).is_some() { +fn edit_yank(s: &mut State, text: &str, anchor: Anchor) -> Result<()> { + if s.line.yank(text, anchor).is_some() { s.refresh_line() } else { Ok(()) @@ -337,7 +337,7 @@ fn edit_yank(s: &mut State, text: &str) -> Result<()> { // Delete previously yanked text and yank/paste `text` at current position. fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> { s.line.yank_pop(yank_size, text); - edit_yank(s, text) + edit_yank(s, text, Anchor::Before) } /// Move cursor on the left. @@ -945,10 +945,10 @@ fn readline_edit(prompt: &str, let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c)) // FIXME } - Cmd::Yank(_) => { + Cmd::Yank(_, anchor) => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { - try!(edit_yank(&mut s, text)) + try!(edit_yank(&mut s, text, anchor)) } } // TODO CTRL-_ // undo diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 29435d2d74..ce080cabfa 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,6 +1,6 @@ //! Line buffer with current cursor position use std::ops::Deref; -use keymap::{At, CharSearch, Word}; +use keymap::{Anchor, At, CharSearch, Word}; /// Maximum buffer size for the line read pub static MAX_LINE: usize = 4096; @@ -126,11 +126,14 @@ impl LineBuffer { /// Yank/paste `text` at current position. /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. - pub fn yank(&mut self, text: &str) -> Option { + pub fn yank(&mut self, text: &str, anchor: Anchor) -> Option { let shift = text.len(); if text.is_empty() || (self.buf.len() + shift) > self.buf.capacity() { return None; } + if let Anchor::After = anchor { + self.move_right(); + } let pos = self.pos; let push = self.insert_str(pos, text); self.pos += shift; @@ -141,7 +144,7 @@ impl LineBuffer { pub fn yank_pop(&mut self, yank_size: usize, text: &str) -> Option { self.buf.drain((self.pos - yank_size)..self.pos); self.pos -= yank_size; - self.yank(text) + self.yank(text, Anchor::Before) } /// Move cursor on the left. From 783cfe2e470b03221e0031521dd35652f2d72215 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 21 Dec 2016 21:15:39 +0100 Subject: [PATCH 0248/1201] Ensure tests do not write to stdout --- src/lib.rs | 2 +- src/tty/mod.rs | 3 +++ src/tty/test.rs | 6 +++++- src/tty/unix.rs | 7 ++++++- src/tty/windows.rs | 8 ++++++-- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 78d8a78db8..bc50d75d41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -813,7 +813,7 @@ fn readline_edit(prompt: &str, -> Result { let completer = editor.completer.as_ref().map(|c| c as &Completer); - let mut stdout = io::stdout(); + let mut stdout = editor.term.create_writer(); editor.kill_ring.reset(); let mut s = State::new(&mut stdout, diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 4a0475220e..2c4c4e3291 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -19,6 +19,7 @@ pub trait RawReader: Sized { /// Terminal contract pub trait Term: Clone { type Reader: RawReader; + type Writer: Write; type Mode; fn new() -> Self; @@ -36,6 +37,8 @@ pub trait Term: Clone { fn enable_raw_mode(&self) -> Result; /// Create a RAW reader fn create_reader(&self) -> Result; + /// Create a writer + fn create_writer(&self) -> Self::Writer; /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, w: &mut Write) -> Result<()>; } diff --git a/src/tty/test.rs b/src/tty/test.rs index 19ce23ba5b..efaf950d2f 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -1,5 +1,5 @@ //! Tests specific definitions -use std::io::Write; +use std::io::{self, Sink, Write}; use std::iter::IntoIterator; use std::slice::Iter; use std::vec::IntoIter; @@ -90,6 +90,7 @@ impl DummyTerminal { impl Term for DummyTerminal { type Reader = IntoIter; + type Writer = Sink; type Mode = Mode; fn new() -> DummyTerminal { @@ -134,6 +135,9 @@ impl Term for DummyTerminal { Ok(self.keys.clone().into_iter()) } + fn create_writer(&self) -> Sink { + io::sink() + } /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, _: &mut Write) -> Result<()> { diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 183ad2c54a..bdbbb95959 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,6 +1,6 @@ //! Unix specific definitions use std; -use std::io::{self, Chars, Read, Write}; +use std::io::{self, Chars, Read, Stdout, Write}; use std::sync; use std::sync::atomic; use libc; @@ -230,6 +230,7 @@ pub struct PosixTerminal { impl Term for PosixTerminal { type Reader = PosixRawReader; + type Writer = Stdout; type Mode = Mode; fn new() -> PosixTerminal { @@ -299,6 +300,10 @@ impl Term for PosixTerminal { PosixRawReader::new() } + fn create_writer(&self) -> Stdout { + io::stdout() + } + /// Check if a SIGWINCH signal has been received fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 44894ebb5a..5f07bf63e1 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,6 +1,5 @@ //! Windows specific definitions -use std::io; -use std::io::Write; +use std::io::{self, Stdout, Write}; use std::mem; use std::sync::atomic; @@ -220,6 +219,7 @@ impl Console { impl Term for Console { type Reader = ConsoleRawReader; + type Writer = Stdout; type Mode = Mode; fn new() -> Console { @@ -296,6 +296,10 @@ impl Term for Console { ConsoleRawReader::new() } + fn create_writer(&self) -> Stdout { + io::stdout() + } + fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } From 0e23b9542904b5abec8d08349f6736bee196417d Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 26 Dec 2016 19:12:19 +0100 Subject: [PATCH 0249/1201] Unicode feature https://github.com/rust-lang/rust/issues/27784 --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index bc50d75d41..7085628fb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,6 @@ //! } //! ``` #![feature(io)] -#![feature(unicode)] #![allow(unknown_lints)] extern crate libc; From 7acf1704e56003549e6bbf101d5925cfef01a80b Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 28 Dec 2016 19:00:49 +0100 Subject: [PATCH 0250/1201] Use unstable 'insert_str' feature --- src/lib.rs | 1 + src/line_buffer.rs | 21 +-------------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7085628fb3..1362e88632 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ //! } //! ``` #![feature(io)] +#![feature(insert_str)] #![allow(unknown_lints)] extern crate libc; diff --git a/src/line_buffer.rs b/src/line_buffer.rs index ce080cabfa..aa81550fad 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -547,7 +547,7 @@ impl LineBuffer { self.buf.push_str(s); true } else { - insert_str(&mut self.buf, idx, s); + self.buf.insert_str(idx, s); false } } @@ -561,25 +561,6 @@ impl Deref for LineBuffer { } } -fn insert_str(buf: &mut String, idx: usize, s: &str) { - use std::ptr; - - let len = buf.len(); - assert!(idx <= len); - assert!(buf.is_char_boundary(idx)); - let amt = s.len(); - buf.reserve(amt); - - unsafe { - let v = buf.as_mut_vec(); - ptr::copy(v.as_ptr().offset(idx as isize), - v.as_mut_ptr().offset((idx + amt) as isize), - len - idx); - ptr::copy_nonoverlapping(s.as_ptr(), v.as_mut_ptr().offset(idx as isize), amt); - v.set_len(len + amt); - } -} - fn is_break_char(word_def: Word) -> fn(&char) -> bool { match word_def { Word::Emacs => is_not_alphanumeric, From b42683c6c81556ad00bb081376dc215fd10613bf Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 28 Dec 2016 21:29:48 +0100 Subject: [PATCH 0251/1201] Partial support to repeated commands --- src/keymap.rs | 217 +++++++++++++++++++++++++++++++++------------ src/lib.rs | 106 +++++++++++----------- src/line_buffer.rs | 154 +++++++++++++++++++------------- 3 files changed, 307 insertions(+), 170 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 8909e7d35c..a59313a5ef 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -8,32 +8,32 @@ use super::Result; pub enum Cmd { Abort, // Miscellaneous Command AcceptLine, - BackwardChar(i32), - BackwardDeleteChar(i32), - BackwardKillWord(i32, Word), // Backward until start of word - BackwardWord(i32, Word), // Backward until start of word + BackwardChar(u16), + BackwardDeleteChar(u16), + BackwardKillWord(u16, Word), // Backward until start of word + BackwardWord(u16, Word), // Backward until start of word BeginningOfHistory, BeginningOfLine, CapitalizeWord, ClearScreen, Complete, - DeleteChar(i32), + DeleteChar(u16), DowncaseWord, EndOfFile, EndOfHistory, EndOfLine, - ForwardChar(i32), + ForwardChar(u16), ForwardSearchHistory, - ForwardWord(i32, At, Word), // Forward until start/end of word + ForwardWord(u16, At, Word), // Forward until start/end of word Interrupt, KillLine, KillWholeLine, - KillWord(i32, At, Word), // Forward until start/end of word + KillWord(u16, At, Word), // Forward until start/end of word NextHistory, Noop, PreviousHistory, QuotedInsert, - Replace(i32, char), // TODO DeleteChar + SelfInsert + Replace(u16, char), // TODO DeleteChar + SelfInsert ReverseSearchHistory, SelfInsert(char), Suspend, @@ -43,9 +43,9 @@ pub enum Cmd { UnixLikeDiscard, // UnixWordRubout, // = BackwardKillWord(Word::Big) UpcaseWord, - ViCharSearch(i32, CharSearch), - ViDeleteTo(i32, CharSearch), - Yank(i32, Anchor), + ViCharSearch(u16, CharSearch), + ViDeleteTo(u16, CharSearch), + Yank(u16, Anchor), YankPop, } @@ -86,7 +86,7 @@ pub struct EditState { // Vi Command/Alternate, Insert/Input mode insert: bool, // vi only ? // numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 - num_args: i32, + num_args: i16, } impl EditState { @@ -117,7 +117,7 @@ impl EditState { -> Result { match digit { '0'...'9' => { - self.num_args = digit.to_digit(10).unwrap() as i32; + self.num_args = digit.to_digit(10).unwrap() as i16; } '-' => { self.num_args = -1; @@ -129,7 +129,7 @@ impl EditState { match key { KeyPress::Char(digit @ '0'...'9') | KeyPress::Meta(digit @ '0'...'9') => { - self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i16; } _ => return Ok(key), }; @@ -146,26 +146,75 @@ impl EditState { let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), KeyPress::Ctrl('A') => Cmd::BeginningOfLine, - KeyPress::Ctrl('B') => Cmd::BackwardChar(self.num_args()), + KeyPress::Ctrl('B') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardChar(count) + } else { + Cmd::ForwardChar(count) + } + } KeyPress::Ctrl('E') => Cmd::EndOfLine, - KeyPress::Ctrl('F') => Cmd::ForwardChar(self.num_args()), + KeyPress::Ctrl('F') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::ForwardChar(count) + } else { + Cmd::BackwardChar(count) + } + } KeyPress::Ctrl('G') | KeyPress::Esc => Cmd::Abort, KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), + KeyPress::Backspace => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardDeleteChar(count) + } else { + Cmd::DeleteChar(count) + } + } KeyPress::Tab => Cmd::Complete, KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Ctrl('N') => Cmd::NextHistory, KeyPress::Ctrl('P') => Cmd::PreviousHistory, KeyPress::Meta('\x08') | - KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(self.num_args(), Word::Emacs), + KeyPress::Meta('\x7f') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardKillWord(count, Word::Emacs) + } else { + Cmd::KillWord(count, At::End, Word::Emacs) + } + } KeyPress::Meta('<') => Cmd::BeginningOfHistory, KeyPress::Meta('>') => Cmd::EndOfHistory, - KeyPress::Meta('B') => Cmd::BackwardWord(self.num_args(), Word::Emacs), + KeyPress::Meta('B') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardWord(count, Word::Emacs) + } else { + Cmd::ForwardWord(count, At::End, Word::Emacs) + } + } KeyPress::Meta('C') => Cmd::CapitalizeWord, - KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), At::End, Word::Emacs), - KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), At::End, Word::Emacs), + KeyPress::Meta('D') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::KillWord(count, At::End, Word::Emacs) + } else { + Cmd::BackwardKillWord(count, Word::Emacs) + } + } + KeyPress::Meta('F') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::ForwardWord(count, At::End, Word::Emacs) + } else { + Cmd::BackwardWord(count, Word::Emacs) + } + } KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, @@ -180,12 +229,12 @@ impl EditState { config: &Config, digit: char) -> Result { - self.num_args = digit.to_digit(10).unwrap() as i32; + self.num_args = digit.to_digit(10).unwrap() as i16; loop { let key = try!(rdr.next_key(config.keyseq_timeout())); match key { KeyPress::Char(digit @ '0'...'9') => { - self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i16; } _ => return Ok(key), }; @@ -206,15 +255,15 @@ impl EditState { KeyPress::Char('a') => { // vi-append-mode: Vi enter insert mode after the cursor. self.insert = true; - Cmd::ForwardChar(self.num_args()) + Cmd::ForwardChar(self.vi_num_args()) } KeyPress::Char('A') => { // vi-append-eol: Vi enter insert mode at end of line. self.insert = true; Cmd::EndOfLine } - KeyPress::Char('b') => Cmd::BackwardWord(self.num_args(), Word::Vi), // vi-prev-word - KeyPress::Char('B') => Cmd::BackwardWord(self.num_args(), Word::Big), + KeyPress::Char('b') => Cmd::BackwardWord(self.vi_num_args(), Word::Vi), // vi-prev-word + KeyPress::Char('B') => Cmd::BackwardWord(self.vi_num_args(), Word::Big), KeyPress::Char('c') => { self.insert = true; try!(self.vi_delete_motion(rdr, config, key)) @@ -226,8 +275,8 @@ impl EditState { KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), KeyPress::Char('D') | KeyPress::Ctrl('K') => Cmd::KillLine, - KeyPress::Char('e') => Cmd::ForwardWord(self.num_args(), At::End, Word::Vi), - KeyPress::Char('E') => Cmd::ForwardWord(self.num_args(), At::End, Word::Big), + KeyPress::Char('e') => Cmd::ForwardWord(self.vi_num_args(), At::End, Word::Vi), + KeyPress::Char('E') => Cmd::ForwardWord(self.vi_num_args(), At::End, Word::Big), KeyPress::Char('i') => { // vi-insertion-mode self.insert = true; @@ -242,18 +291,18 @@ impl EditState { // vi-char-search let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViCharSearch(self.num_args(), cs), + Some(cs) => Cmd::ViCharSearch(self.vi_num_args(), cs), None => Cmd::Unknown, } } // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank(self.num_args(), Anchor::After), // vi-put - KeyPress::Char('P') => Cmd::Yank(self.num_args(), Anchor::Before), // vi-put + KeyPress::Char('p') => Cmd::Yank(self.vi_num_args(), Anchor::After), // vi-put, FIXME cursor position + KeyPress::Char('P') => Cmd::Yank(self.vi_num_args(), Anchor::Before), // vi-put, FIXME cursor position KeyPress::Char('r') => { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); match ch { - KeyPress::Char(c) => Cmd::Replace(self.num_args(), c), + KeyPress::Char(c) => Cmd::Replace(self.vi_num_args(), c), KeyPress::Esc => Cmd::Noop, _ => Cmd::Unknown, } @@ -262,7 +311,7 @@ impl EditState { KeyPress::Char('s') => { // vi-substitute-char: Vi replace character under the cursor and enter insert mode. self.insert = true; - Cmd::DeleteChar(self.num_args()) + Cmd::DeleteChar(self.vi_num_args()) } KeyPress::Char('S') => { // vi-substitute-line: Vi substitute entire line. @@ -270,18 +319,18 @@ impl EditState { Cmd::KillWholeLine } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), At::Start, Word::Vi), // vi-next-word - KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), At::Start, Word::Big), // vi-next-word - KeyPress::Char('x') => Cmd::DeleteChar(self.num_args()), // vi-delete: TODO move backward if eol - KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.num_args()), // vi-rubout + KeyPress::Char('w') => Cmd::ForwardWord(self.vi_num_args(), At::Start, Word::Vi), // vi-next-word + KeyPress::Char('W') => Cmd::ForwardWord(self.vi_num_args(), At::Start, Word::Big), // vi-next-word + KeyPress::Char('x') => Cmd::DeleteChar(self.vi_num_args()), // vi-delete: TODO move backward if eol + KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.vi_num_args()), // vi-rubout // KeyPress::Char('y') => Cmd::???, // vi-yank-to // KeyPress::Char('Y') => Cmd::???, // vi-yank-to KeyPress::Char('h') | KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardChar(self.num_args()), // TODO Validate + KeyPress::Backspace => Cmd::BackwardChar(self.vi_num_args()), // TODO Validate KeyPress::Ctrl('G') => Cmd::Abort, KeyPress::Char('l') | - KeyPress::Char(' ') => Cmd::ForwardChar(self.num_args()), + KeyPress::Char(' ') => Cmd::ForwardChar(self.vi_num_args()), KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Char('+') | KeyPress::Char('j') | @@ -336,24 +385,24 @@ impl EditState { Ok(match mvt { KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. - KeyPress::Char('b') => Cmd::BackwardKillWord(self.num_args(), Word::Vi), - KeyPress::Char('B') => Cmd::BackwardKillWord(self.num_args(), Word::Big), - KeyPress::Char('e') => Cmd::KillWord(self.num_args(), At::End, Word::Vi), - KeyPress::Char('E') => Cmd::KillWord(self.num_args(), At::End, Word::Big), + KeyPress::Char('b') => Cmd::BackwardKillWord(self.vi_num_args(), Word::Vi), + KeyPress::Char('B') => Cmd::BackwardKillWord(self.vi_num_args(), Word::Big), + KeyPress::Char('e') => Cmd::KillWord(self.vi_num_args(), At::End, Word::Vi), + KeyPress::Char('E') => Cmd::KillWord(self.vi_num_args(), At::End, Word::Big), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViDeleteTo(self.num_args(), cs), + Some(cs) => Cmd::ViDeleteTo(self.vi_num_args(), cs), None => Cmd::Unknown, } } KeyPress::Char('h') | KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Backspace => Cmd::BackwardDeleteChar(self.vi_num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). KeyPress::Char('l') | - KeyPress::Char(' ') => Cmd::DeleteChar(self.num_args()), - KeyPress::Char('w') => Cmd::KillWord(self.num_args(), At::Start, Word::Vi), - KeyPress::Char('W') => Cmd::KillWord(self.num_args(), At::Start, Word::Big), + KeyPress::Char(' ') => Cmd::DeleteChar(self.vi_num_args()), + KeyPress::Char('w') => Cmd::KillWord(self.vi_num_args(), At::Start, Word::Vi), + KeyPress::Char('W') => Cmd::KillWord(self.vi_num_args(), At::Start, Word::Big), _ => Cmd::Unknown, }) } @@ -381,12 +430,33 @@ impl EditState { fn common(&mut self, key: KeyPress) -> Cmd { match key { KeyPress::Home => Cmd::BeginningOfLine, - KeyPress::Left => Cmd::BackwardChar(self.num_args()), + KeyPress::Left => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardChar(count) + } else { + Cmd::ForwardChar(count) + } + } KeyPress::Ctrl('C') => Cmd::Interrupt, KeyPress::Ctrl('D') => Cmd::EndOfFile, - KeyPress::Delete => Cmd::DeleteChar(self.num_args()), + KeyPress::Delete => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::DeleteChar(count) + } else { + Cmd::BackwardDeleteChar(count) + } + } KeyPress::End => Cmd::EndOfLine, - KeyPress::Right => Cmd::ForwardChar(self.num_args()), + KeyPress::Right => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::ForwardChar(count) + } else { + Cmd::BackwardChar(count) + } + } KeyPress::Ctrl('J') | KeyPress::Enter => Cmd::AcceptLine, KeyPress::Down => Cmd::NextHistory, @@ -397,15 +467,28 @@ impl EditState { KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::BackwardKillWord(self.num_args(), Word::Big), - KeyPress::Ctrl('Y') => Cmd::Yank(self.num_args(), Anchor::Before), + KeyPress::Ctrl('W') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardKillWord(count, Word::Big) + } else { + Cmd::KillWord(count, At::End, Word::Big) + } + } + KeyPress::Ctrl('Y') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::Yank(count, Anchor::Before) + } else { + Cmd::Unknown // TODO Validate + } + } KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, } } - - fn num_args(&mut self) -> i32 { + fn num_args(&mut self) -> i16 { let num_args = match self.num_args { 0 => 1, _ => self.num_args, @@ -413,4 +496,26 @@ impl EditState { self.num_args = 0; num_args } + + fn emacs_num_args(&mut self) -> (u16, bool) { + let num_args = self.num_args(); + if num_args < 0 { + if let (count, false) = num_args.overflowing_abs() { + (count as u16, false) + } else { + (u16::max_value(), false) + } + } else { + (num_args as u16, true) + } + } + + fn vi_num_args(&mut self) -> u16 { + let num_args = self.num_args(); + if num_args < 0 { + unreachable!() + } else { + num_args.abs() as u16 + } + } } diff --git a/src/lib.rs b/src/lib.rs index 1362e88632..6b20479b84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -306,7 +306,7 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { } /// Insert the character `ch` at cursor current position. -fn edit_insert(s: &mut State, ch: char) -> Result<()> { +fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { if let Some(push) = s.line.insert(ch) { if push { if s.cursor.col + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) < s.cols { @@ -328,8 +328,8 @@ fn edit_insert(s: &mut State, ch: char) -> Result<()> { } // Yank/paste `text` at current position. -fn edit_yank(s: &mut State, text: &str, anchor: Anchor) -> Result<()> { - if s.line.yank(text, anchor).is_some() { +fn edit_yank(s: &mut State, text: &str, anchor: Anchor, count: u16) -> Result<()> { + if s.line.yank(text, anchor, count).is_some() { s.refresh_line() } else { Ok(()) @@ -339,12 +339,12 @@ fn edit_yank(s: &mut State, text: &str, anchor: Anchor) -> Result<()> { // Delete previously yanked text and yank/paste `text` at current position. fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> { s.line.yank_pop(yank_size, text); - edit_yank(s, text, Anchor::Before) + edit_yank(s, text, Anchor::Before, 1) } /// Move cursor on the left. -fn edit_move_left(s: &mut State) -> Result<()> { - if s.line.move_left() { +fn edit_move_left(s: &mut State, count: u16) -> Result<()> { + if s.line.move_left(count) { s.refresh_line() } else { Ok(()) @@ -352,8 +352,8 @@ fn edit_move_left(s: &mut State) -> Result<()> { } /// Move cursor on the right. -fn edit_move_right(s: &mut State) -> Result<()> { - if s.line.move_right() { +fn edit_move_right(s: &mut State, count: u16) -> Result<()> { + if s.line.move_right(count) { s.refresh_line() } else { Ok(()) @@ -380,8 +380,8 @@ fn edit_move_end(s: &mut State) -> Result<()> { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. -fn edit_delete(s: &mut State) -> Result<()> { - if s.line.delete() { +fn edit_delete(s: &mut State, count: u16) -> Result<()> { + if s.line.delete(count) { s.refresh_line() } else { Ok(()) @@ -389,8 +389,8 @@ fn edit_delete(s: &mut State) -> Result<()> { } /// Backspace implementation. -fn edit_backspace(s: &mut State) -> Result<()> { - if s.line.backspace() { +fn edit_backspace(s: &mut State, count: u16) -> Result<()> { + if s.line.backspace(count) { s.refresh_line() } else { Ok(()) @@ -426,8 +426,8 @@ fn edit_transpose_chars(s: &mut State) -> Result<()> { } } -fn edit_move_to_prev_word(s: &mut State, word_def: Word) -> Result<()> { - if s.line.move_to_prev_word(word_def) { +fn edit_move_to_prev_word(s: &mut State, word_def: Word, count: u16) -> Result<()> { + if s.line.move_to_prev_word(word_def, count) { s.refresh_line() } else { Ok(()) @@ -436,8 +436,8 @@ fn edit_move_to_prev_word(s: &mut State, word_def: Word) -> Result<()> { /// Delete the previous word, maintaining the cursor at the start of the /// current word. -fn edit_delete_prev_word(s: &mut State, word_def: Word) -> Result> { - if let Some(text) = s.line.delete_prev_word(word_def) { +fn edit_delete_prev_word(s: &mut State, word_def: Word, count: u16) -> Result> { + if let Some(text) = s.line.delete_prev_word(word_def, count) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -445,16 +445,16 @@ fn edit_delete_prev_word(s: &mut State, word_def: Word) -> Result } } -fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word) -> Result<()> { - if s.line.move_to_next_word(at, word_def) { +fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word, count: u16) -> Result<()> { + if s.line.move_to_next_word(at, word_def, count) { s.refresh_line() } else { Ok(()) } } -fn edit_move_to(s: &mut State, cs: CharSearch) -> Result<()> { - if s.line.move_to(cs) { +fn edit_move_to(s: &mut State, cs: CharSearch, count: u16) -> Result<()> { + if s.line.move_to(cs, count) { s.refresh_line() } else { Ok(()) @@ -462,8 +462,8 @@ fn edit_move_to(s: &mut State, cs: CharSearch) -> Result<()> { } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. -fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result> { - if let Some(text) = s.line.delete_word(at, word_def) { +fn edit_delete_word(s: &mut State, at: At, word_def: Word, count: u16) -> Result> { + if let Some(text) = s.line.delete_word(at, word_def, count) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -471,8 +471,8 @@ fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result Result> { - if let Some(text) = s.line.delete_to(cs) { +fn edit_delete_to(s: &mut State, cs: CharSearch, count: u16) -> Result> { + if let Some(text) = s.line.delete_to(cs, count) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -842,7 +842,7 @@ fn readline_edit(prompt: &str, if let Cmd::SelfInsert(c) = cmd { editor.kill_ring.reset(); - try!(edit_insert(&mut s, c)); + try!(edit_insert(&mut s, c, 1)); continue; } @@ -863,28 +863,28 @@ fn readline_edit(prompt: &str, // Move to the beginning of line. try!(edit_move_home(&mut s)) } - Cmd::BackwardChar(_) => { + Cmd::BackwardChar(count) => { editor.kill_ring.reset(); // Move back a character. - try!(edit_move_left(&mut s)) + try!(edit_move_left(&mut s, count)) } - Cmd::DeleteChar(_) => { + Cmd::DeleteChar(count) => { editor.kill_ring.reset(); // Delete (forward) one character at point. - try!(edit_delete(&mut s)) + try!(edit_delete(&mut s, count)) } - Cmd::Replace(_, c) => { + Cmd::Replace(count, c) => { editor.kill_ring.reset(); - try!(edit_delete(&mut s)); - try!(edit_insert(&mut s, c)); - try!(edit_move_left(&mut s)) + try!(edit_delete(&mut s, count)); + try!(edit_insert(&mut s, c, count)); + try!(edit_move_left(&mut s, 1)) } Cmd::EndOfFile => { editor.kill_ring.reset(); if !s.edit_state.is_emacs_mode() || s.line.is_empty() { return Err(error::ReadlineError::Eof); } else { - try!(edit_delete(&mut s)) + try!(edit_delete(&mut s, 1)) } } Cmd::EndOfLine => { @@ -892,15 +892,15 @@ fn readline_edit(prompt: &str, // Move to the end of line. try!(edit_move_end(&mut s)) } - Cmd::ForwardChar(_) => { + Cmd::ForwardChar(count) => { editor.kill_ring.reset(); // Move forward a character. - try!(edit_move_right(&mut s)) + try!(edit_move_right(&mut s, count)) } - Cmd::BackwardDeleteChar(_) => { + Cmd::BackwardDeleteChar(count) => { editor.kill_ring.reset(); // Delete one character backward. - try!(edit_backspace(&mut s)) + try!(edit_backspace(&mut s, count)) } Cmd::KillLine => { // Kill the text from point to the end of the line. @@ -945,12 +945,12 @@ fn readline_edit(prompt: &str, // Quoted insert editor.kill_ring.reset(); let c = try!(rdr.next_char()); - try!(edit_insert(&mut s, c)) // FIXME + try!(edit_insert(&mut s, c, 1)) // FIXME } - Cmd::Yank(_, anchor) => { + Cmd::Yank(count, anchor) => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { - try!(edit_yank(&mut s, text, anchor)) + try!(edit_yank(&mut s, text, anchor, count)) } } // TODO CTRL-_ // undo @@ -960,9 +960,9 @@ fn readline_edit(prompt: &str, try!(edit_move_end(&mut s)); break; } - Cmd::BackwardKillWord(_, word_def) => { + Cmd::BackwardKillWord(count, word_def) => { // kill one word backward - if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def)) { + if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def, count)) { editor.kill_ring.kill(&text, Mode::Prepend) } } @@ -976,26 +976,26 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, false)) } - Cmd::BackwardWord(_, word_def) => { + Cmd::BackwardWord(count, word_def) => { // move backwards one word editor.kill_ring.reset(); - try!(edit_move_to_prev_word(&mut s, word_def)) + try!(edit_move_to_prev_word(&mut s, word_def, count)) } Cmd::CapitalizeWord => { // capitalize word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - Cmd::KillWord(_, at, word_def) => { + Cmd::KillWord(count, at, word_def) => { // kill one word forward - if let Some(text) = try!(edit_delete_word(&mut s, at, word_def)) { + if let Some(text) = try!(edit_delete_word(&mut s, at, word_def, count)) { editor.kill_ring.kill(&text, Mode::Append) } } - Cmd::ForwardWord(_, at, word_def) => { + Cmd::ForwardWord(count, at, word_def) => { // move forwards one word editor.kill_ring.reset(); - try!(edit_move_to_next_word(&mut s, at, word_def)) + try!(edit_move_to_next_word(&mut s, at, word_def, count)) } Cmd::DowncaseWord => { // lowercase word after point @@ -1018,12 +1018,12 @@ fn readline_edit(prompt: &str, try!(edit_yank_pop(&mut s, yank_size, text)) } } - Cmd::ViCharSearch(_, cs) => { + Cmd::ViCharSearch(count, cs) => { editor.kill_ring.reset(); - try!(edit_move_to(&mut s, cs)) + try!(edit_move_to(&mut s, cs, count)) } - Cmd::ViDeleteTo(_, cs) => { - if let Some(text) = try!(edit_delete_to(&mut s, cs)) { + Cmd::ViDeleteTo(count, cs) => { + if let Some(text) = try!(edit_delete_to(&mut s, cs, count)) { editor.kill_ring.kill(&text, Mode::Append) } } diff --git a/src/line_buffer.rs b/src/line_buffer.rs index aa81550fad..47bd13c59f 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -126,13 +126,13 @@ impl LineBuffer { /// Yank/paste `text` at current position. /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. - pub fn yank(&mut self, text: &str, anchor: Anchor) -> Option { + pub fn yank(&mut self, text: &str, anchor: Anchor, count: u16) -> Option { let shift = text.len(); if text.is_empty() || (self.buf.len() + shift) > self.buf.capacity() { return None; } if let Anchor::After = anchor { - self.move_right(); + self.move_right(1); } let pos = self.pos; let push = self.insert_str(pos, text); @@ -144,27 +144,35 @@ impl LineBuffer { pub fn yank_pop(&mut self, yank_size: usize, text: &str) -> Option { self.buf.drain((self.pos - yank_size)..self.pos); self.pos -= yank_size; - self.yank(text, Anchor::Before) + self.yank(text, Anchor::Before, 1) } /// Move cursor on the left. - pub fn move_left(&mut self) -> bool { - if let Some(ch) = self.char_before_cursor() { - self.pos -= ch.len_utf8(); - true - } else { - false + pub fn move_left(&mut self, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(ch) = self.char_before_cursor() { + self.pos -= ch.len_utf8(); + moved = true + } else { + break; + } } + moved } /// Move cursor on the right. - pub fn move_right(&mut self) -> bool { - if let Some(ch) = self.char_at_cursor() { - self.pos += ch.len_utf8(); - true - } else { - false + pub fn move_right(&mut self, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(ch) = self.char_at_cursor() { + self.pos += ch.len_utf8(); + moved = true + } else { + break; + } } + moved } /// Move cursor to the start of the line. @@ -189,30 +197,42 @@ impl LineBuffer { /// Replace a single character under the cursor (Vi mode) pub fn replace_char(&mut self, ch: char) -> Option { - if self.delete() { self.insert(ch) } else { None } + if self.delete(1) { + self.insert(ch) + } else { + None + } } /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. - pub fn delete(&mut self) -> bool { - if !self.buf.is_empty() && self.pos < self.buf.len() { - self.buf.remove(self.pos); - true - } else { - false + pub fn delete(&mut self, count: u16) -> bool { + let mut deleted = false; + for _ in 0..count { + if !self.buf.is_empty() && self.pos < self.buf.len() { + self.buf.remove(self.pos); + deleted = true + } else { + break; + } } + deleted } /// Delete the character at the left of the cursor. /// Basically that is what happens with the "Backspace" keyboard key. - pub fn backspace(&mut self) -> bool { - if let Some(ch) = self.char_before_cursor() { - self.pos -= ch.len_utf8(); - self.buf.remove(self.pos); - true - } else { - false + pub fn backspace(&mut self, count: u16) -> bool { + let mut deleted = false; + for _ in 0..count { + if let Some(ch) = self.char_before_cursor() { + self.pos -= ch.len_utf8(); + self.buf.remove(self.pos); + deleted = true + } else { + break; + } } + deleted } /// Kill all characters on the current line. @@ -248,7 +268,7 @@ impl LineBuffer { return false; } if self.pos == self.buf.len() { - self.move_left(); + self.move_left(1); } let ch = self.buf.remove(self.pos); let size = ch.len_utf8(); @@ -292,18 +312,22 @@ impl LineBuffer { } /// Moves the cursor to the beginning of previous word. - pub fn move_to_prev_word(&mut self, word_def: Word) -> bool { - if let Some(pos) = self.prev_word_pos(self.pos, word_def) { - self.pos = pos; - true - } else { - false + pub fn move_to_prev_word(&mut self, word_def: Word, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(pos) = self.prev_word_pos(self.pos, word_def) { + self.pos = pos; + moved = true + } else { + break; + } } + moved } /// Delete the previous word, maintaining the cursor at the start of the /// current word. - pub fn delete_prev_word(&mut self, word_def: Word) -> Option { + pub fn delete_prev_word(&mut self, word_def: Word, count: u16) -> Option { if let Some(pos) = self.prev_word_pos(self.pos, word_def) { let word = self.buf.drain(pos..self.pos).collect(); self.pos = pos; @@ -378,13 +402,17 @@ impl LineBuffer { } /// Moves the cursor to the end of next word. - pub fn move_to_next_word(&mut self, at: At, word_def: Word) -> bool { - if let Some(pos) = self.next_pos(self.pos, at, word_def) { - self.pos = pos; - true - } else { - false + pub fn move_to_next_word(&mut self, at: At, word_def: Word, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(pos) = self.next_pos(self.pos, at, word_def) { + self.pos = pos; + moved = true + } else { + break; + } } + moved } fn search_char_pos(&mut self, cs: &CharSearch) -> Option { @@ -427,17 +455,21 @@ impl LineBuffer { } } - pub fn move_to(&mut self, cs: CharSearch) -> bool { - if let Some(pos) = self.search_char_pos(&cs) { - self.pos = pos; - true - } else { - false + pub fn move_to(&mut self, cs: CharSearch, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(pos) = self.search_char_pos(&cs) { + self.pos = pos; + moved = true + } else { + break; + } } + moved } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. - pub fn delete_word(&mut self, at: At, word_def: Word) -> Option { + pub fn delete_word(&mut self, at: At, word_def: Word, count: u16) -> Option { if let Some(pos) = self.next_pos(self.pos, at, word_def) { let word = self.buf.drain(self.pos..pos).collect(); Some(word) @@ -446,7 +478,7 @@ impl LineBuffer { } } - pub fn delete_to(&mut self, cs: CharSearch) -> Option { + pub fn delete_to(&mut self, cs: CharSearch, count: u16) -> Option { let search_result = match cs { CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c)), _ => self.search_char_pos(&cs), @@ -607,12 +639,12 @@ mod test { #[test] fn moves() { let mut s = LineBuffer::init("αß", 4); - let ok = s.move_left(); + let ok = s.move_left(1); assert_eq!("αß", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); - let ok = s.move_right(); + let ok = s.move_right(1); assert_eq!("αß", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); @@ -631,12 +663,12 @@ mod test { #[test] fn delete() { let mut s = LineBuffer::init("αß", 2); - let ok = s.delete(); + let ok = s.delete(1); assert_eq!("α", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); - let ok = s.backspace(); + let ok = s.backspace(1); assert_eq!("", s.buf); assert_eq!(0, s.pos); assert_eq!(true, ok); @@ -683,7 +715,7 @@ mod test { #[test] fn move_to_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let ok = s.move_to_prev_word(Word::Emacs); + let ok = s.move_to_prev_word(Word::Emacs, 1); assert_eq!("a ß c", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); @@ -692,7 +724,7 @@ mod test { #[test] fn delete_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let text = s.delete_prev_word(Word::Big); + let text = s.delete_prev_word(Word::Big, 1); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); @@ -701,7 +733,7 @@ mod test { #[test] fn move_to_next_word() { let mut s = LineBuffer::init("a ß c", 1); - let ok = s.move_to_next_word(At::End, Word::Emacs); + let ok = s.move_to_next_word(At::End, Word::Emacs, 1); assert_eq!("a ß c", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); @@ -710,7 +742,7 @@ mod test { #[test] fn move_to_start_of_word() { let mut s = LineBuffer::init("a ß c", 2); - let ok = s.move_to_next_word(At::Start, Word::Emacs); + let ok = s.move_to_next_word(At::Start, Word::Emacs, 1); assert_eq!("a ß c", s.buf); assert_eq!(6, s.pos); assert_eq!(true, ok); @@ -719,7 +751,7 @@ mod test { #[test] fn delete_word() { let mut s = LineBuffer::init("a ß c", 1); - let text = s.delete_word(At::End, Word::Emacs); + let text = s.delete_word(At::End, Word::Emacs, 1); assert_eq!("a c", s.buf); assert_eq!(1, s.pos); assert_eq!(Some(" ß".to_string()), text); @@ -728,7 +760,7 @@ mod test { #[test] fn delete_til_start_of_word() { let mut s = LineBuffer::init("a ß c", 2); - let text = s.delete_word(At::Start, Word::Emacs); + let text = s.delete_word(At::Start, Word::Emacs, 1); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); From 8d6547bd4dfc24b90f69cfd7afce3627aac2e8bb Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 1 Jan 2017 21:51:31 +0100 Subject: [PATCH 0252/1201] Misc --- Cargo.toml | 1 + src/completion.rs | 6 +++++- src/config.rs | 8 +++++++- src/keymap.rs | 15 ++++++++------- src/lib.rs | 14 ++++++++------ src/line_buffer.rs | 7 ++++--- src/tty/mod.rs | 4 +++- 7 files changed, 36 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fd9af2e367..aa463c47f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ license = "MIT" [dependencies] libc = "0.2" unicode-width = "0.1" +unicode-segmentation = "1.0" [target.'cfg(unix)'.dependencies] nix = "0.7" diff --git a/src/completion.rs b/src/completion.rs index 868a4fbad2..28bb8e9938 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -20,7 +20,7 @@ pub trait Completer { /// Updates the edited `line` with the `elected` candidate. fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) { let end = line.pos(); - line.replace(start, end, elected) + line.replace(start..end, elected) } } @@ -60,6 +60,7 @@ use std::rc::Rc; use std::sync::Arc; box_completer! { Box Rc Arc } +/// A `Completer` for file and folder names. pub struct FilenameCompleter { break_chars: BTreeSet, } @@ -121,6 +122,9 @@ pub fn unescape(input: &str, esc_char: Option) -> Cow { Owned(result) } +/// Escape any `break_chars` in `input` string with `esc_char`. +/// For example, '/User Information' becomes '/User\ Information' +/// when space is a breaking char and '\' the escape char. pub fn escape(input: String, esc_char: Option, break_chars: &BTreeSet) -> String { if esc_char.is_none() { return input; diff --git a/src/config.rs b/src/config.rs index 2585c3e33a..d9966bcccd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,6 +13,7 @@ pub struct Config { completion_prompt_limit: usize, /// Duration (milliseconds) Rustyline will wait for a character when reading an ambiguous key sequence. keyseq_timeout: i32, + // Emacs or Vi mode edit_mode: EditMode, } @@ -131,17 +132,22 @@ impl Builder { self } + /// The number of possible completions that determines when the user is asked + /// whether the list of possibilities should be displayed. pub fn completion_prompt_limit(mut self, completion_prompt_limit: usize) -> Builder { self.p.completion_prompt_limit = completion_prompt_limit; self } - /// Set `keyseq_timeout` in milliseconds. + /// Timeout for ambiguous key sequences in milliseconds. + /// Currently, it is used only to distinguish a single ESC from an ESC sequence. + /// After seeing an ESC key, wait at most `keyseq_timeout_ms` for another byte. pub fn keyseq_timeout(mut self, keyseq_timeout_ms: i32) -> Builder { self.p.keyseq_timeout = keyseq_timeout_ms; self } + /// Choose between Emacs or Vi mode. pub fn edit_mode(mut self, edit_mode: EditMode) -> Builder { self.p.edit_mode = edit_mode; self diff --git a/src/keymap.rs b/src/keymap.rs index a59313a5ef..efadf78150 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -110,11 +110,12 @@ impl EditState { } } - fn digit_argument(&mut self, - rdr: &mut R, - config: &Config, - digit: char) - -> Result { + // TODO dynamic prompt (arg: ?) + fn emacs_digit_argument(&mut self, + rdr: &mut R, + config: &Config, + digit: char) + -> Result { match digit { '0'...'9' => { self.num_args = digit.to_digit(10).unwrap() as i16; @@ -139,9 +140,9 @@ impl EditState { fn emacs(&mut self, rdr: &mut R, config: &Config) -> Result { let mut key = try!(rdr.next_key(config.keyseq_timeout())); if let KeyPress::Meta(digit @ '-') = key { - key = try!(self.digit_argument(rdr, config, digit)); + key = try!(self.emacs_digit_argument(rdr, config, digit)); } else if let KeyPress::Meta(digit @ '0'...'9') = key { - key = try!(self.digit_argument(rdr, config, digit)); + key = try!(self.emacs_digit_argument(rdr, config, digit)); } let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), diff --git a/src/lib.rs b/src/lib.rs index 6b20479b84..fd69425cee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,9 +19,10 @@ #![allow(unknown_lints)] extern crate libc; +extern crate unicode_segmentation; +extern crate unicode_width; #[cfg(unix)] extern crate nix; -extern crate unicode_width; #[cfg(windows)] extern crate winapi; #[cfg(windows)] @@ -43,6 +44,8 @@ use std::io::{self, Write}; use std::mem; use std::path::Path; use std::result; +use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; + use tty::{RawMode, RawReader, Terminal, Term}; use completion::{Completer, longest_common_prefix}; @@ -288,7 +291,7 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { pos.row += 1; None } else { - unicode_width::UnicodeWidthChar::width(c) + c.width() }; if let Some(cw) = cw { pos.col += cw; @@ -309,7 +312,7 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { if let Some(push) = s.line.insert(ch) { if push { - if s.cursor.col + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) < s.cols { + if s.cursor.col + ch.width().unwrap_or(0) < s.cols { // Avoid a full update of the line in the trivial case. let cursor = calculate_position(&s.line[..s.line.pos()], s.prompt_size, s.cols); s.cursor = cursor; @@ -665,12 +668,11 @@ fn page_completions(rdr: &mut R, candidates: &[String]) -> Result> { use std::cmp; - use unicode_width::UnicodeWidthStr; let min_col_pad = 2; let max_width = cmp::min(s.cols, candidates.into_iter() - .map(|s| UnicodeWidthStr::width(s.as_str())) + .map(|s| s.as_str().width()) .max() .unwrap() + min_col_pad); let num_cols = s.cols / max_width; @@ -711,7 +713,7 @@ fn page_completions(rdr: &mut R, if i < candidates.len() { let candidate = &candidates[i]; ab.push_str(candidate); - let width = UnicodeWidthStr::width(candidate.as_str()); + let width = candidate.as_str().width(); if ((col + 1) * num_rows) + row < candidates.len() { for _ in width..max_width { ab.push(' '); diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 47bd13c59f..fcd17ff667 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,5 +1,5 @@ //! Line buffer with current cursor position -use std::ops::Deref; +use std::ops::{Deref, Range}; use keymap::{Anchor, At, CharSearch, Word}; /// Maximum buffer size for the line read @@ -568,8 +568,9 @@ impl LineBuffer { } /// Replaces the content between [`start`..`end`] with `text` and positions the cursor to the end of text. - pub fn replace(&mut self, start: usize, end: usize, text: &str) { - self.buf.drain(start..end); + pub fn replace(&mut self, range: Range, text: &str) { + let start = range.start; + self.buf.drain(range); self.insert_str(start, text); self.pos = start + text.len(); } diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 2c4c4e3291..59d4e2a5e9 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -3,11 +3,13 @@ use std::io::Write; use ::Result; use consts::KeyPress; +/// Terminal state pub trait RawMode: Copy + Sized { /// Disable RAW mode for the terminal. fn disable_raw_mode(&self) -> Result<()>; } +/// Translate bytes read from stdin to keys. pub trait RawReader: Sized { /// Blocking read of key pressed. fn next_key(&mut self, timeout_ms: i32) -> Result; @@ -20,7 +22,7 @@ pub trait RawReader: Sized { pub trait Term: Clone { type Reader: RawReader; type Writer: Write; - type Mode; + type Mode: RawMode; fn new() -> Self; /// Check if current terminal can provide a rich line-editing user interface. From bbc7e95fdb9cc9f186f135c8e88762ac1d163613 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 2 Jan 2017 20:13:03 +0100 Subject: [PATCH 0253/1201] Repeated insert and replace char --- src/lib.rs | 19 ++++++++++++++----- src/line_buffer.rs | 33 +++++++++++++++++---------------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fd69425cee..f68d9e03b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -310,9 +310,9 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { /// Insert the character `ch` at cursor current position. fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { - if let Some(push) = s.line.insert(ch) { + if let Some(push) = s.line.insert(ch, count) { if push { - if s.cursor.col + ch.width().unwrap_or(0) < s.cols { + if count == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.cols { // Avoid a full update of the line in the trivial case. let cursor = calculate_position(&s.line[..s.line.pos()], s.prompt_size, s.cols); s.cursor = cursor; @@ -330,6 +330,17 @@ fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { } } +/// Replace a single (or count) character(s) under the cursor (Vi mode) +fn edit_replace_char(s: &mut State, ch: char, count: u16) -> Result<()> { + if s.line.delete(count) { + s.line.insert(ch, count); + s.line.move_left(1); + s.refresh_line() + } else { + Ok(()) + } +} + // Yank/paste `text` at current position. fn edit_yank(s: &mut State, text: &str, anchor: Anchor, count: u16) -> Result<()> { if s.line.yank(text, anchor, count).is_some() { @@ -877,9 +888,7 @@ fn readline_edit(prompt: &str, } Cmd::Replace(count, c) => { editor.kill_ring.reset(); - try!(edit_delete(&mut s, count)); - try!(edit_insert(&mut s, c, count)); - try!(edit_move_left(&mut s, 1)) + try!(edit_replace_char(&mut s, c, count)); } Cmd::EndOfFile => { editor.kill_ring.reset(); diff --git a/src/line_buffer.rs b/src/line_buffer.rs index fcd17ff667..9fa8bfca40 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,4 +1,5 @@ //! Line buffer with current cursor position +use std::iter; use std::ops::{Deref, Range}; use keymap::{Anchor, At, CharSearch, Word}; @@ -108,16 +109,25 @@ impl LineBuffer { /// and advance cursor position accordingly. /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. - pub fn insert(&mut self, ch: char) -> Option { - let shift = ch.len_utf8(); + pub fn insert(&mut self, ch: char, count: u16) -> Option { + let shift = ch.len_utf8() * count as usize; if self.buf.len() + shift > self.buf.capacity() { return None; } let push = self.pos == self.buf.len(); if push { - self.buf.push(ch); + self.buf.reserve(shift); + for _ in 0..count { + self.buf.push(ch); + } } else { - self.buf.insert(self.pos, ch); + if count == 1 { + self.buf.insert(self.pos, ch); + } else { + let text = iter::repeat(ch).take(count as usize).collect::(); + let pos = self.pos; + self.insert_str(pos, &text); + } } self.pos += shift; Some(push) @@ -195,15 +205,6 @@ impl LineBuffer { } } - /// Replace a single character under the cursor (Vi mode) - pub fn replace_char(&mut self, ch: char) -> Option { - if self.delete(1) { - self.insert(ch) - } else { - None - } - } - /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. pub fn delete(&mut self, count: u16) -> bool { @@ -620,18 +621,18 @@ mod test { #[test] fn insert() { let mut s = LineBuffer::with_capacity(MAX_LINE); - let push = s.insert('α').unwrap(); + let push = s.insert('α', 1).unwrap(); assert_eq!("α", s.buf); assert_eq!(2, s.pos); assert_eq!(true, push); - let push = s.insert('ß').unwrap(); + let push = s.insert('ß', 1).unwrap(); assert_eq!("αß", s.buf); assert_eq!(4, s.pos); assert_eq!(true, push); s.pos = 0; - let push = s.insert('γ').unwrap(); + let push = s.insert('γ', 1).unwrap(); assert_eq!("γαß", s.buf); assert_eq!(2, s.pos); assert_eq!(false, push); From a9ea62d7832922205083bda99ae9f101765332ec Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 3 Jan 2017 18:34:13 +0100 Subject: [PATCH 0254/1201] Fix repeated replace char --- src/lib.rs | 7 ++++--- src/line_buffer.rs | 13 +++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f68d9e03b0..5bda1e90cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -332,8 +332,9 @@ fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { /// Replace a single (or count) character(s) under the cursor (Vi mode) fn edit_replace_char(s: &mut State, ch: char, count: u16) -> Result<()> { - if s.line.delete(count) { - s.line.insert(ch, count); + let n = s.line.delete(count); + if n > 0 { + s.line.insert(ch, n); s.line.move_left(1); s.refresh_line() } else { @@ -395,7 +396,7 @@ fn edit_move_end(s: &mut State) -> Result<()> { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. fn edit_delete(s: &mut State, count: u16) -> Result<()> { - if s.line.delete(count) { + if s.line.delete(count) > 0 { s.refresh_line() } else { Ok(()) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 9fa8bfca40..738cbeee24 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -207,17 +207,18 @@ impl LineBuffer { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. - pub fn delete(&mut self, count: u16) -> bool { - let mut deleted = false; + /// Return the number of characters deleted. + pub fn delete(&mut self, count: u16) -> u16 { + let mut n = 0; for _ in 0..count { if !self.buf.is_empty() && self.pos < self.buf.len() { self.buf.remove(self.pos); - deleted = true + n += 1 } else { break; } } - deleted + n } /// Delete the character at the left of the cursor. @@ -665,10 +666,10 @@ mod test { #[test] fn delete() { let mut s = LineBuffer::init("αß", 2); - let ok = s.delete(1); + let n = s.delete(1); assert_eq!("α", s.buf); assert_eq!(2, s.pos); - assert_eq!(true, ok); + assert_eq!(1, n); let ok = s.backspace(1); assert_eq!("", s.buf); From aa6eecd1817139646cb3df5777538d9c0372b4d5 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 3 Jan 2017 19:24:50 +0100 Subject: [PATCH 0255/1201] Repeated yank --- src/line_buffer.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 738cbeee24..d95cfc44d9 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -137,15 +137,24 @@ impl LineBuffer { /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. pub fn yank(&mut self, text: &str, anchor: Anchor, count: u16) -> Option { - let shift = text.len(); + let shift = text.len() * count as usize; if text.is_empty() || (self.buf.len() + shift) > self.buf.capacity() { return None; } if let Anchor::After = anchor { self.move_right(1); } - let pos = self.pos; - let push = self.insert_str(pos, text); + let push = self.pos == self.buf.len(); + if push { + self.buf.reserve(shift); + for _ in 0..count { + self.buf.push_str(text); + } + } else { + let text = iter::repeat(text).take(count as usize).collect::(); + let pos = self.pos; + self.insert_str(pos, &text); + } self.pos += shift; Some(push) } From 897eace4fc047754fe170800a65932b2ac4d8b36 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 3 Jan 2017 20:37:49 +0100 Subject: [PATCH 0256/1201] Repeated insert char --- src/keymap.rs | 144 ++++++++++++++++++++++----------------------- src/lib.rs | 140 +++++++++++++++++++++---------------------- src/line_buffer.rs | 58 +++++++++--------- 3 files changed, 172 insertions(+), 170 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index efadf78150..f7a5acebe8 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -35,7 +35,7 @@ pub enum Cmd { QuotedInsert, Replace(u16, char), // TODO DeleteChar + SelfInsert ReverseSearchHistory, - SelfInsert(char), + SelfInsert(u16, char), Suspend, TransposeChars, TransposeWords, @@ -144,35 +144,39 @@ impl EditState { } else if let KeyPress::Meta(digit @ '0'...'9') = key { key = try!(self.emacs_digit_argument(rdr, config, digit)); } + let (n, positive) = self.emacs_num_args(); // consume them in all cases let cmd = match key { - KeyPress::Char(c) => Cmd::SelfInsert(c), + KeyPress::Char(c) => { + if positive { + Cmd::SelfInsert(n, c) + } else { + Cmd::Unknown // TODO ??? + } + } KeyPress::Ctrl('A') => Cmd::BeginningOfLine, KeyPress::Ctrl('B') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardChar(count) + Cmd::BackwardChar(n) } else { - Cmd::ForwardChar(count) + Cmd::ForwardChar(n) } } KeyPress::Ctrl('E') => Cmd::EndOfLine, KeyPress::Ctrl('F') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::ForwardChar(count) + Cmd::ForwardChar(n) } else { - Cmd::BackwardChar(count) + Cmd::BackwardChar(n) } } KeyPress::Ctrl('G') | KeyPress::Esc => Cmd::Abort, KeyPress::Ctrl('H') | KeyPress::Backspace => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardDeleteChar(count) + Cmd::BackwardDeleteChar(n) } else { - Cmd::DeleteChar(count) + Cmd::DeleteChar(n) } } KeyPress::Tab => Cmd::Complete, @@ -182,45 +186,41 @@ impl EditState { KeyPress::Ctrl('P') => Cmd::PreviousHistory, KeyPress::Meta('\x08') | KeyPress::Meta('\x7f') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardKillWord(count, Word::Emacs) + Cmd::BackwardKillWord(n, Word::Emacs) } else { - Cmd::KillWord(count, At::End, Word::Emacs) + Cmd::KillWord(n, At::End, Word::Emacs) } } KeyPress::Meta('<') => Cmd::BeginningOfHistory, KeyPress::Meta('>') => Cmd::EndOfHistory, KeyPress::Meta('B') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardWord(count, Word::Emacs) + Cmd::BackwardWord(n, Word::Emacs) } else { - Cmd::ForwardWord(count, At::End, Word::Emacs) + Cmd::ForwardWord(n, At::End, Word::Emacs) } } KeyPress::Meta('C') => Cmd::CapitalizeWord, KeyPress::Meta('D') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::KillWord(count, At::End, Word::Emacs) + Cmd::KillWord(n, At::End, Word::Emacs) } else { - Cmd::BackwardKillWord(count, Word::Emacs) + Cmd::BackwardKillWord(n, Word::Emacs) } } KeyPress::Meta('F') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::ForwardWord(count, At::End, Word::Emacs) + Cmd::ForwardWord(n, At::End, Word::Emacs) } else { - Cmd::BackwardWord(count, Word::Emacs) + Cmd::BackwardWord(n, Word::Emacs) } } KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, KeyPress::Meta('Y') => Cmd::YankPop, - _ => self.common(key), + _ => self.common(key, n, positive), }; Ok(cmd) } @@ -247,6 +247,7 @@ impl EditState { if let KeyPress::Char(digit @ '1'...'9') = key { key = try!(self.vi_arg_digit(rdr, config, digit)); } + let n = self.vi_num_args(); // consume them in all cases let cmd = match key { KeyPress::Char('$') | KeyPress::End => Cmd::EndOfLine, @@ -256,28 +257,28 @@ impl EditState { KeyPress::Char('a') => { // vi-append-mode: Vi enter insert mode after the cursor. self.insert = true; - Cmd::ForwardChar(self.vi_num_args()) + Cmd::ForwardChar(n) } KeyPress::Char('A') => { // vi-append-eol: Vi enter insert mode at end of line. self.insert = true; Cmd::EndOfLine } - KeyPress::Char('b') => Cmd::BackwardWord(self.vi_num_args(), Word::Vi), // vi-prev-word - KeyPress::Char('B') => Cmd::BackwardWord(self.vi_num_args(), Word::Big), + KeyPress::Char('b') => Cmd::BackwardWord(n, Word::Vi), // vi-prev-word + KeyPress::Char('B') => Cmd::BackwardWord(n, Word::Big), KeyPress::Char('c') => { self.insert = true; - try!(self.vi_delete_motion(rdr, config, key)) + try!(self.vi_delete_motion(rdr, config, key, n)) } KeyPress::Char('C') => { self.insert = true; Cmd::KillLine } - KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), + KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key, n)), KeyPress::Char('D') | KeyPress::Ctrl('K') => Cmd::KillLine, - KeyPress::Char('e') => Cmd::ForwardWord(self.vi_num_args(), At::End, Word::Vi), - KeyPress::Char('E') => Cmd::ForwardWord(self.vi_num_args(), At::End, Word::Big), + KeyPress::Char('e') => Cmd::ForwardWord(n, At::End, Word::Vi), + KeyPress::Char('E') => Cmd::ForwardWord(n, At::End, Word::Big), KeyPress::Char('i') => { // vi-insertion-mode self.insert = true; @@ -292,18 +293,18 @@ impl EditState { // vi-char-search let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViCharSearch(self.vi_num_args(), cs), + Some(cs) => Cmd::ViCharSearch(n, cs), None => Cmd::Unknown, } } // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank(self.vi_num_args(), Anchor::After), // vi-put, FIXME cursor position - KeyPress::Char('P') => Cmd::Yank(self.vi_num_args(), Anchor::Before), // vi-put, FIXME cursor position + KeyPress::Char('p') => Cmd::Yank(n, Anchor::After), // vi-put, FIXME cursor position + KeyPress::Char('P') => Cmd::Yank(n, Anchor::Before), // vi-put, FIXME cursor position KeyPress::Char('r') => { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); match ch { - KeyPress::Char(c) => Cmd::Replace(self.vi_num_args(), c), + KeyPress::Char(c) => Cmd::Replace(n, c), KeyPress::Esc => Cmd::Noop, _ => Cmd::Unknown, } @@ -312,7 +313,7 @@ impl EditState { KeyPress::Char('s') => { // vi-substitute-char: Vi replace character under the cursor and enter insert mode. self.insert = true; - Cmd::DeleteChar(self.vi_num_args()) + Cmd::DeleteChar(n) } KeyPress::Char('S') => { // vi-substitute-line: Vi substitute entire line. @@ -320,18 +321,18 @@ impl EditState { Cmd::KillWholeLine } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord(self.vi_num_args(), At::Start, Word::Vi), // vi-next-word - KeyPress::Char('W') => Cmd::ForwardWord(self.vi_num_args(), At::Start, Word::Big), // vi-next-word - KeyPress::Char('x') => Cmd::DeleteChar(self.vi_num_args()), // vi-delete: TODO move backward if eol - KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.vi_num_args()), // vi-rubout + KeyPress::Char('w') => Cmd::ForwardWord(n, At::Start, Word::Vi), // vi-next-word + KeyPress::Char('W') => Cmd::ForwardWord(n, At::Start, Word::Big), // vi-next-word + KeyPress::Char('x') => Cmd::DeleteChar(n), // vi-delete: TODO move backward if eol + KeyPress::Char('X') => Cmd::BackwardDeleteChar(n), // vi-rubout // KeyPress::Char('y') => Cmd::???, // vi-yank-to // KeyPress::Char('Y') => Cmd::???, // vi-yank-to KeyPress::Char('h') | KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardChar(self.vi_num_args()), // TODO Validate + KeyPress::Backspace => Cmd::BackwardChar(n), // TODO Validate KeyPress::Ctrl('G') => Cmd::Abort, KeyPress::Char('l') | - KeyPress::Char(' ') => Cmd::ForwardChar(self.vi_num_args()), + KeyPress::Char(' ') => Cmd::ForwardChar(n), KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Char('+') | KeyPress::Char('j') | @@ -348,7 +349,7 @@ impl EditState { Cmd::ForwardSearchHistory } KeyPress::Esc => Cmd::Noop, - _ => self.common(key), + _ => self.common(key, n, true), }; Ok(cmd) } @@ -356,7 +357,7 @@ impl EditState { fn vi_insert(&mut self, rdr: &mut R, config: &Config) -> Result { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { - KeyPress::Char(c) => Cmd::SelfInsert(c), + KeyPress::Char(c) => Cmd::SelfInsert(1, c), KeyPress::Ctrl('H') | KeyPress::Backspace => Cmd::BackwardDeleteChar(1), KeyPress::Tab => Cmd::Complete, @@ -365,7 +366,7 @@ impl EditState { self.insert = false; Cmd::BackwardChar(1) } - _ => self.common(key), + _ => self.common(key, 1, true), }; Ok(cmd) } @@ -373,37 +374,40 @@ impl EditState { fn vi_delete_motion(&mut self, rdr: &mut R, config: &Config, - key: KeyPress) + key: KeyPress, + n: u16) -> Result { let mut mvt = try!(rdr.next_key(config.keyseq_timeout())); if mvt == key { return Ok(Cmd::KillWholeLine); } + let mut n = n; if let KeyPress::Char(digit @ '1'...'9') = mvt { // vi-arg-digit mvt = try!(self.vi_arg_digit(rdr, config, digit)); + n = self.vi_num_args(); } Ok(match mvt { KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. - KeyPress::Char('b') => Cmd::BackwardKillWord(self.vi_num_args(), Word::Vi), - KeyPress::Char('B') => Cmd::BackwardKillWord(self.vi_num_args(), Word::Big), - KeyPress::Char('e') => Cmd::KillWord(self.vi_num_args(), At::End, Word::Vi), - KeyPress::Char('E') => Cmd::KillWord(self.vi_num_args(), At::End, Word::Big), + KeyPress::Char('b') => Cmd::BackwardKillWord(n, Word::Vi), + KeyPress::Char('B') => Cmd::BackwardKillWord(n, Word::Big), + KeyPress::Char('e') => Cmd::KillWord(n, At::End, Word::Vi), + KeyPress::Char('E') => Cmd::KillWord(n, At::End, Word::Big), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViDeleteTo(self.vi_num_args(), cs), + Some(cs) => Cmd::ViDeleteTo(n, cs), None => Cmd::Unknown, } } KeyPress::Char('h') | KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardDeleteChar(self.vi_num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Backspace => Cmd::BackwardDeleteChar(n), // vi-delete-prev-char: Vi move to previous character (backspace). KeyPress::Char('l') | - KeyPress::Char(' ') => Cmd::DeleteChar(self.vi_num_args()), - KeyPress::Char('w') => Cmd::KillWord(self.vi_num_args(), At::Start, Word::Vi), - KeyPress::Char('W') => Cmd::KillWord(self.vi_num_args(), At::Start, Word::Big), + KeyPress::Char(' ') => Cmd::DeleteChar(n), + KeyPress::Char('w') => Cmd::KillWord(n, At::Start, Word::Vi), + KeyPress::Char('W') => Cmd::KillWord(n, At::Start, Word::Big), _ => Cmd::Unknown, }) } @@ -428,34 +432,31 @@ impl EditState { }) } - fn common(&mut self, key: KeyPress) -> Cmd { + fn common(&mut self, key: KeyPress, n: u16, positive: bool) -> Cmd { match key { KeyPress::Home => Cmd::BeginningOfLine, KeyPress::Left => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardChar(count) + Cmd::BackwardChar(n) } else { - Cmd::ForwardChar(count) + Cmd::ForwardChar(n) } } KeyPress::Ctrl('C') => Cmd::Interrupt, KeyPress::Ctrl('D') => Cmd::EndOfFile, KeyPress::Delete => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::DeleteChar(count) + Cmd::DeleteChar(n) } else { - Cmd::BackwardDeleteChar(count) + Cmd::BackwardDeleteChar(n) } } KeyPress::End => Cmd::EndOfLine, KeyPress::Right => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::ForwardChar(count) + Cmd::ForwardChar(n) } else { - Cmd::BackwardChar(count) + Cmd::BackwardChar(n) } } KeyPress::Ctrl('J') | @@ -469,17 +470,15 @@ impl EditState { KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('V') => Cmd::QuotedInsert, KeyPress::Ctrl('W') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardKillWord(count, Word::Big) + Cmd::BackwardKillWord(n, Word::Big) } else { - Cmd::KillWord(count, At::End, Word::Big) + Cmd::KillWord(n, At::End, Word::Big) } } KeyPress::Ctrl('Y') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::Yank(count, Anchor::Before) + Cmd::Yank(n, Anchor::Before) } else { Cmd::Unknown // TODO Validate } @@ -489,6 +488,7 @@ impl EditState { _ => Cmd::Unknown, } } + fn num_args(&mut self) -> i16 { let num_args = match self.num_args { 0 => 1, @@ -501,8 +501,8 @@ impl EditState { fn emacs_num_args(&mut self) -> (u16, bool) { let num_args = self.num_args(); if num_args < 0 { - if let (count, false) = num_args.overflowing_abs() { - (count as u16, false) + if let (n, false) = num_args.overflowing_abs() { + (n as u16, false) } else { (u16::max_value(), false) } diff --git a/src/lib.rs b/src/lib.rs index 5bda1e90cd..7a70ef16b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -309,10 +309,10 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { } /// Insert the character `ch` at cursor current position. -fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { - if let Some(push) = s.line.insert(ch, count) { +fn edit_insert(s: &mut State, ch: char, n: u16) -> Result<()> { + if let Some(push) = s.line.insert(ch, n) { if push { - if count == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.cols { + if n == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.cols { // Avoid a full update of the line in the trivial case. let cursor = calculate_position(&s.line[..s.line.pos()], s.prompt_size, s.cols); s.cursor = cursor; @@ -330,11 +330,11 @@ fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { } } -/// Replace a single (or count) character(s) under the cursor (Vi mode) -fn edit_replace_char(s: &mut State, ch: char, count: u16) -> Result<()> { - let n = s.line.delete(count); - if n > 0 { - s.line.insert(ch, n); +/// Replace a single (or n) character(s) under the cursor (Vi mode) +fn edit_replace_char(s: &mut State, ch: char, n: u16) -> Result<()> { + let count = s.line.delete(n); + if count > 0 { + s.line.insert(ch, count); s.line.move_left(1); s.refresh_line() } else { @@ -343,8 +343,8 @@ fn edit_replace_char(s: &mut State, ch: char, count: u16) -> Result<()> { } // Yank/paste `text` at current position. -fn edit_yank(s: &mut State, text: &str, anchor: Anchor, count: u16) -> Result<()> { - if s.line.yank(text, anchor, count).is_some() { +fn edit_yank(s: &mut State, text: &str, anchor: Anchor, n: u16) -> Result<()> { + if s.line.yank(text, anchor, n).is_some() { s.refresh_line() } else { Ok(()) @@ -358,8 +358,8 @@ fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> { } /// Move cursor on the left. -fn edit_move_left(s: &mut State, count: u16) -> Result<()> { - if s.line.move_left(count) { +fn edit_move_left(s: &mut State, n: u16) -> Result<()> { + if s.line.move_left(n) { s.refresh_line() } else { Ok(()) @@ -367,8 +367,8 @@ fn edit_move_left(s: &mut State, count: u16) -> Result<()> { } /// Move cursor on the right. -fn edit_move_right(s: &mut State, count: u16) -> Result<()> { - if s.line.move_right(count) { +fn edit_move_right(s: &mut State, n: u16) -> Result<()> { + if s.line.move_right(n) { s.refresh_line() } else { Ok(()) @@ -395,8 +395,8 @@ fn edit_move_end(s: &mut State) -> Result<()> { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. -fn edit_delete(s: &mut State, count: u16) -> Result<()> { - if s.line.delete(count) > 0 { +fn edit_delete(s: &mut State, n: u16) -> Result<()> { + if s.line.delete(n) > 0 { s.refresh_line() } else { Ok(()) @@ -404,8 +404,8 @@ fn edit_delete(s: &mut State, count: u16) -> Result<()> { } /// Backspace implementation. -fn edit_backspace(s: &mut State, count: u16) -> Result<()> { - if s.line.backspace(count) { +fn edit_backspace(s: &mut State, n: u16) -> Result<()> { + if s.line.backspace(n) { s.refresh_line() } else { Ok(()) @@ -441,8 +441,8 @@ fn edit_transpose_chars(s: &mut State) -> Result<()> { } } -fn edit_move_to_prev_word(s: &mut State, word_def: Word, count: u16) -> Result<()> { - if s.line.move_to_prev_word(word_def, count) { +fn edit_move_to_prev_word(s: &mut State, word_def: Word, n: u16) -> Result<()> { + if s.line.move_to_prev_word(word_def, n) { s.refresh_line() } else { Ok(()) @@ -451,8 +451,8 @@ fn edit_move_to_prev_word(s: &mut State, word_def: Word, count: u16) -> Result<( /// Delete the previous word, maintaining the cursor at the start of the /// current word. -fn edit_delete_prev_word(s: &mut State, word_def: Word, count: u16) -> Result> { - if let Some(text) = s.line.delete_prev_word(word_def, count) { +fn edit_delete_prev_word(s: &mut State, word_def: Word, n: u16) -> Result> { + if let Some(text) = s.line.delete_prev_word(word_def, n) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -460,16 +460,16 @@ fn edit_delete_prev_word(s: &mut State, word_def: Word, count: u16) -> Result Result<()> { - if s.line.move_to_next_word(at, word_def, count) { +fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word, n: u16) -> Result<()> { + if s.line.move_to_next_word(at, word_def, n) { s.refresh_line() } else { Ok(()) } } -fn edit_move_to(s: &mut State, cs: CharSearch, count: u16) -> Result<()> { - if s.line.move_to(cs, count) { +fn edit_move_to(s: &mut State, cs: CharSearch, n: u16) -> Result<()> { + if s.line.move_to(cs, n) { s.refresh_line() } else { Ok(()) @@ -477,8 +477,8 @@ fn edit_move_to(s: &mut State, cs: CharSearch, count: u16) -> Result<()> { } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. -fn edit_delete_word(s: &mut State, at: At, word_def: Word, count: u16) -> Result> { - if let Some(text) = s.line.delete_word(at, word_def, count) { +fn edit_delete_word(s: &mut State, at: At, word_def: Word, n: u16) -> Result> { + if let Some(text) = s.line.delete_word(at, word_def, n) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -486,8 +486,8 @@ fn edit_delete_word(s: &mut State, at: At, word_def: Word, count: u16) -> Result } } -fn edit_delete_to(s: &mut State, cs: CharSearch, count: u16) -> Result> { - if let Some(text) = s.line.delete_to(cs, count) { +fn edit_delete_to(s: &mut State, cs: CharSearch, n: u16) -> Result> { + if let Some(text) = s.line.delete_to(cs, n) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -652,14 +652,15 @@ fn complete_line(rdr: &mut R, let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len()); try!(write_and_flush(s.out, msg.as_bytes())); s.old_rows += 1; - while cmd != Cmd::SelfInsert('y') && cmd != Cmd::SelfInsert('Y') && - cmd != Cmd::SelfInsert('n') && cmd != Cmd::SelfInsert('N') && + while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y') && + cmd != Cmd::SelfInsert(1, 'n') && + cmd != Cmd::SelfInsert(1, 'N') && cmd != Cmd::BackwardDeleteChar(1) { cmd = try!(s.next_cmd(rdr, config)); } show_completions = match cmd { - Cmd::SelfInsert('y') | - Cmd::SelfInsert('Y') => true, + Cmd::SelfInsert(1, 'y') | + Cmd::SelfInsert(1, 'Y') => true, _ => false, }; } @@ -696,18 +697,19 @@ fn page_completions(rdr: &mut R, if row == pause_row { try!(write_and_flush(s.out, b"\n--More--")); let mut cmd = Cmd::Noop; - while cmd != Cmd::SelfInsert('y') && cmd != Cmd::SelfInsert('Y') && - cmd != Cmd::SelfInsert('n') && cmd != Cmd::SelfInsert('N') && - cmd != Cmd::SelfInsert('q') && - cmd != Cmd::SelfInsert('Q') && - cmd != Cmd::SelfInsert(' ') && + while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1_, 'Y') && + cmd != Cmd::SelfInsert(1, 'n') && + cmd != Cmd::SelfInsert(1_, 'N') && + cmd != Cmd::SelfInsert(1, 'q') && + cmd != Cmd::SelfInsert(1, 'Q') && + cmd != Cmd::SelfInsert(1, ' ') && cmd != Cmd::BackwardDeleteChar(1) && cmd != Cmd::AcceptLine { cmd = try!(s.next_cmd(rdr, config)); } match cmd { - Cmd::SelfInsert('y') | - Cmd::SelfInsert('Y') | - Cmd::SelfInsert(' ') => { + Cmd::SelfInsert(1, 'y') | + Cmd::SelfInsert(1, 'Y') | + Cmd::SelfInsert(1, ' ') => { pause_row += s.term.get_rows() - 1; } Cmd::AcceptLine => { @@ -768,7 +770,7 @@ fn reverse_incremental_search(rdr: &mut R, try!(s.refresh_prompt_and_line(&prompt)); cmd = try!(s.next_cmd(rdr, config)); - if let Cmd::SelfInsert(c) = cmd { + if let Cmd::SelfInsert(_, c) = cmd { search_buf.push(c); } else { match cmd { @@ -854,9 +856,9 @@ fn readline_edit(prompt: &str, } } - if let Cmd::SelfInsert(c) = cmd { + if let Cmd::SelfInsert(n, c) = cmd { editor.kill_ring.reset(); - try!(edit_insert(&mut s, c, 1)); + try!(edit_insert(&mut s, c, n)); continue; } @@ -877,19 +879,19 @@ fn readline_edit(prompt: &str, // Move to the beginning of line. try!(edit_move_home(&mut s)) } - Cmd::BackwardChar(count) => { + Cmd::BackwardChar(n) => { editor.kill_ring.reset(); // Move back a character. - try!(edit_move_left(&mut s, count)) + try!(edit_move_left(&mut s, n)) } - Cmd::DeleteChar(count) => { + Cmd::DeleteChar(n) => { editor.kill_ring.reset(); // Delete (forward) one character at point. - try!(edit_delete(&mut s, count)) + try!(edit_delete(&mut s, n)) } - Cmd::Replace(count, c) => { + Cmd::Replace(n, c) => { editor.kill_ring.reset(); - try!(edit_replace_char(&mut s, c, count)); + try!(edit_replace_char(&mut s, c, n)); } Cmd::EndOfFile => { editor.kill_ring.reset(); @@ -904,15 +906,15 @@ fn readline_edit(prompt: &str, // Move to the end of line. try!(edit_move_end(&mut s)) } - Cmd::ForwardChar(count) => { + Cmd::ForwardChar(n) => { editor.kill_ring.reset(); // Move forward a character. - try!(edit_move_right(&mut s, count)) + try!(edit_move_right(&mut s, n)) } - Cmd::BackwardDeleteChar(count) => { + Cmd::BackwardDeleteChar(n) => { editor.kill_ring.reset(); // Delete one character backward. - try!(edit_backspace(&mut s, count)) + try!(edit_backspace(&mut s, n)) } Cmd::KillLine => { // Kill the text from point to the end of the line. @@ -959,10 +961,10 @@ fn readline_edit(prompt: &str, let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c, 1)) // FIXME } - Cmd::Yank(count, anchor) => { + Cmd::Yank(n, anchor) => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { - try!(edit_yank(&mut s, text, anchor, count)) + try!(edit_yank(&mut s, text, anchor, n)) } } // TODO CTRL-_ // undo @@ -972,9 +974,9 @@ fn readline_edit(prompt: &str, try!(edit_move_end(&mut s)); break; } - Cmd::BackwardKillWord(count, word_def) => { + Cmd::BackwardKillWord(n, word_def) => { // kill one word backward - if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def, count)) { + if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def, n)) { editor.kill_ring.kill(&text, Mode::Prepend) } } @@ -988,26 +990,26 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, false)) } - Cmd::BackwardWord(count, word_def) => { + Cmd::BackwardWord(n, word_def) => { // move backwards one word editor.kill_ring.reset(); - try!(edit_move_to_prev_word(&mut s, word_def, count)) + try!(edit_move_to_prev_word(&mut s, word_def, n)) } Cmd::CapitalizeWord => { // capitalize word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - Cmd::KillWord(count, at, word_def) => { + Cmd::KillWord(n, at, word_def) => { // kill one word forward - if let Some(text) = try!(edit_delete_word(&mut s, at, word_def, count)) { + if let Some(text) = try!(edit_delete_word(&mut s, at, word_def, n)) { editor.kill_ring.kill(&text, Mode::Append) } } - Cmd::ForwardWord(count, at, word_def) => { + Cmd::ForwardWord(n, at, word_def) => { // move forwards one word editor.kill_ring.reset(); - try!(edit_move_to_next_word(&mut s, at, word_def, count)) + try!(edit_move_to_next_word(&mut s, at, word_def, n)) } Cmd::DowncaseWord => { // lowercase word after point @@ -1030,12 +1032,12 @@ fn readline_edit(prompt: &str, try!(edit_yank_pop(&mut s, yank_size, text)) } } - Cmd::ViCharSearch(count, cs) => { + Cmd::ViCharSearch(n, cs) => { editor.kill_ring.reset(); - try!(edit_move_to(&mut s, cs, count)) + try!(edit_move_to(&mut s, cs, n)) } - Cmd::ViDeleteTo(count, cs) => { - if let Some(text) = try!(edit_delete_to(&mut s, cs, count)) { + Cmd::ViDeleteTo(n, cs) => { + if let Some(text) = try!(edit_delete_to(&mut s, cs, n)) { editor.kill_ring.kill(&text, Mode::Append) } } diff --git a/src/line_buffer.rs b/src/line_buffer.rs index d95cfc44d9..ef4dc957da 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -109,22 +109,22 @@ impl LineBuffer { /// and advance cursor position accordingly. /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. - pub fn insert(&mut self, ch: char, count: u16) -> Option { - let shift = ch.len_utf8() * count as usize; + pub fn insert(&mut self, ch: char, n: u16) -> Option { + let shift = ch.len_utf8() * n as usize; if self.buf.len() + shift > self.buf.capacity() { return None; } let push = self.pos == self.buf.len(); if push { self.buf.reserve(shift); - for _ in 0..count { + for _ in 0..n { self.buf.push(ch); } } else { - if count == 1 { + if n == 1 { self.buf.insert(self.pos, ch); } else { - let text = iter::repeat(ch).take(count as usize).collect::(); + let text = iter::repeat(ch).take(n as usize).collect::(); let pos = self.pos; self.insert_str(pos, &text); } @@ -136,8 +136,8 @@ impl LineBuffer { /// Yank/paste `text` at current position. /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. - pub fn yank(&mut self, text: &str, anchor: Anchor, count: u16) -> Option { - let shift = text.len() * count as usize; + pub fn yank(&mut self, text: &str, anchor: Anchor, n: u16) -> Option { + let shift = text.len() * n as usize; if text.is_empty() || (self.buf.len() + shift) > self.buf.capacity() { return None; } @@ -147,11 +147,11 @@ impl LineBuffer { let push = self.pos == self.buf.len(); if push { self.buf.reserve(shift); - for _ in 0..count { + for _ in 0..n { self.buf.push_str(text); } } else { - let text = iter::repeat(text).take(count as usize).collect::(); + let text = iter::repeat(text).take(n as usize).collect::(); let pos = self.pos; self.insert_str(pos, &text); } @@ -167,9 +167,9 @@ impl LineBuffer { } /// Move cursor on the left. - pub fn move_left(&mut self, count: u16) -> bool { + pub fn move_left(&mut self, n: u16) -> bool { let mut moved = false; - for _ in 0..count { + for _ in 0..n { if let Some(ch) = self.char_before_cursor() { self.pos -= ch.len_utf8(); moved = true @@ -181,9 +181,9 @@ impl LineBuffer { } /// Move cursor on the right. - pub fn move_right(&mut self, count: u16) -> bool { + pub fn move_right(&mut self, n: u16) -> bool { let mut moved = false; - for _ in 0..count { + for _ in 0..n { if let Some(ch) = self.char_at_cursor() { self.pos += ch.len_utf8(); moved = true @@ -217,24 +217,24 @@ impl LineBuffer { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. /// Return the number of characters deleted. - pub fn delete(&mut self, count: u16) -> u16 { - let mut n = 0; - for _ in 0..count { + pub fn delete(&mut self, n: u16) -> u16 { + let mut count = 0; + for _ in 0..n { if !self.buf.is_empty() && self.pos < self.buf.len() { self.buf.remove(self.pos); - n += 1 + count += 1 } else { break; } } - n + count } /// Delete the character at the left of the cursor. /// Basically that is what happens with the "Backspace" keyboard key. - pub fn backspace(&mut self, count: u16) -> bool { + pub fn backspace(&mut self, n: u16) -> bool { let mut deleted = false; - for _ in 0..count { + for _ in 0..n { if let Some(ch) = self.char_before_cursor() { self.pos -= ch.len_utf8(); self.buf.remove(self.pos); @@ -323,9 +323,9 @@ impl LineBuffer { } /// Moves the cursor to the beginning of previous word. - pub fn move_to_prev_word(&mut self, word_def: Word, count: u16) -> bool { + pub fn move_to_prev_word(&mut self, word_def: Word, n: u16) -> bool { let mut moved = false; - for _ in 0..count { + for _ in 0..n { if let Some(pos) = self.prev_word_pos(self.pos, word_def) { self.pos = pos; moved = true @@ -338,7 +338,7 @@ impl LineBuffer { /// Delete the previous word, maintaining the cursor at the start of the /// current word. - pub fn delete_prev_word(&mut self, word_def: Word, count: u16) -> Option { + pub fn delete_prev_word(&mut self, word_def: Word, n: u16) -> Option { if let Some(pos) = self.prev_word_pos(self.pos, word_def) { let word = self.buf.drain(pos..self.pos).collect(); self.pos = pos; @@ -413,9 +413,9 @@ impl LineBuffer { } /// Moves the cursor to the end of next word. - pub fn move_to_next_word(&mut self, at: At, word_def: Word, count: u16) -> bool { + pub fn move_to_next_word(&mut self, at: At, word_def: Word, n: u16) -> bool { let mut moved = false; - for _ in 0..count { + for _ in 0..n { if let Some(pos) = self.next_pos(self.pos, at, word_def) { self.pos = pos; moved = true @@ -466,9 +466,9 @@ impl LineBuffer { } } - pub fn move_to(&mut self, cs: CharSearch, count: u16) -> bool { + pub fn move_to(&mut self, cs: CharSearch, n: u16) -> bool { let mut moved = false; - for _ in 0..count { + for _ in 0..n { if let Some(pos) = self.search_char_pos(&cs) { self.pos = pos; moved = true @@ -480,7 +480,7 @@ impl LineBuffer { } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. - pub fn delete_word(&mut self, at: At, word_def: Word, count: u16) -> Option { + pub fn delete_word(&mut self, at: At, word_def: Word, n: u16) -> Option { if let Some(pos) = self.next_pos(self.pos, at, word_def) { let word = self.buf.drain(self.pos..pos).collect(); Some(word) @@ -489,7 +489,7 @@ impl LineBuffer { } } - pub fn delete_to(&mut self, cs: CharSearch, count: u16) -> Option { + pub fn delete_to(&mut self, cs: CharSearch, n: u16) -> Option { let search_result = match cs { CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c)), _ => self.search_char_pos(&cs), From 9a343713eec5d03cc95d379eaab9720df21637f3 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 4 Jan 2017 20:04:09 +0100 Subject: [PATCH 0257/1201] Fix repeated word commands --- src/line_buffer.rs | 145 ++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 75 deletions(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index ef4dc957da..8938c54074 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -297,49 +297,50 @@ impl LineBuffer { } /// Go left until start of word - fn prev_word_pos(&self, pos: usize, word_def: Word) -> Option { + fn prev_word_pos(&self, pos: usize, word_def: Word, n: u16) -> Option { if pos == 0 { return None; } let test = is_break_char(word_def); let mut pos = pos; - // eat any spaces on the left - pos -= self.buf[..pos] - .chars() - .rev() - .take_while(|ch| test(ch)) - .map(char::len_utf8) - .sum(); - if pos > 0 { - // eat any non-spaces on the left + for _ in 0..n { + // eat any spaces on the left pos -= self.buf[..pos] .chars() .rev() - .take_while(|ch| !test(ch)) + .take_while(|ch| test(ch)) .map(char::len_utf8) .sum(); + if pos > 0 { + // eat any non-spaces on the left + pos -= self.buf[..pos] + .chars() + .rev() + .take_while(|ch| !test(ch)) + .map(char::len_utf8) + .sum(); + } + if pos == 0 { + break; + } } Some(pos) } /// Moves the cursor to the beginning of previous word. pub fn move_to_prev_word(&mut self, word_def: Word, n: u16) -> bool { - let mut moved = false; - for _ in 0..n { - if let Some(pos) = self.prev_word_pos(self.pos, word_def) { - self.pos = pos; - moved = true - } else { - break; - } + if let Some(pos) = self.prev_word_pos(self.pos, word_def, n) { + self.pos = pos; + true + } else { + false } - moved } /// Delete the previous word, maintaining the cursor at the start of the /// current word. pub fn delete_prev_word(&mut self, word_def: Word, n: u16) -> Option { - if let Some(pos) = self.prev_word_pos(self.pos, word_def) { + if let Some(pos) = self.prev_word_pos(self.pos, word_def, n) { let word = self.buf.drain(pos..self.pos).collect(); self.pos = pos; Some(word) @@ -348,23 +349,26 @@ impl LineBuffer { } } - fn next_pos(&self, pos: usize, at: At, word_def: Word) -> Option { + fn next_pos(&self, pos: usize, at: At, word_def: Word, n: u16) -> Option { match at { At::End => { - match self.next_word_pos(pos, word_def) { + match self.next_word_pos(pos, word_def, n) { Some((_, end)) => Some(end), _ => None, } } - At::Start => self.next_start_of_word_pos(pos, word_def), + At::Start => self.next_start_of_word_pos(pos, word_def, n), } } /// Go right until start of word - fn next_start_of_word_pos(&self, pos: usize, word_def: Word) -> Option { - if pos < self.buf.len() { - let test = is_break_char(word_def); - let mut pos = pos; + fn next_start_of_word_pos(&self, pos: usize, word_def: Word, n: u16) -> Option { + if pos == self.buf.len() { + return None; + } + let test = is_break_char(word_def); + let mut pos = pos; + for _ in 0..n { // eat any non-spaces pos += self.buf[pos..] .chars() @@ -379,25 +383,30 @@ impl LineBuffer { .map(char::len_utf8) .sum(); } - Some(pos) - } else { - None + if pos == self.buf.len() { + break; + } } + Some(pos) } /// Go right until end of word /// Returns the position (start, end) of the next word. - fn next_word_pos(&self, pos: usize, word_def: Word) -> Option<(usize, usize)> { - if pos < self.buf.len() { - let test = is_break_char(word_def); - let mut pos = pos; + fn next_word_pos(&self, pos: usize, word_def: Word, n: u16) -> Option<(usize, usize)> { + if pos == self.buf.len() { + return None; + } + let test = is_break_char(word_def); + let mut pos = pos; + let mut start = pos; + for _ in 0..n { // eat any spaces pos += self.buf[pos..] .chars() .take_while(test) .map(char::len_utf8) .sum(); - let start = pos; + start = pos; if pos < self.buf.len() { // eat any non-spaces pos += self.buf[pos..] @@ -406,38 +415,28 @@ impl LineBuffer { .map(char::len_utf8) .sum(); } - Some((start, pos)) - } else { - None + if pos == self.buf.len() { + break; + } } + Some((start, pos)) } /// Moves the cursor to the end of next word. pub fn move_to_next_word(&mut self, at: At, word_def: Word, n: u16) -> bool { - let mut moved = false; - for _ in 0..n { - if let Some(pos) = self.next_pos(self.pos, at, word_def) { - self.pos = pos; - moved = true - } else { - break; - } + if let Some(pos) = self.next_pos(self.pos, at, word_def, n) { + self.pos = pos; + true + } else { + false } - moved } - fn search_char_pos(&mut self, cs: &CharSearch) -> Option { + fn search_char_pos(&mut self, cs: &CharSearch, n: u16) -> Option { let mut shift = 0; let search_result = match *cs { CharSearch::Backward(c) | - CharSearch::BackwardAfter(c) => { - self.buf[..self.pos].rfind(c) - // if let Some(pc) = self.char_before_cursor() { - // self.buf[..self.pos - pc.len_utf8()].rfind(c) - // } else { - // None - // } - } + CharSearch::BackwardAfter(c) => self.buf[..self.pos].rfind(c), CharSearch::Forward(c) | CharSearch::ForwardBefore(c) => { if let Some(cc) = self.char_at_cursor() { @@ -467,21 +466,17 @@ impl LineBuffer { } pub fn move_to(&mut self, cs: CharSearch, n: u16) -> bool { - let mut moved = false; - for _ in 0..n { - if let Some(pos) = self.search_char_pos(&cs) { - self.pos = pos; - moved = true - } else { - break; - } + if let Some(pos) = self.search_char_pos(&cs, n) { + self.pos = pos; + true + } else { + false } - moved } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. pub fn delete_word(&mut self, at: At, word_def: Word, n: u16) -> Option { - if let Some(pos) = self.next_pos(self.pos, at, word_def) { + if let Some(pos) = self.next_pos(self.pos, at, word_def, n) { let word = self.buf.drain(self.pos..pos).collect(); Some(word) } else { @@ -491,8 +486,8 @@ impl LineBuffer { pub fn delete_to(&mut self, cs: CharSearch, n: u16) -> Option { let search_result = match cs { - CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c)), - _ => self.search_char_pos(&cs), + CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c), n), + _ => self.search_char_pos(&cs, n), }; if let Some(pos) = search_result { let chunk = match cs { @@ -513,7 +508,7 @@ impl LineBuffer { /// Alter the next word. pub fn edit_word(&mut self, a: WordAction) -> bool { - if let Some((start, end)) = self.next_word_pos(self.pos, Word::Emacs) { + if let Some((start, end)) = self.next_word_pos(self.pos, Word::Emacs, 1) { if start == end { return false; } @@ -544,16 +539,16 @@ impl LineBuffer { // ^ ^ ^ // prev_start start self.pos/end let word_def = Word::Emacs; - if let Some(start) = self.prev_word_pos(self.pos, word_def) { - if let Some(prev_start) = self.prev_word_pos(start, word_def) { - let (_, prev_end) = self.next_word_pos(prev_start, word_def).unwrap(); + if let Some(start) = self.prev_word_pos(self.pos, word_def, 1) { + if let Some(prev_start) = self.prev_word_pos(start, word_def, 1) { + let (_, prev_end) = self.next_word_pos(prev_start, word_def, 1).unwrap(); if prev_end >= start { return false; } - let (_, mut end) = self.next_word_pos(start, word_def).unwrap(); + let (_, mut end) = self.next_word_pos(start, word_def, 1).unwrap(); if end < self.pos { if self.pos < self.buf.len() { - let (s, _) = self.next_word_pos(self.pos, word_def).unwrap(); + let (s, _) = self.next_word_pos(self.pos, word_def, 1).unwrap(); end = s; } else { end = self.pos; From bcb0b8ae5059bf02c887ca9c33b27e9e8e4acc8e Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 5 Jan 2017 21:36:57 +0100 Subject: [PATCH 0258/1201] Fix Clippy warning --- src/line_buffer.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 8938c54074..3c9b58dfdf 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -120,14 +120,12 @@ impl LineBuffer { for _ in 0..n { self.buf.push(ch); } + } else if n == 1 { + self.buf.insert(self.pos, ch); } else { - if n == 1 { - self.buf.insert(self.pos, ch); - } else { - let text = iter::repeat(ch).take(n as usize).collect::(); - let pos = self.pos; - self.insert_str(pos, &text); - } + let text = iter::repeat(ch).take(n as usize).collect::(); + let pos = self.pos; + self.insert_str(pos, &text); } self.pos += shift; Some(push) From 63cbaf3727d9d44e993fb422fb7cdb5bac8acc48 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 7 Jan 2017 12:07:29 +0100 Subject: [PATCH 0259/1201] Fix repeated char search --- src/line_buffer.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 3c9b58dfdf..838b13c771 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -434,13 +434,26 @@ impl LineBuffer { let mut shift = 0; let search_result = match *cs { CharSearch::Backward(c) | - CharSearch::BackwardAfter(c) => self.buf[..self.pos].rfind(c), + CharSearch::BackwardAfter(c) => { + self.buf[..self.pos] + .chars() + .rev() + .enumerate() + .filter(|&(_, ch)| ch == c) + .nth(n as usize - 1) + .map(|(i, _)| self.pos - i) + } CharSearch::Forward(c) | CharSearch::ForwardBefore(c) => { if let Some(cc) = self.char_at_cursor() { shift = self.pos + cc.len_utf8(); if shift < self.buf.len() { - self.buf[shift..].find(c) + self.buf[shift..] + .chars() + .enumerate() + .filter(|&(_, ch)| ch == c) + .nth(n as usize - 1) + .map(|(i, _)| i) } else { None } @@ -451,8 +464,8 @@ impl LineBuffer { }; if let Some(pos) = search_result { Some(match *cs { - CharSearch::Backward(_) => pos, - CharSearch::BackwardAfter(c) => pos + c.len_utf8(), + CharSearch::Backward(c) => pos - c.len_utf8(), + CharSearch::BackwardAfter(_) => pos, CharSearch::Forward(_) => shift + pos, CharSearch::ForwardBefore(_) => { shift + pos - self.buf[..shift + pos].chars().next_back().unwrap().len_utf8() From 931c43c02a98bdfd42d98f7bcf4d38faa80bb9f3 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 21 Dec 2016 21:15:39 +0100 Subject: [PATCH 0260/1201] Ensure tests do not write to stdout --- src/lib.rs | 2 +- src/tty/mod.rs | 3 +++ src/tty/test.rs | 6 +++++- src/tty/unix.rs | 7 ++++++- src/tty/windows.rs | 8 ++++++-- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8955ff57de..ad7a0ff19d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -811,7 +811,7 @@ fn readline_edit(prompt: &str, -> Result { let completer = editor.completer.as_ref().map(|c| c as &Completer); - let mut stdout = io::stdout(); + let mut stdout = editor.term.create_writer(); editor.kill_ring.reset(); let mut s = State::new(&mut stdout, diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 4a0475220e..2c4c4e3291 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -19,6 +19,7 @@ pub trait RawReader: Sized { /// Terminal contract pub trait Term: Clone { type Reader: RawReader; + type Writer: Write; type Mode; fn new() -> Self; @@ -36,6 +37,8 @@ pub trait Term: Clone { fn enable_raw_mode(&self) -> Result; /// Create a RAW reader fn create_reader(&self) -> Result; + /// Create a writer + fn create_writer(&self) -> Self::Writer; /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, w: &mut Write) -> Result<()>; } diff --git a/src/tty/test.rs b/src/tty/test.rs index 19ce23ba5b..efaf950d2f 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -1,5 +1,5 @@ //! Tests specific definitions -use std::io::Write; +use std::io::{self, Sink, Write}; use std::iter::IntoIterator; use std::slice::Iter; use std::vec::IntoIter; @@ -90,6 +90,7 @@ impl DummyTerminal { impl Term for DummyTerminal { type Reader = IntoIter; + type Writer = Sink; type Mode = Mode; fn new() -> DummyTerminal { @@ -134,6 +135,9 @@ impl Term for DummyTerminal { Ok(self.keys.clone().into_iter()) } + fn create_writer(&self) -> Sink { + io::sink() + } /// Clear the screen. Used to handle ctrl+l fn clear_screen(&mut self, _: &mut Write) -> Result<()> { diff --git a/src/tty/unix.rs b/src/tty/unix.rs index e28f771fee..c5daef85b8 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,6 +1,6 @@ //! Unix specific definitions use std; -use std::io::{self, Read, Write}; +use std::io::{self, Read, Stdout, Write}; use std::sync; use std::sync::atomic; use libc; @@ -231,6 +231,7 @@ pub struct PosixTerminal { impl Term for PosixTerminal { type Reader = PosixRawReader; + type Writer = Stdout; type Mode = Mode; fn new() -> PosixTerminal { @@ -300,6 +301,10 @@ impl Term for PosixTerminal { PosixRawReader::new() } + fn create_writer(&self) -> Stdout { + io::stdout() + } + /// Check if a SIGWINCH signal has been received fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index c279e0d3f4..1b176cf849 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,6 +1,5 @@ //! Windows specific definitions -use std::io; -use std::io::Write; +use std::io::{self, Stdout, Write}; use std::mem; use std::sync::atomic; @@ -218,6 +217,7 @@ impl Console { impl Term for Console { type Reader = ConsoleRawReader; + type Writer = Stdout; type Mode = Mode; fn new() -> Console { @@ -294,6 +294,10 @@ impl Term for Console { ConsoleRawReader::new() } + fn create_writer(&self) -> Stdout { + io::stdout() + } + fn sigwinch(&self) -> bool { SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) } From 5b3acc489e8f0120985a35a1f8e9b403c6519c4b Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 28 Dec 2016 21:29:48 +0100 Subject: [PATCH 0261/1201] Partial support to repeated commands --- src/keymap.rs | 217 +++++++++++++++++++++++++++++++++------------ src/lib.rs | 106 +++++++++++----------- src/line_buffer.rs | 154 +++++++++++++++++++------------- 3 files changed, 307 insertions(+), 170 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index 8909e7d35c..a59313a5ef 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -8,32 +8,32 @@ use super::Result; pub enum Cmd { Abort, // Miscellaneous Command AcceptLine, - BackwardChar(i32), - BackwardDeleteChar(i32), - BackwardKillWord(i32, Word), // Backward until start of word - BackwardWord(i32, Word), // Backward until start of word + BackwardChar(u16), + BackwardDeleteChar(u16), + BackwardKillWord(u16, Word), // Backward until start of word + BackwardWord(u16, Word), // Backward until start of word BeginningOfHistory, BeginningOfLine, CapitalizeWord, ClearScreen, Complete, - DeleteChar(i32), + DeleteChar(u16), DowncaseWord, EndOfFile, EndOfHistory, EndOfLine, - ForwardChar(i32), + ForwardChar(u16), ForwardSearchHistory, - ForwardWord(i32, At, Word), // Forward until start/end of word + ForwardWord(u16, At, Word), // Forward until start/end of word Interrupt, KillLine, KillWholeLine, - KillWord(i32, At, Word), // Forward until start/end of word + KillWord(u16, At, Word), // Forward until start/end of word NextHistory, Noop, PreviousHistory, QuotedInsert, - Replace(i32, char), // TODO DeleteChar + SelfInsert + Replace(u16, char), // TODO DeleteChar + SelfInsert ReverseSearchHistory, SelfInsert(char), Suspend, @@ -43,9 +43,9 @@ pub enum Cmd { UnixLikeDiscard, // UnixWordRubout, // = BackwardKillWord(Word::Big) UpcaseWord, - ViCharSearch(i32, CharSearch), - ViDeleteTo(i32, CharSearch), - Yank(i32, Anchor), + ViCharSearch(u16, CharSearch), + ViDeleteTo(u16, CharSearch), + Yank(u16, Anchor), YankPop, } @@ -86,7 +86,7 @@ pub struct EditState { // Vi Command/Alternate, Insert/Input mode insert: bool, // vi only ? // numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 - num_args: i32, + num_args: i16, } impl EditState { @@ -117,7 +117,7 @@ impl EditState { -> Result { match digit { '0'...'9' => { - self.num_args = digit.to_digit(10).unwrap() as i32; + self.num_args = digit.to_digit(10).unwrap() as i16; } '-' => { self.num_args = -1; @@ -129,7 +129,7 @@ impl EditState { match key { KeyPress::Char(digit @ '0'...'9') | KeyPress::Meta(digit @ '0'...'9') => { - self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i16; } _ => return Ok(key), }; @@ -146,26 +146,75 @@ impl EditState { let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), KeyPress::Ctrl('A') => Cmd::BeginningOfLine, - KeyPress::Ctrl('B') => Cmd::BackwardChar(self.num_args()), + KeyPress::Ctrl('B') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardChar(count) + } else { + Cmd::ForwardChar(count) + } + } KeyPress::Ctrl('E') => Cmd::EndOfLine, - KeyPress::Ctrl('F') => Cmd::ForwardChar(self.num_args()), + KeyPress::Ctrl('F') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::ForwardChar(count) + } else { + Cmd::BackwardChar(count) + } + } KeyPress::Ctrl('G') | KeyPress::Esc => Cmd::Abort, KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), + KeyPress::Backspace => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardDeleteChar(count) + } else { + Cmd::DeleteChar(count) + } + } KeyPress::Tab => Cmd::Complete, KeyPress::Ctrl('K') => Cmd::KillLine, KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Ctrl('N') => Cmd::NextHistory, KeyPress::Ctrl('P') => Cmd::PreviousHistory, KeyPress::Meta('\x08') | - KeyPress::Meta('\x7f') => Cmd::BackwardKillWord(self.num_args(), Word::Emacs), + KeyPress::Meta('\x7f') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardKillWord(count, Word::Emacs) + } else { + Cmd::KillWord(count, At::End, Word::Emacs) + } + } KeyPress::Meta('<') => Cmd::BeginningOfHistory, KeyPress::Meta('>') => Cmd::EndOfHistory, - KeyPress::Meta('B') => Cmd::BackwardWord(self.num_args(), Word::Emacs), + KeyPress::Meta('B') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardWord(count, Word::Emacs) + } else { + Cmd::ForwardWord(count, At::End, Word::Emacs) + } + } KeyPress::Meta('C') => Cmd::CapitalizeWord, - KeyPress::Meta('D') => Cmd::KillWord(self.num_args(), At::End, Word::Emacs), - KeyPress::Meta('F') => Cmd::ForwardWord(self.num_args(), At::End, Word::Emacs), + KeyPress::Meta('D') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::KillWord(count, At::End, Word::Emacs) + } else { + Cmd::BackwardKillWord(count, Word::Emacs) + } + } + KeyPress::Meta('F') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::ForwardWord(count, At::End, Word::Emacs) + } else { + Cmd::BackwardWord(count, Word::Emacs) + } + } KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, @@ -180,12 +229,12 @@ impl EditState { config: &Config, digit: char) -> Result { - self.num_args = digit.to_digit(10).unwrap() as i32; + self.num_args = digit.to_digit(10).unwrap() as i16; loop { let key = try!(rdr.next_key(config.keyseq_timeout())); match key { KeyPress::Char(digit @ '0'...'9') => { - self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i32; + self.num_args = self.num_args * 10 + digit.to_digit(10).unwrap() as i16; } _ => return Ok(key), }; @@ -206,15 +255,15 @@ impl EditState { KeyPress::Char('a') => { // vi-append-mode: Vi enter insert mode after the cursor. self.insert = true; - Cmd::ForwardChar(self.num_args()) + Cmd::ForwardChar(self.vi_num_args()) } KeyPress::Char('A') => { // vi-append-eol: Vi enter insert mode at end of line. self.insert = true; Cmd::EndOfLine } - KeyPress::Char('b') => Cmd::BackwardWord(self.num_args(), Word::Vi), // vi-prev-word - KeyPress::Char('B') => Cmd::BackwardWord(self.num_args(), Word::Big), + KeyPress::Char('b') => Cmd::BackwardWord(self.vi_num_args(), Word::Vi), // vi-prev-word + KeyPress::Char('B') => Cmd::BackwardWord(self.vi_num_args(), Word::Big), KeyPress::Char('c') => { self.insert = true; try!(self.vi_delete_motion(rdr, config, key)) @@ -226,8 +275,8 @@ impl EditState { KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), KeyPress::Char('D') | KeyPress::Ctrl('K') => Cmd::KillLine, - KeyPress::Char('e') => Cmd::ForwardWord(self.num_args(), At::End, Word::Vi), - KeyPress::Char('E') => Cmd::ForwardWord(self.num_args(), At::End, Word::Big), + KeyPress::Char('e') => Cmd::ForwardWord(self.vi_num_args(), At::End, Word::Vi), + KeyPress::Char('E') => Cmd::ForwardWord(self.vi_num_args(), At::End, Word::Big), KeyPress::Char('i') => { // vi-insertion-mode self.insert = true; @@ -242,18 +291,18 @@ impl EditState { // vi-char-search let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViCharSearch(self.num_args(), cs), + Some(cs) => Cmd::ViCharSearch(self.vi_num_args(), cs), None => Cmd::Unknown, } } // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank(self.num_args(), Anchor::After), // vi-put - KeyPress::Char('P') => Cmd::Yank(self.num_args(), Anchor::Before), // vi-put + KeyPress::Char('p') => Cmd::Yank(self.vi_num_args(), Anchor::After), // vi-put, FIXME cursor position + KeyPress::Char('P') => Cmd::Yank(self.vi_num_args(), Anchor::Before), // vi-put, FIXME cursor position KeyPress::Char('r') => { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); match ch { - KeyPress::Char(c) => Cmd::Replace(self.num_args(), c), + KeyPress::Char(c) => Cmd::Replace(self.vi_num_args(), c), KeyPress::Esc => Cmd::Noop, _ => Cmd::Unknown, } @@ -262,7 +311,7 @@ impl EditState { KeyPress::Char('s') => { // vi-substitute-char: Vi replace character under the cursor and enter insert mode. self.insert = true; - Cmd::DeleteChar(self.num_args()) + Cmd::DeleteChar(self.vi_num_args()) } KeyPress::Char('S') => { // vi-substitute-line: Vi substitute entire line. @@ -270,18 +319,18 @@ impl EditState { Cmd::KillWholeLine } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord(self.num_args(), At::Start, Word::Vi), // vi-next-word - KeyPress::Char('W') => Cmd::ForwardWord(self.num_args(), At::Start, Word::Big), // vi-next-word - KeyPress::Char('x') => Cmd::DeleteChar(self.num_args()), // vi-delete: TODO move backward if eol - KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.num_args()), // vi-rubout + KeyPress::Char('w') => Cmd::ForwardWord(self.vi_num_args(), At::Start, Word::Vi), // vi-next-word + KeyPress::Char('W') => Cmd::ForwardWord(self.vi_num_args(), At::Start, Word::Big), // vi-next-word + KeyPress::Char('x') => Cmd::DeleteChar(self.vi_num_args()), // vi-delete: TODO move backward if eol + KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.vi_num_args()), // vi-rubout // KeyPress::Char('y') => Cmd::???, // vi-yank-to // KeyPress::Char('Y') => Cmd::???, // vi-yank-to KeyPress::Char('h') | KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardChar(self.num_args()), // TODO Validate + KeyPress::Backspace => Cmd::BackwardChar(self.vi_num_args()), // TODO Validate KeyPress::Ctrl('G') => Cmd::Abort, KeyPress::Char('l') | - KeyPress::Char(' ') => Cmd::ForwardChar(self.num_args()), + KeyPress::Char(' ') => Cmd::ForwardChar(self.vi_num_args()), KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Char('+') | KeyPress::Char('j') | @@ -336,24 +385,24 @@ impl EditState { Ok(match mvt { KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. - KeyPress::Char('b') => Cmd::BackwardKillWord(self.num_args(), Word::Vi), - KeyPress::Char('B') => Cmd::BackwardKillWord(self.num_args(), Word::Big), - KeyPress::Char('e') => Cmd::KillWord(self.num_args(), At::End, Word::Vi), - KeyPress::Char('E') => Cmd::KillWord(self.num_args(), At::End, Word::Big), + KeyPress::Char('b') => Cmd::BackwardKillWord(self.vi_num_args(), Word::Vi), + KeyPress::Char('B') => Cmd::BackwardKillWord(self.vi_num_args(), Word::Big), + KeyPress::Char('e') => Cmd::KillWord(self.vi_num_args(), At::End, Word::Vi), + KeyPress::Char('E') => Cmd::KillWord(self.vi_num_args(), At::End, Word::Big), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViDeleteTo(self.num_args(), cs), + Some(cs) => Cmd::ViDeleteTo(self.vi_num_args(), cs), None => Cmd::Unknown, } } KeyPress::Char('h') | KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardDeleteChar(self.num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Backspace => Cmd::BackwardDeleteChar(self.vi_num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). KeyPress::Char('l') | - KeyPress::Char(' ') => Cmd::DeleteChar(self.num_args()), - KeyPress::Char('w') => Cmd::KillWord(self.num_args(), At::Start, Word::Vi), - KeyPress::Char('W') => Cmd::KillWord(self.num_args(), At::Start, Word::Big), + KeyPress::Char(' ') => Cmd::DeleteChar(self.vi_num_args()), + KeyPress::Char('w') => Cmd::KillWord(self.vi_num_args(), At::Start, Word::Vi), + KeyPress::Char('W') => Cmd::KillWord(self.vi_num_args(), At::Start, Word::Big), _ => Cmd::Unknown, }) } @@ -381,12 +430,33 @@ impl EditState { fn common(&mut self, key: KeyPress) -> Cmd { match key { KeyPress::Home => Cmd::BeginningOfLine, - KeyPress::Left => Cmd::BackwardChar(self.num_args()), + KeyPress::Left => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardChar(count) + } else { + Cmd::ForwardChar(count) + } + } KeyPress::Ctrl('C') => Cmd::Interrupt, KeyPress::Ctrl('D') => Cmd::EndOfFile, - KeyPress::Delete => Cmd::DeleteChar(self.num_args()), + KeyPress::Delete => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::DeleteChar(count) + } else { + Cmd::BackwardDeleteChar(count) + } + } KeyPress::End => Cmd::EndOfLine, - KeyPress::Right => Cmd::ForwardChar(self.num_args()), + KeyPress::Right => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::ForwardChar(count) + } else { + Cmd::BackwardChar(count) + } + } KeyPress::Ctrl('J') | KeyPress::Enter => Cmd::AcceptLine, KeyPress::Down => Cmd::NextHistory, @@ -397,15 +467,28 @@ impl EditState { KeyPress::Ctrl('U') => Cmd::UnixLikeDiscard, KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('V') => Cmd::QuotedInsert, - KeyPress::Ctrl('W') => Cmd::BackwardKillWord(self.num_args(), Word::Big), - KeyPress::Ctrl('Y') => Cmd::Yank(self.num_args(), Anchor::Before), + KeyPress::Ctrl('W') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::BackwardKillWord(count, Word::Big) + } else { + Cmd::KillWord(count, At::End, Word::Big) + } + } + KeyPress::Ctrl('Y') => { + let (count, positive) = self.emacs_num_args(); + if positive { + Cmd::Yank(count, Anchor::Before) + } else { + Cmd::Unknown // TODO Validate + } + } KeyPress::Ctrl('Z') => Cmd::Suspend, KeyPress::UnknownEscSeq => Cmd::Noop, _ => Cmd::Unknown, } } - - fn num_args(&mut self) -> i32 { + fn num_args(&mut self) -> i16 { let num_args = match self.num_args { 0 => 1, _ => self.num_args, @@ -413,4 +496,26 @@ impl EditState { self.num_args = 0; num_args } + + fn emacs_num_args(&mut self) -> (u16, bool) { + let num_args = self.num_args(); + if num_args < 0 { + if let (count, false) = num_args.overflowing_abs() { + (count as u16, false) + } else { + (u16::max_value(), false) + } + } else { + (num_args as u16, true) + } + } + + fn vi_num_args(&mut self) -> u16 { + let num_args = self.num_args(); + if num_args < 0 { + unreachable!() + } else { + num_args.abs() as u16 + } + } } diff --git a/src/lib.rs b/src/lib.rs index ad7a0ff19d..bd60786ce7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -306,7 +306,7 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { } /// Insert the character `ch` at cursor current position. -fn edit_insert(s: &mut State, ch: char) -> Result<()> { +fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { if let Some(push) = s.line.insert(ch) { if push { if s.cursor.col + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) < s.cols { @@ -326,8 +326,8 @@ fn edit_insert(s: &mut State, ch: char) -> Result<()> { } // Yank/paste `text` at current position. -fn edit_yank(s: &mut State, text: &str, anchor: Anchor) -> Result<()> { - if s.line.yank(text, anchor).is_some() { +fn edit_yank(s: &mut State, text: &str, anchor: Anchor, count: u16) -> Result<()> { + if s.line.yank(text, anchor, count).is_some() { s.refresh_line() } else { Ok(()) @@ -337,12 +337,12 @@ fn edit_yank(s: &mut State, text: &str, anchor: Anchor) -> Result<()> { // Delete previously yanked text and yank/paste `text` at current position. fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> { s.line.yank_pop(yank_size, text); - edit_yank(s, text, Anchor::Before) + edit_yank(s, text, Anchor::Before, 1) } /// Move cursor on the left. -fn edit_move_left(s: &mut State) -> Result<()> { - if s.line.move_left() { +fn edit_move_left(s: &mut State, count: u16) -> Result<()> { + if s.line.move_left(count) { s.refresh_line() } else { Ok(()) @@ -350,8 +350,8 @@ fn edit_move_left(s: &mut State) -> Result<()> { } /// Move cursor on the right. -fn edit_move_right(s: &mut State) -> Result<()> { - if s.line.move_right() { +fn edit_move_right(s: &mut State, count: u16) -> Result<()> { + if s.line.move_right(count) { s.refresh_line() } else { Ok(()) @@ -378,8 +378,8 @@ fn edit_move_end(s: &mut State) -> Result<()> { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. -fn edit_delete(s: &mut State) -> Result<()> { - if s.line.delete() { +fn edit_delete(s: &mut State, count: u16) -> Result<()> { + if s.line.delete(count) { s.refresh_line() } else { Ok(()) @@ -387,8 +387,8 @@ fn edit_delete(s: &mut State) -> Result<()> { } /// Backspace implementation. -fn edit_backspace(s: &mut State) -> Result<()> { - if s.line.backspace() { +fn edit_backspace(s: &mut State, count: u16) -> Result<()> { + if s.line.backspace(count) { s.refresh_line() } else { Ok(()) @@ -424,8 +424,8 @@ fn edit_transpose_chars(s: &mut State) -> Result<()> { } } -fn edit_move_to_prev_word(s: &mut State, word_def: Word) -> Result<()> { - if s.line.move_to_prev_word(word_def) { +fn edit_move_to_prev_word(s: &mut State, word_def: Word, count: u16) -> Result<()> { + if s.line.move_to_prev_word(word_def, count) { s.refresh_line() } else { Ok(()) @@ -434,8 +434,8 @@ fn edit_move_to_prev_word(s: &mut State, word_def: Word) -> Result<()> { /// Delete the previous word, maintaining the cursor at the start of the /// current word. -fn edit_delete_prev_word(s: &mut State, word_def: Word) -> Result> { - if let Some(text) = s.line.delete_prev_word(word_def) { +fn edit_delete_prev_word(s: &mut State, word_def: Word, count: u16) -> Result> { + if let Some(text) = s.line.delete_prev_word(word_def, count) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -443,16 +443,16 @@ fn edit_delete_prev_word(s: &mut State, word_def: Word) -> Result } } -fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word) -> Result<()> { - if s.line.move_to_next_word(at, word_def) { +fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word, count: u16) -> Result<()> { + if s.line.move_to_next_word(at, word_def, count) { s.refresh_line() } else { Ok(()) } } -fn edit_move_to(s: &mut State, cs: CharSearch) -> Result<()> { - if s.line.move_to(cs) { +fn edit_move_to(s: &mut State, cs: CharSearch, count: u16) -> Result<()> { + if s.line.move_to(cs, count) { s.refresh_line() } else { Ok(()) @@ -460,8 +460,8 @@ fn edit_move_to(s: &mut State, cs: CharSearch) -> Result<()> { } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. -fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result> { - if let Some(text) = s.line.delete_word(at, word_def) { +fn edit_delete_word(s: &mut State, at: At, word_def: Word, count: u16) -> Result> { + if let Some(text) = s.line.delete_word(at, word_def, count) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -469,8 +469,8 @@ fn edit_delete_word(s: &mut State, at: At, word_def: Word) -> Result Result> { - if let Some(text) = s.line.delete_to(cs) { +fn edit_delete_to(s: &mut State, cs: CharSearch, count: u16) -> Result> { + if let Some(text) = s.line.delete_to(cs, count) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -840,7 +840,7 @@ fn readline_edit(prompt: &str, if let Cmd::SelfInsert(c) = cmd { editor.kill_ring.reset(); - try!(edit_insert(&mut s, c)); + try!(edit_insert(&mut s, c, 1)); continue; } @@ -861,28 +861,28 @@ fn readline_edit(prompt: &str, // Move to the beginning of line. try!(edit_move_home(&mut s)) } - Cmd::BackwardChar(_) => { + Cmd::BackwardChar(count) => { editor.kill_ring.reset(); // Move back a character. - try!(edit_move_left(&mut s)) + try!(edit_move_left(&mut s, count)) } - Cmd::DeleteChar(_) => { + Cmd::DeleteChar(count) => { editor.kill_ring.reset(); // Delete (forward) one character at point. - try!(edit_delete(&mut s)) + try!(edit_delete(&mut s, count)) } - Cmd::Replace(_, c) => { + Cmd::Replace(count, c) => { editor.kill_ring.reset(); - try!(edit_delete(&mut s)); - try!(edit_insert(&mut s, c)); - try!(edit_move_left(&mut s)) + try!(edit_delete(&mut s, count)); + try!(edit_insert(&mut s, c, count)); + try!(edit_move_left(&mut s, 1)) } Cmd::EndOfFile => { editor.kill_ring.reset(); if !s.edit_state.is_emacs_mode() || s.line.is_empty() { return Err(error::ReadlineError::Eof); } else { - try!(edit_delete(&mut s)) + try!(edit_delete(&mut s, 1)) } } Cmd::EndOfLine => { @@ -890,15 +890,15 @@ fn readline_edit(prompt: &str, // Move to the end of line. try!(edit_move_end(&mut s)) } - Cmd::ForwardChar(_) => { + Cmd::ForwardChar(count) => { editor.kill_ring.reset(); // Move forward a character. - try!(edit_move_right(&mut s)) + try!(edit_move_right(&mut s, count)) } - Cmd::BackwardDeleteChar(_) => { + Cmd::BackwardDeleteChar(count) => { editor.kill_ring.reset(); // Delete one character backward. - try!(edit_backspace(&mut s)) + try!(edit_backspace(&mut s, count)) } Cmd::KillLine => { // Kill the text from point to the end of the line. @@ -943,12 +943,12 @@ fn readline_edit(prompt: &str, // Quoted insert editor.kill_ring.reset(); let c = try!(rdr.next_char()); - try!(edit_insert(&mut s, c)) // FIXME + try!(edit_insert(&mut s, c, 1)) // FIXME } - Cmd::Yank(_, anchor) => { + Cmd::Yank(count, anchor) => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { - try!(edit_yank(&mut s, text, anchor)) + try!(edit_yank(&mut s, text, anchor, count)) } } // TODO CTRL-_ // undo @@ -958,9 +958,9 @@ fn readline_edit(prompt: &str, try!(edit_move_end(&mut s)); break; } - Cmd::BackwardKillWord(_, word_def) => { + Cmd::BackwardKillWord(count, word_def) => { // kill one word backward - if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def)) { + if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def, count)) { editor.kill_ring.kill(&text, Mode::Prepend) } } @@ -974,26 +974,26 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, false)) } - Cmd::BackwardWord(_, word_def) => { + Cmd::BackwardWord(count, word_def) => { // move backwards one word editor.kill_ring.reset(); - try!(edit_move_to_prev_word(&mut s, word_def)) + try!(edit_move_to_prev_word(&mut s, word_def, count)) } Cmd::CapitalizeWord => { // capitalize word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - Cmd::KillWord(_, at, word_def) => { + Cmd::KillWord(count, at, word_def) => { // kill one word forward - if let Some(text) = try!(edit_delete_word(&mut s, at, word_def)) { + if let Some(text) = try!(edit_delete_word(&mut s, at, word_def, count)) { editor.kill_ring.kill(&text, Mode::Append) } } - Cmd::ForwardWord(_, at, word_def) => { + Cmd::ForwardWord(count, at, word_def) => { // move forwards one word editor.kill_ring.reset(); - try!(edit_move_to_next_word(&mut s, at, word_def)) + try!(edit_move_to_next_word(&mut s, at, word_def, count)) } Cmd::DowncaseWord => { // lowercase word after point @@ -1016,12 +1016,12 @@ fn readline_edit(prompt: &str, try!(edit_yank_pop(&mut s, yank_size, text)) } } - Cmd::ViCharSearch(_, cs) => { + Cmd::ViCharSearch(count, cs) => { editor.kill_ring.reset(); - try!(edit_move_to(&mut s, cs)) + try!(edit_move_to(&mut s, cs, count)) } - Cmd::ViDeleteTo(_, cs) => { - if let Some(text) = try!(edit_delete_to(&mut s, cs)) { + Cmd::ViDeleteTo(count, cs) => { + if let Some(text) = try!(edit_delete_to(&mut s, cs, count)) { editor.kill_ring.kill(&text, Mode::Append) } } diff --git a/src/line_buffer.rs b/src/line_buffer.rs index ce080cabfa..8a3cb871a2 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -126,13 +126,13 @@ impl LineBuffer { /// Yank/paste `text` at current position. /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. - pub fn yank(&mut self, text: &str, anchor: Anchor) -> Option { + pub fn yank(&mut self, text: &str, anchor: Anchor, count: u16) -> Option { let shift = text.len(); if text.is_empty() || (self.buf.len() + shift) > self.buf.capacity() { return None; } if let Anchor::After = anchor { - self.move_right(); + self.move_right(1); } let pos = self.pos; let push = self.insert_str(pos, text); @@ -144,27 +144,35 @@ impl LineBuffer { pub fn yank_pop(&mut self, yank_size: usize, text: &str) -> Option { self.buf.drain((self.pos - yank_size)..self.pos); self.pos -= yank_size; - self.yank(text, Anchor::Before) + self.yank(text, Anchor::Before, 1) } /// Move cursor on the left. - pub fn move_left(&mut self) -> bool { - if let Some(ch) = self.char_before_cursor() { - self.pos -= ch.len_utf8(); - true - } else { - false + pub fn move_left(&mut self, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(ch) = self.char_before_cursor() { + self.pos -= ch.len_utf8(); + moved = true + } else { + break; + } } + moved } /// Move cursor on the right. - pub fn move_right(&mut self) -> bool { - if let Some(ch) = self.char_at_cursor() { - self.pos += ch.len_utf8(); - true - } else { - false + pub fn move_right(&mut self, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(ch) = self.char_at_cursor() { + self.pos += ch.len_utf8(); + moved = true + } else { + break; + } } + moved } /// Move cursor to the start of the line. @@ -189,30 +197,42 @@ impl LineBuffer { /// Replace a single character under the cursor (Vi mode) pub fn replace_char(&mut self, ch: char) -> Option { - if self.delete() { self.insert(ch) } else { None } + if self.delete(1) { + self.insert(ch) + } else { + None + } } /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. - pub fn delete(&mut self) -> bool { - if !self.buf.is_empty() && self.pos < self.buf.len() { - self.buf.remove(self.pos); - true - } else { - false + pub fn delete(&mut self, count: u16) -> bool { + let mut deleted = false; + for _ in 0..count { + if !self.buf.is_empty() && self.pos < self.buf.len() { + self.buf.remove(self.pos); + deleted = true + } else { + break; + } } + deleted } /// Delete the character at the left of the cursor. /// Basically that is what happens with the "Backspace" keyboard key. - pub fn backspace(&mut self) -> bool { - if let Some(ch) = self.char_before_cursor() { - self.pos -= ch.len_utf8(); - self.buf.remove(self.pos); - true - } else { - false + pub fn backspace(&mut self, count: u16) -> bool { + let mut deleted = false; + for _ in 0..count { + if let Some(ch) = self.char_before_cursor() { + self.pos -= ch.len_utf8(); + self.buf.remove(self.pos); + deleted = true + } else { + break; + } } + deleted } /// Kill all characters on the current line. @@ -248,7 +268,7 @@ impl LineBuffer { return false; } if self.pos == self.buf.len() { - self.move_left(); + self.move_left(1); } let ch = self.buf.remove(self.pos); let size = ch.len_utf8(); @@ -292,18 +312,22 @@ impl LineBuffer { } /// Moves the cursor to the beginning of previous word. - pub fn move_to_prev_word(&mut self, word_def: Word) -> bool { - if let Some(pos) = self.prev_word_pos(self.pos, word_def) { - self.pos = pos; - true - } else { - false + pub fn move_to_prev_word(&mut self, word_def: Word, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(pos) = self.prev_word_pos(self.pos, word_def) { + self.pos = pos; + moved = true + } else { + break; + } } + moved } /// Delete the previous word, maintaining the cursor at the start of the /// current word. - pub fn delete_prev_word(&mut self, word_def: Word) -> Option { + pub fn delete_prev_word(&mut self, word_def: Word, count: u16) -> Option { if let Some(pos) = self.prev_word_pos(self.pos, word_def) { let word = self.buf.drain(pos..self.pos).collect(); self.pos = pos; @@ -378,13 +402,17 @@ impl LineBuffer { } /// Moves the cursor to the end of next word. - pub fn move_to_next_word(&mut self, at: At, word_def: Word) -> bool { - if let Some(pos) = self.next_pos(self.pos, at, word_def) { - self.pos = pos; - true - } else { - false + pub fn move_to_next_word(&mut self, at: At, word_def: Word, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(pos) = self.next_pos(self.pos, at, word_def) { + self.pos = pos; + moved = true + } else { + break; + } } + moved } fn search_char_pos(&mut self, cs: &CharSearch) -> Option { @@ -427,17 +455,21 @@ impl LineBuffer { } } - pub fn move_to(&mut self, cs: CharSearch) -> bool { - if let Some(pos) = self.search_char_pos(&cs) { - self.pos = pos; - true - } else { - false + pub fn move_to(&mut self, cs: CharSearch, count: u16) -> bool { + let mut moved = false; + for _ in 0..count { + if let Some(pos) = self.search_char_pos(&cs) { + self.pos = pos; + moved = true + } else { + break; + } } + moved } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. - pub fn delete_word(&mut self, at: At, word_def: Word) -> Option { + pub fn delete_word(&mut self, at: At, word_def: Word, count: u16) -> Option { if let Some(pos) = self.next_pos(self.pos, at, word_def) { let word = self.buf.drain(self.pos..pos).collect(); Some(word) @@ -446,7 +478,7 @@ impl LineBuffer { } } - pub fn delete_to(&mut self, cs: CharSearch) -> Option { + pub fn delete_to(&mut self, cs: CharSearch, count: u16) -> Option { let search_result = match cs { CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c)), _ => self.search_char_pos(&cs), @@ -626,12 +658,12 @@ mod test { #[test] fn moves() { let mut s = LineBuffer::init("αß", 4); - let ok = s.move_left(); + let ok = s.move_left(1); assert_eq!("αß", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); - let ok = s.move_right(); + let ok = s.move_right(1); assert_eq!("αß", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); @@ -650,12 +682,12 @@ mod test { #[test] fn delete() { let mut s = LineBuffer::init("αß", 2); - let ok = s.delete(); + let ok = s.delete(1); assert_eq!("α", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); - let ok = s.backspace(); + let ok = s.backspace(1); assert_eq!("", s.buf); assert_eq!(0, s.pos); assert_eq!(true, ok); @@ -702,7 +734,7 @@ mod test { #[test] fn move_to_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let ok = s.move_to_prev_word(Word::Emacs); + let ok = s.move_to_prev_word(Word::Emacs, 1); assert_eq!("a ß c", s.buf); assert_eq!(2, s.pos); assert_eq!(true, ok); @@ -711,7 +743,7 @@ mod test { #[test] fn delete_prev_word() { let mut s = LineBuffer::init("a ß c", 6); - let text = s.delete_prev_word(Word::Big); + let text = s.delete_prev_word(Word::Big, 1); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); @@ -720,7 +752,7 @@ mod test { #[test] fn move_to_next_word() { let mut s = LineBuffer::init("a ß c", 1); - let ok = s.move_to_next_word(At::End, Word::Emacs); + let ok = s.move_to_next_word(At::End, Word::Emacs, 1); assert_eq!("a ß c", s.buf); assert_eq!(4, s.pos); assert_eq!(true, ok); @@ -729,7 +761,7 @@ mod test { #[test] fn move_to_start_of_word() { let mut s = LineBuffer::init("a ß c", 2); - let ok = s.move_to_next_word(At::Start, Word::Emacs); + let ok = s.move_to_next_word(At::Start, Word::Emacs, 1); assert_eq!("a ß c", s.buf); assert_eq!(6, s.pos); assert_eq!(true, ok); @@ -738,7 +770,7 @@ mod test { #[test] fn delete_word() { let mut s = LineBuffer::init("a ß c", 1); - let text = s.delete_word(At::End, Word::Emacs); + let text = s.delete_word(At::End, Word::Emacs, 1); assert_eq!("a c", s.buf); assert_eq!(1, s.pos); assert_eq!(Some(" ß".to_string()), text); @@ -747,7 +779,7 @@ mod test { #[test] fn delete_til_start_of_word() { let mut s = LineBuffer::init("a ß c", 2); - let text = s.delete_word(At::Start, Word::Emacs); + let text = s.delete_word(At::Start, Word::Emacs, 1); assert_eq!("a c", s.buf); assert_eq!(2, s.pos); assert_eq!(Some("ß ".to_string()), text); From 8a40e713a7ff7414e6fdf590fb872b54d52f6122 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 1 Jan 2017 21:51:31 +0100 Subject: [PATCH 0262/1201] Misc --- src/completion.rs | 6 +++++- src/config.rs | 8 +++++++- src/keymap.rs | 15 ++++++++------- src/lib.rs | 15 ++++++++------- src/line_buffer.rs | 7 ++++--- src/tty/mod.rs | 4 +++- 6 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 868a4fbad2..28bb8e9938 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -20,7 +20,7 @@ pub trait Completer { /// Updates the edited `line` with the `elected` candidate. fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) { let end = line.pos(); - line.replace(start, end, elected) + line.replace(start..end, elected) } } @@ -60,6 +60,7 @@ use std::rc::Rc; use std::sync::Arc; box_completer! { Box Rc Arc } +/// A `Completer` for file and folder names. pub struct FilenameCompleter { break_chars: BTreeSet, } @@ -121,6 +122,9 @@ pub fn unescape(input: &str, esc_char: Option) -> Cow { Owned(result) } +/// Escape any `break_chars` in `input` string with `esc_char`. +/// For example, '/User Information' becomes '/User\ Information' +/// when space is a breaking char and '\' the escape char. pub fn escape(input: String, esc_char: Option, break_chars: &BTreeSet) -> String { if esc_char.is_none() { return input; diff --git a/src/config.rs b/src/config.rs index 2585c3e33a..d9966bcccd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,6 +13,7 @@ pub struct Config { completion_prompt_limit: usize, /// Duration (milliseconds) Rustyline will wait for a character when reading an ambiguous key sequence. keyseq_timeout: i32, + // Emacs or Vi mode edit_mode: EditMode, } @@ -131,17 +132,22 @@ impl Builder { self } + /// The number of possible completions that determines when the user is asked + /// whether the list of possibilities should be displayed. pub fn completion_prompt_limit(mut self, completion_prompt_limit: usize) -> Builder { self.p.completion_prompt_limit = completion_prompt_limit; self } - /// Set `keyseq_timeout` in milliseconds. + /// Timeout for ambiguous key sequences in milliseconds. + /// Currently, it is used only to distinguish a single ESC from an ESC sequence. + /// After seeing an ESC key, wait at most `keyseq_timeout_ms` for another byte. pub fn keyseq_timeout(mut self, keyseq_timeout_ms: i32) -> Builder { self.p.keyseq_timeout = keyseq_timeout_ms; self } + /// Choose between Emacs or Vi mode. pub fn edit_mode(mut self, edit_mode: EditMode) -> Builder { self.p.edit_mode = edit_mode; self diff --git a/src/keymap.rs b/src/keymap.rs index a59313a5ef..efadf78150 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -110,11 +110,12 @@ impl EditState { } } - fn digit_argument(&mut self, - rdr: &mut R, - config: &Config, - digit: char) - -> Result { + // TODO dynamic prompt (arg: ?) + fn emacs_digit_argument(&mut self, + rdr: &mut R, + config: &Config, + digit: char) + -> Result { match digit { '0'...'9' => { self.num_args = digit.to_digit(10).unwrap() as i16; @@ -139,9 +140,9 @@ impl EditState { fn emacs(&mut self, rdr: &mut R, config: &Config) -> Result { let mut key = try!(rdr.next_key(config.keyseq_timeout())); if let KeyPress::Meta(digit @ '-') = key { - key = try!(self.digit_argument(rdr, config, digit)); + key = try!(self.emacs_digit_argument(rdr, config, digit)); } else if let KeyPress::Meta(digit @ '0'...'9') = key { - key = try!(self.digit_argument(rdr, config, digit)); + key = try!(self.emacs_digit_argument(rdr, config, digit)); } let cmd = match key { KeyPress::Char(c) => Cmd::SelfInsert(c), diff --git a/src/lib.rs b/src/lib.rs index bd60786ce7..1b0c625fdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,10 +17,10 @@ #![allow(unknown_lints)] extern crate libc; +extern crate encode_unicode; +extern crate unicode_width; #[cfg(unix)] extern crate nix; -extern crate unicode_width; -extern crate encode_unicode; #[cfg(windows)] extern crate winapi; #[cfg(windows)] @@ -44,6 +44,8 @@ use std::io::{self, Write}; use std::mem; use std::path::Path; use std::result; +use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; + use tty::{RawMode, RawReader, Terminal, Term}; use encode_unicode::CharExt; @@ -288,7 +290,7 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { pos.row += 1; None } else { - unicode_width::UnicodeWidthChar::width(c) + c.width() }; if let Some(cw) = cw { pos.col += cw; @@ -309,7 +311,7 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { if let Some(push) = s.line.insert(ch) { if push { - if s.cursor.col + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) < s.cols { + if s.cursor.col + ch.width().unwrap_or(0) < s.cols { // Avoid a full update of the line in the trivial case. let cursor = calculate_position(&s.line[..s.line.pos()], s.prompt_size, s.cols); s.cursor = cursor; @@ -663,12 +665,11 @@ fn page_completions(rdr: &mut R, candidates: &[String]) -> Result> { use std::cmp; - use unicode_width::UnicodeWidthStr; let min_col_pad = 2; let max_width = cmp::min(s.cols, candidates.into_iter() - .map(|s| UnicodeWidthStr::width(s.as_str())) + .map(|s| s.as_str().width()) .max() .unwrap() + min_col_pad); let num_cols = s.cols / max_width; @@ -709,7 +710,7 @@ fn page_completions(rdr: &mut R, if i < candidates.len() { let candidate = &candidates[i]; ab.push_str(candidate); - let width = UnicodeWidthStr::width(candidate.as_str()); + let width = candidate.as_str().width(); if ((col + 1) * num_rows) + row < candidates.len() { for _ in width..max_width { ab.push(' '); diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 8a3cb871a2..bd059860db 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,5 +1,5 @@ //! Line buffer with current cursor position -use std::ops::Deref; +use std::ops::{Deref, Range}; use keymap::{Anchor, At, CharSearch, Word}; /// Maximum buffer size for the line read @@ -568,8 +568,9 @@ impl LineBuffer { } /// Replaces the content between [`start`..`end`] with `text` and positions the cursor to the end of text. - pub fn replace(&mut self, start: usize, end: usize, text: &str) { - self.buf.drain(start..end); + pub fn replace(&mut self, range: Range, text: &str) { + let start = range.start; + self.buf.drain(range); self.insert_str(start, text); self.pos = start + text.len(); } diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 2c4c4e3291..59d4e2a5e9 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -3,11 +3,13 @@ use std::io::Write; use ::Result; use consts::KeyPress; +/// Terminal state pub trait RawMode: Copy + Sized { /// Disable RAW mode for the terminal. fn disable_raw_mode(&self) -> Result<()>; } +/// Translate bytes read from stdin to keys. pub trait RawReader: Sized { /// Blocking read of key pressed. fn next_key(&mut self, timeout_ms: i32) -> Result; @@ -20,7 +22,7 @@ pub trait RawReader: Sized { pub trait Term: Clone { type Reader: RawReader; type Writer: Write; - type Mode; + type Mode: RawMode; fn new() -> Self; /// Check if current terminal can provide a rich line-editing user interface. From 38fbadd8e4722d076e66b0bed3f269dd650a062c Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 2 Jan 2017 20:13:03 +0100 Subject: [PATCH 0263/1201] Repeated insert and replace char --- src/lib.rs | 19 ++++++++++++++----- src/line_buffer.rs | 33 +++++++++++++++++---------------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1b0c625fdb..52a0bc7bc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -309,9 +309,9 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { /// Insert the character `ch` at cursor current position. fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { - if let Some(push) = s.line.insert(ch) { + if let Some(push) = s.line.insert(ch, count) { if push { - if s.cursor.col + ch.width().unwrap_or(0) < s.cols { + if count == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.cols { // Avoid a full update of the line in the trivial case. let cursor = calculate_position(&s.line[..s.line.pos()], s.prompt_size, s.cols); s.cursor = cursor; @@ -327,6 +327,17 @@ fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { } } +/// Replace a single (or count) character(s) under the cursor (Vi mode) +fn edit_replace_char(s: &mut State, ch: char, count: u16) -> Result<()> { + if s.line.delete(count) { + s.line.insert(ch, count); + s.line.move_left(1); + s.refresh_line() + } else { + Ok(()) + } +} + // Yank/paste `text` at current position. fn edit_yank(s: &mut State, text: &str, anchor: Anchor, count: u16) -> Result<()> { if s.line.yank(text, anchor, count).is_some() { @@ -874,9 +885,7 @@ fn readline_edit(prompt: &str, } Cmd::Replace(count, c) => { editor.kill_ring.reset(); - try!(edit_delete(&mut s, count)); - try!(edit_insert(&mut s, c, count)); - try!(edit_move_left(&mut s, 1)) + try!(edit_replace_char(&mut s, c, count)); } Cmd::EndOfFile => { editor.kill_ring.reset(); diff --git a/src/line_buffer.rs b/src/line_buffer.rs index bd059860db..fa113d0378 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1,4 +1,5 @@ //! Line buffer with current cursor position +use std::iter; use std::ops::{Deref, Range}; use keymap::{Anchor, At, CharSearch, Word}; @@ -108,16 +109,25 @@ impl LineBuffer { /// and advance cursor position accordingly. /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. - pub fn insert(&mut self, ch: char) -> Option { - let shift = ch.len_utf8(); + pub fn insert(&mut self, ch: char, count: u16) -> Option { + let shift = ch.len_utf8() * count as usize; if self.buf.len() + shift > self.buf.capacity() { return None; } let push = self.pos == self.buf.len(); if push { - self.buf.push(ch); + self.buf.reserve(shift); + for _ in 0..count { + self.buf.push(ch); + } } else { - self.buf.insert(self.pos, ch); + if count == 1 { + self.buf.insert(self.pos, ch); + } else { + let text = iter::repeat(ch).take(count as usize).collect::(); + let pos = self.pos; + self.insert_str(pos, &text); + } } self.pos += shift; Some(push) @@ -195,15 +205,6 @@ impl LineBuffer { } } - /// Replace a single character under the cursor (Vi mode) - pub fn replace_char(&mut self, ch: char) -> Option { - if self.delete(1) { - self.insert(ch) - } else { - None - } - } - /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. pub fn delete(&mut self, count: u16) -> bool { @@ -639,18 +640,18 @@ mod test { #[test] fn insert() { let mut s = LineBuffer::with_capacity(MAX_LINE); - let push = s.insert('α').unwrap(); + let push = s.insert('α', 1).unwrap(); assert_eq!("α", s.buf); assert_eq!(2, s.pos); assert_eq!(true, push); - let push = s.insert('ß').unwrap(); + let push = s.insert('ß', 1).unwrap(); assert_eq!("αß", s.buf); assert_eq!(4, s.pos); assert_eq!(true, push); s.pos = 0; - let push = s.insert('γ').unwrap(); + let push = s.insert('γ', 1).unwrap(); assert_eq!("γαß", s.buf); assert_eq!(2, s.pos); assert_eq!(false, push); From 3ad20ddcf8900945bcda7e23acfdd0dae6420e23 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 3 Jan 2017 18:34:13 +0100 Subject: [PATCH 0264/1201] Fix repeated replace char --- src/lib.rs | 7 ++++--- src/line_buffer.rs | 13 +++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 52a0bc7bc2..148a6710af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -329,8 +329,9 @@ fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { /// Replace a single (or count) character(s) under the cursor (Vi mode) fn edit_replace_char(s: &mut State, ch: char, count: u16) -> Result<()> { - if s.line.delete(count) { - s.line.insert(ch, count); + let n = s.line.delete(count); + if n > 0 { + s.line.insert(ch, n); s.line.move_left(1); s.refresh_line() } else { @@ -392,7 +393,7 @@ fn edit_move_end(s: &mut State) -> Result<()> { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. fn edit_delete(s: &mut State, count: u16) -> Result<()> { - if s.line.delete(count) { + if s.line.delete(count) > 0 { s.refresh_line() } else { Ok(()) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index fa113d0378..fff406038a 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -207,17 +207,18 @@ impl LineBuffer { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. - pub fn delete(&mut self, count: u16) -> bool { - let mut deleted = false; + /// Return the number of characters deleted. + pub fn delete(&mut self, count: u16) -> u16 { + let mut n = 0; for _ in 0..count { if !self.buf.is_empty() && self.pos < self.buf.len() { self.buf.remove(self.pos); - deleted = true + n += 1 } else { break; } } - deleted + n } /// Delete the character at the left of the cursor. @@ -684,10 +685,10 @@ mod test { #[test] fn delete() { let mut s = LineBuffer::init("αß", 2); - let ok = s.delete(1); + let n = s.delete(1); assert_eq!("α", s.buf); assert_eq!(2, s.pos); - assert_eq!(true, ok); + assert_eq!(1, n); let ok = s.backspace(1); assert_eq!("", s.buf); From 1641f98ac692ca6176e377af0d3246f3e9cdf864 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 3 Jan 2017 19:24:50 +0100 Subject: [PATCH 0265/1201] Repeated yank --- src/line_buffer.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index fff406038a..2fc36f660e 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -137,15 +137,24 @@ impl LineBuffer { /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. pub fn yank(&mut self, text: &str, anchor: Anchor, count: u16) -> Option { - let shift = text.len(); + let shift = text.len() * count as usize; if text.is_empty() || (self.buf.len() + shift) > self.buf.capacity() { return None; } if let Anchor::After = anchor { self.move_right(1); } - let pos = self.pos; - let push = self.insert_str(pos, text); + let push = self.pos == self.buf.len(); + if push { + self.buf.reserve(shift); + for _ in 0..count { + self.buf.push_str(text); + } + } else { + let text = iter::repeat(text).take(count as usize).collect::(); + let pos = self.pos; + self.insert_str(pos, &text); + } self.pos += shift; Some(push) } From 4e38d5d293d91cf512482ed65ccc7934203fba62 Mon Sep 17 00:00:00 2001 From: gwenn Date: Tue, 3 Jan 2017 20:37:49 +0100 Subject: [PATCH 0266/1201] Repeated insert char --- src/keymap.rs | 144 ++++++++++++++++++++++----------------------- src/lib.rs | 140 +++++++++++++++++++++---------------------- src/line_buffer.rs | 58 +++++++++--------- 3 files changed, 172 insertions(+), 170 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index efadf78150..f7a5acebe8 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -35,7 +35,7 @@ pub enum Cmd { QuotedInsert, Replace(u16, char), // TODO DeleteChar + SelfInsert ReverseSearchHistory, - SelfInsert(char), + SelfInsert(u16, char), Suspend, TransposeChars, TransposeWords, @@ -144,35 +144,39 @@ impl EditState { } else if let KeyPress::Meta(digit @ '0'...'9') = key { key = try!(self.emacs_digit_argument(rdr, config, digit)); } + let (n, positive) = self.emacs_num_args(); // consume them in all cases let cmd = match key { - KeyPress::Char(c) => Cmd::SelfInsert(c), + KeyPress::Char(c) => { + if positive { + Cmd::SelfInsert(n, c) + } else { + Cmd::Unknown // TODO ??? + } + } KeyPress::Ctrl('A') => Cmd::BeginningOfLine, KeyPress::Ctrl('B') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardChar(count) + Cmd::BackwardChar(n) } else { - Cmd::ForwardChar(count) + Cmd::ForwardChar(n) } } KeyPress::Ctrl('E') => Cmd::EndOfLine, KeyPress::Ctrl('F') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::ForwardChar(count) + Cmd::ForwardChar(n) } else { - Cmd::BackwardChar(count) + Cmd::BackwardChar(n) } } KeyPress::Ctrl('G') | KeyPress::Esc => Cmd::Abort, KeyPress::Ctrl('H') | KeyPress::Backspace => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardDeleteChar(count) + Cmd::BackwardDeleteChar(n) } else { - Cmd::DeleteChar(count) + Cmd::DeleteChar(n) } } KeyPress::Tab => Cmd::Complete, @@ -182,45 +186,41 @@ impl EditState { KeyPress::Ctrl('P') => Cmd::PreviousHistory, KeyPress::Meta('\x08') | KeyPress::Meta('\x7f') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardKillWord(count, Word::Emacs) + Cmd::BackwardKillWord(n, Word::Emacs) } else { - Cmd::KillWord(count, At::End, Word::Emacs) + Cmd::KillWord(n, At::End, Word::Emacs) } } KeyPress::Meta('<') => Cmd::BeginningOfHistory, KeyPress::Meta('>') => Cmd::EndOfHistory, KeyPress::Meta('B') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardWord(count, Word::Emacs) + Cmd::BackwardWord(n, Word::Emacs) } else { - Cmd::ForwardWord(count, At::End, Word::Emacs) + Cmd::ForwardWord(n, At::End, Word::Emacs) } } KeyPress::Meta('C') => Cmd::CapitalizeWord, KeyPress::Meta('D') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::KillWord(count, At::End, Word::Emacs) + Cmd::KillWord(n, At::End, Word::Emacs) } else { - Cmd::BackwardKillWord(count, Word::Emacs) + Cmd::BackwardKillWord(n, Word::Emacs) } } KeyPress::Meta('F') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::ForwardWord(count, At::End, Word::Emacs) + Cmd::ForwardWord(n, At::End, Word::Emacs) } else { - Cmd::BackwardWord(count, Word::Emacs) + Cmd::BackwardWord(n, Word::Emacs) } } KeyPress::Meta('L') => Cmd::DowncaseWord, KeyPress::Meta('T') => Cmd::TransposeWords, KeyPress::Meta('U') => Cmd::UpcaseWord, KeyPress::Meta('Y') => Cmd::YankPop, - _ => self.common(key), + _ => self.common(key, n, positive), }; Ok(cmd) } @@ -247,6 +247,7 @@ impl EditState { if let KeyPress::Char(digit @ '1'...'9') = key { key = try!(self.vi_arg_digit(rdr, config, digit)); } + let n = self.vi_num_args(); // consume them in all cases let cmd = match key { KeyPress::Char('$') | KeyPress::End => Cmd::EndOfLine, @@ -256,28 +257,28 @@ impl EditState { KeyPress::Char('a') => { // vi-append-mode: Vi enter insert mode after the cursor. self.insert = true; - Cmd::ForwardChar(self.vi_num_args()) + Cmd::ForwardChar(n) } KeyPress::Char('A') => { // vi-append-eol: Vi enter insert mode at end of line. self.insert = true; Cmd::EndOfLine } - KeyPress::Char('b') => Cmd::BackwardWord(self.vi_num_args(), Word::Vi), // vi-prev-word - KeyPress::Char('B') => Cmd::BackwardWord(self.vi_num_args(), Word::Big), + KeyPress::Char('b') => Cmd::BackwardWord(n, Word::Vi), // vi-prev-word + KeyPress::Char('B') => Cmd::BackwardWord(n, Word::Big), KeyPress::Char('c') => { self.insert = true; - try!(self.vi_delete_motion(rdr, config, key)) + try!(self.vi_delete_motion(rdr, config, key, n)) } KeyPress::Char('C') => { self.insert = true; Cmd::KillLine } - KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key)), + KeyPress::Char('d') => try!(self.vi_delete_motion(rdr, config, key, n)), KeyPress::Char('D') | KeyPress::Ctrl('K') => Cmd::KillLine, - KeyPress::Char('e') => Cmd::ForwardWord(self.vi_num_args(), At::End, Word::Vi), - KeyPress::Char('E') => Cmd::ForwardWord(self.vi_num_args(), At::End, Word::Big), + KeyPress::Char('e') => Cmd::ForwardWord(n, At::End, Word::Vi), + KeyPress::Char('E') => Cmd::ForwardWord(n, At::End, Word::Big), KeyPress::Char('i') => { // vi-insertion-mode self.insert = true; @@ -292,18 +293,18 @@ impl EditState { // vi-char-search let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViCharSearch(self.vi_num_args(), cs), + Some(cs) => Cmd::ViCharSearch(n, cs), None => Cmd::Unknown, } } // TODO KeyPress::Char('G') => Cmd::???, Move to the history line n - KeyPress::Char('p') => Cmd::Yank(self.vi_num_args(), Anchor::After), // vi-put, FIXME cursor position - KeyPress::Char('P') => Cmd::Yank(self.vi_num_args(), Anchor::Before), // vi-put, FIXME cursor position + KeyPress::Char('p') => Cmd::Yank(n, Anchor::After), // vi-put, FIXME cursor position + KeyPress::Char('P') => Cmd::Yank(n, Anchor::Before), // vi-put, FIXME cursor position KeyPress::Char('r') => { // vi-replace-char: Vi replace character under the cursor with the next character typed. let ch = try!(rdr.next_key(config.keyseq_timeout())); match ch { - KeyPress::Char(c) => Cmd::Replace(self.vi_num_args(), c), + KeyPress::Char(c) => Cmd::Replace(n, c), KeyPress::Esc => Cmd::Noop, _ => Cmd::Unknown, } @@ -312,7 +313,7 @@ impl EditState { KeyPress::Char('s') => { // vi-substitute-char: Vi replace character under the cursor and enter insert mode. self.insert = true; - Cmd::DeleteChar(self.vi_num_args()) + Cmd::DeleteChar(n) } KeyPress::Char('S') => { // vi-substitute-line: Vi substitute entire line. @@ -320,18 +321,18 @@ impl EditState { Cmd::KillWholeLine } // KeyPress::Char('U') => Cmd::???, // revert-line - KeyPress::Char('w') => Cmd::ForwardWord(self.vi_num_args(), At::Start, Word::Vi), // vi-next-word - KeyPress::Char('W') => Cmd::ForwardWord(self.vi_num_args(), At::Start, Word::Big), // vi-next-word - KeyPress::Char('x') => Cmd::DeleteChar(self.vi_num_args()), // vi-delete: TODO move backward if eol - KeyPress::Char('X') => Cmd::BackwardDeleteChar(self.vi_num_args()), // vi-rubout + KeyPress::Char('w') => Cmd::ForwardWord(n, At::Start, Word::Vi), // vi-next-word + KeyPress::Char('W') => Cmd::ForwardWord(n, At::Start, Word::Big), // vi-next-word + KeyPress::Char('x') => Cmd::DeleteChar(n), // vi-delete: TODO move backward if eol + KeyPress::Char('X') => Cmd::BackwardDeleteChar(n), // vi-rubout // KeyPress::Char('y') => Cmd::???, // vi-yank-to // KeyPress::Char('Y') => Cmd::???, // vi-yank-to KeyPress::Char('h') | KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardChar(self.vi_num_args()), // TODO Validate + KeyPress::Backspace => Cmd::BackwardChar(n), // TODO Validate KeyPress::Ctrl('G') => Cmd::Abort, KeyPress::Char('l') | - KeyPress::Char(' ') => Cmd::ForwardChar(self.vi_num_args()), + KeyPress::Char(' ') => Cmd::ForwardChar(n), KeyPress::Ctrl('L') => Cmd::ClearScreen, KeyPress::Char('+') | KeyPress::Char('j') | @@ -348,7 +349,7 @@ impl EditState { Cmd::ForwardSearchHistory } KeyPress::Esc => Cmd::Noop, - _ => self.common(key), + _ => self.common(key, n, true), }; Ok(cmd) } @@ -356,7 +357,7 @@ impl EditState { fn vi_insert(&mut self, rdr: &mut R, config: &Config) -> Result { let key = try!(rdr.next_key(config.keyseq_timeout())); let cmd = match key { - KeyPress::Char(c) => Cmd::SelfInsert(c), + KeyPress::Char(c) => Cmd::SelfInsert(1, c), KeyPress::Ctrl('H') | KeyPress::Backspace => Cmd::BackwardDeleteChar(1), KeyPress::Tab => Cmd::Complete, @@ -365,7 +366,7 @@ impl EditState { self.insert = false; Cmd::BackwardChar(1) } - _ => self.common(key), + _ => self.common(key, 1, true), }; Ok(cmd) } @@ -373,37 +374,40 @@ impl EditState { fn vi_delete_motion(&mut self, rdr: &mut R, config: &Config, - key: KeyPress) + key: KeyPress, + n: u16) -> Result { let mut mvt = try!(rdr.next_key(config.keyseq_timeout())); if mvt == key { return Ok(Cmd::KillWholeLine); } + let mut n = n; if let KeyPress::Char(digit @ '1'...'9') = mvt { // vi-arg-digit mvt = try!(self.vi_arg_digit(rdr, config, digit)); + n = self.vi_num_args(); } Ok(match mvt { KeyPress::Char('$') => Cmd::KillLine, // vi-change-to-eol: Vi change to end of line. KeyPress::Char('0') => Cmd::UnixLikeDiscard, // vi-kill-line-prev: Vi cut from beginning of line to cursor. - KeyPress::Char('b') => Cmd::BackwardKillWord(self.vi_num_args(), Word::Vi), - KeyPress::Char('B') => Cmd::BackwardKillWord(self.vi_num_args(), Word::Big), - KeyPress::Char('e') => Cmd::KillWord(self.vi_num_args(), At::End, Word::Vi), - KeyPress::Char('E') => Cmd::KillWord(self.vi_num_args(), At::End, Word::Big), + KeyPress::Char('b') => Cmd::BackwardKillWord(n, Word::Vi), + KeyPress::Char('B') => Cmd::BackwardKillWord(n, Word::Big), + KeyPress::Char('e') => Cmd::KillWord(n, At::End, Word::Vi), + KeyPress::Char('E') => Cmd::KillWord(n, At::End, Word::Big), KeyPress::Char(c) if c == 'f' || c == 'F' || c == 't' || c == 'T' => { let cs = try!(self.vi_char_search(rdr, config, c)); match cs { - Some(cs) => Cmd::ViDeleteTo(self.vi_num_args(), cs), + Some(cs) => Cmd::ViDeleteTo(n, cs), None => Cmd::Unknown, } } KeyPress::Char('h') | KeyPress::Ctrl('H') | - KeyPress::Backspace => Cmd::BackwardDeleteChar(self.vi_num_args()), // vi-delete-prev-char: Vi move to previous character (backspace). + KeyPress::Backspace => Cmd::BackwardDeleteChar(n), // vi-delete-prev-char: Vi move to previous character (backspace). KeyPress::Char('l') | - KeyPress::Char(' ') => Cmd::DeleteChar(self.vi_num_args()), - KeyPress::Char('w') => Cmd::KillWord(self.vi_num_args(), At::Start, Word::Vi), - KeyPress::Char('W') => Cmd::KillWord(self.vi_num_args(), At::Start, Word::Big), + KeyPress::Char(' ') => Cmd::DeleteChar(n), + KeyPress::Char('w') => Cmd::KillWord(n, At::Start, Word::Vi), + KeyPress::Char('W') => Cmd::KillWord(n, At::Start, Word::Big), _ => Cmd::Unknown, }) } @@ -428,34 +432,31 @@ impl EditState { }) } - fn common(&mut self, key: KeyPress) -> Cmd { + fn common(&mut self, key: KeyPress, n: u16, positive: bool) -> Cmd { match key { KeyPress::Home => Cmd::BeginningOfLine, KeyPress::Left => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardChar(count) + Cmd::BackwardChar(n) } else { - Cmd::ForwardChar(count) + Cmd::ForwardChar(n) } } KeyPress::Ctrl('C') => Cmd::Interrupt, KeyPress::Ctrl('D') => Cmd::EndOfFile, KeyPress::Delete => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::DeleteChar(count) + Cmd::DeleteChar(n) } else { - Cmd::BackwardDeleteChar(count) + Cmd::BackwardDeleteChar(n) } } KeyPress::End => Cmd::EndOfLine, KeyPress::Right => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::ForwardChar(count) + Cmd::ForwardChar(n) } else { - Cmd::BackwardChar(count) + Cmd::BackwardChar(n) } } KeyPress::Ctrl('J') | @@ -469,17 +470,15 @@ impl EditState { KeyPress::Ctrl('Q') | // most terminals override Ctrl+Q to resume execution KeyPress::Ctrl('V') => Cmd::QuotedInsert, KeyPress::Ctrl('W') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::BackwardKillWord(count, Word::Big) + Cmd::BackwardKillWord(n, Word::Big) } else { - Cmd::KillWord(count, At::End, Word::Big) + Cmd::KillWord(n, At::End, Word::Big) } } KeyPress::Ctrl('Y') => { - let (count, positive) = self.emacs_num_args(); if positive { - Cmd::Yank(count, Anchor::Before) + Cmd::Yank(n, Anchor::Before) } else { Cmd::Unknown // TODO Validate } @@ -489,6 +488,7 @@ impl EditState { _ => Cmd::Unknown, } } + fn num_args(&mut self) -> i16 { let num_args = match self.num_args { 0 => 1, @@ -501,8 +501,8 @@ impl EditState { fn emacs_num_args(&mut self) -> (u16, bool) { let num_args = self.num_args(); if num_args < 0 { - if let (count, false) = num_args.overflowing_abs() { - (count as u16, false) + if let (n, false) = num_args.overflowing_abs() { + (n as u16, false) } else { (u16::max_value(), false) } diff --git a/src/lib.rs b/src/lib.rs index 148a6710af..fa57ccd7bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -308,10 +308,10 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { } /// Insert the character `ch` at cursor current position. -fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { - if let Some(push) = s.line.insert(ch, count) { +fn edit_insert(s: &mut State, ch: char, n: u16) -> Result<()> { + if let Some(push) = s.line.insert(ch, n) { if push { - if count == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.cols { + if n == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.cols { // Avoid a full update of the line in the trivial case. let cursor = calculate_position(&s.line[..s.line.pos()], s.prompt_size, s.cols); s.cursor = cursor; @@ -327,11 +327,11 @@ fn edit_insert(s: &mut State, ch: char, count: u16) -> Result<()> { } } -/// Replace a single (or count) character(s) under the cursor (Vi mode) -fn edit_replace_char(s: &mut State, ch: char, count: u16) -> Result<()> { - let n = s.line.delete(count); - if n > 0 { - s.line.insert(ch, n); +/// Replace a single (or n) character(s) under the cursor (Vi mode) +fn edit_replace_char(s: &mut State, ch: char, n: u16) -> Result<()> { + let count = s.line.delete(n); + if count > 0 { + s.line.insert(ch, count); s.line.move_left(1); s.refresh_line() } else { @@ -340,8 +340,8 @@ fn edit_replace_char(s: &mut State, ch: char, count: u16) -> Result<()> { } // Yank/paste `text` at current position. -fn edit_yank(s: &mut State, text: &str, anchor: Anchor, count: u16) -> Result<()> { - if s.line.yank(text, anchor, count).is_some() { +fn edit_yank(s: &mut State, text: &str, anchor: Anchor, n: u16) -> Result<()> { + if s.line.yank(text, anchor, n).is_some() { s.refresh_line() } else { Ok(()) @@ -355,8 +355,8 @@ fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> { } /// Move cursor on the left. -fn edit_move_left(s: &mut State, count: u16) -> Result<()> { - if s.line.move_left(count) { +fn edit_move_left(s: &mut State, n: u16) -> Result<()> { + if s.line.move_left(n) { s.refresh_line() } else { Ok(()) @@ -364,8 +364,8 @@ fn edit_move_left(s: &mut State, count: u16) -> Result<()> { } /// Move cursor on the right. -fn edit_move_right(s: &mut State, count: u16) -> Result<()> { - if s.line.move_right(count) { +fn edit_move_right(s: &mut State, n: u16) -> Result<()> { + if s.line.move_right(n) { s.refresh_line() } else { Ok(()) @@ -392,8 +392,8 @@ fn edit_move_end(s: &mut State) -> Result<()> { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. -fn edit_delete(s: &mut State, count: u16) -> Result<()> { - if s.line.delete(count) > 0 { +fn edit_delete(s: &mut State, n: u16) -> Result<()> { + if s.line.delete(n) > 0 { s.refresh_line() } else { Ok(()) @@ -401,8 +401,8 @@ fn edit_delete(s: &mut State, count: u16) -> Result<()> { } /// Backspace implementation. -fn edit_backspace(s: &mut State, count: u16) -> Result<()> { - if s.line.backspace(count) { +fn edit_backspace(s: &mut State, n: u16) -> Result<()> { + if s.line.backspace(n) { s.refresh_line() } else { Ok(()) @@ -438,8 +438,8 @@ fn edit_transpose_chars(s: &mut State) -> Result<()> { } } -fn edit_move_to_prev_word(s: &mut State, word_def: Word, count: u16) -> Result<()> { - if s.line.move_to_prev_word(word_def, count) { +fn edit_move_to_prev_word(s: &mut State, word_def: Word, n: u16) -> Result<()> { + if s.line.move_to_prev_word(word_def, n) { s.refresh_line() } else { Ok(()) @@ -448,8 +448,8 @@ fn edit_move_to_prev_word(s: &mut State, word_def: Word, count: u16) -> Result<( /// Delete the previous word, maintaining the cursor at the start of the /// current word. -fn edit_delete_prev_word(s: &mut State, word_def: Word, count: u16) -> Result> { - if let Some(text) = s.line.delete_prev_word(word_def, count) { +fn edit_delete_prev_word(s: &mut State, word_def: Word, n: u16) -> Result> { + if let Some(text) = s.line.delete_prev_word(word_def, n) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -457,16 +457,16 @@ fn edit_delete_prev_word(s: &mut State, word_def: Word, count: u16) -> Result Result<()> { - if s.line.move_to_next_word(at, word_def, count) { +fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word, n: u16) -> Result<()> { + if s.line.move_to_next_word(at, word_def, n) { s.refresh_line() } else { Ok(()) } } -fn edit_move_to(s: &mut State, cs: CharSearch, count: u16) -> Result<()> { - if s.line.move_to(cs, count) { +fn edit_move_to(s: &mut State, cs: CharSearch, n: u16) -> Result<()> { + if s.line.move_to(cs, n) { s.refresh_line() } else { Ok(()) @@ -474,8 +474,8 @@ fn edit_move_to(s: &mut State, cs: CharSearch, count: u16) -> Result<()> { } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. -fn edit_delete_word(s: &mut State, at: At, word_def: Word, count: u16) -> Result> { - if let Some(text) = s.line.delete_word(at, word_def, count) { +fn edit_delete_word(s: &mut State, at: At, word_def: Word, n: u16) -> Result> { + if let Some(text) = s.line.delete_word(at, word_def, n) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -483,8 +483,8 @@ fn edit_delete_word(s: &mut State, at: At, word_def: Word, count: u16) -> Result } } -fn edit_delete_to(s: &mut State, cs: CharSearch, count: u16) -> Result> { - if let Some(text) = s.line.delete_to(cs, count) { +fn edit_delete_to(s: &mut State, cs: CharSearch, n: u16) -> Result> { + if let Some(text) = s.line.delete_to(cs, n) { try!(s.refresh_line()); Ok(Some(text)) } else { @@ -649,14 +649,15 @@ fn complete_line(rdr: &mut R, let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len()); try!(write_and_flush(s.out, msg.as_bytes())); s.old_rows += 1; - while cmd != Cmd::SelfInsert('y') && cmd != Cmd::SelfInsert('Y') && - cmd != Cmd::SelfInsert('n') && cmd != Cmd::SelfInsert('N') && + while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y') && + cmd != Cmd::SelfInsert(1, 'n') && + cmd != Cmd::SelfInsert(1, 'N') && cmd != Cmd::BackwardDeleteChar(1) { cmd = try!(s.next_cmd(rdr, config)); } show_completions = match cmd { - Cmd::SelfInsert('y') | - Cmd::SelfInsert('Y') => true, + Cmd::SelfInsert(1, 'y') | + Cmd::SelfInsert(1, 'Y') => true, _ => false, }; } @@ -693,18 +694,19 @@ fn page_completions(rdr: &mut R, if row == pause_row { try!(write_and_flush(s.out, b"\n--More--")); let mut cmd = Cmd::Noop; - while cmd != Cmd::SelfInsert('y') && cmd != Cmd::SelfInsert('Y') && - cmd != Cmd::SelfInsert('n') && cmd != Cmd::SelfInsert('N') && - cmd != Cmd::SelfInsert('q') && - cmd != Cmd::SelfInsert('Q') && - cmd != Cmd::SelfInsert(' ') && + while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1_, 'Y') && + cmd != Cmd::SelfInsert(1, 'n') && + cmd != Cmd::SelfInsert(1_, 'N') && + cmd != Cmd::SelfInsert(1, 'q') && + cmd != Cmd::SelfInsert(1, 'Q') && + cmd != Cmd::SelfInsert(1, ' ') && cmd != Cmd::BackwardDeleteChar(1) && cmd != Cmd::AcceptLine { cmd = try!(s.next_cmd(rdr, config)); } match cmd { - Cmd::SelfInsert('y') | - Cmd::SelfInsert('Y') | - Cmd::SelfInsert(' ') => { + Cmd::SelfInsert(1, 'y') | + Cmd::SelfInsert(1, 'Y') | + Cmd::SelfInsert(1, ' ') => { pause_row += s.term.get_rows() - 1; } Cmd::AcceptLine => { @@ -765,7 +767,7 @@ fn reverse_incremental_search(rdr: &mut R, try!(s.refresh_prompt_and_line(&prompt)); cmd = try!(s.next_cmd(rdr, config)); - if let Cmd::SelfInsert(c) = cmd { + if let Cmd::SelfInsert(_, c) = cmd { search_buf.push(c); } else { match cmd { @@ -851,9 +853,9 @@ fn readline_edit(prompt: &str, } } - if let Cmd::SelfInsert(c) = cmd { + if let Cmd::SelfInsert(n, c) = cmd { editor.kill_ring.reset(); - try!(edit_insert(&mut s, c, 1)); + try!(edit_insert(&mut s, c, n)); continue; } @@ -874,19 +876,19 @@ fn readline_edit(prompt: &str, // Move to the beginning of line. try!(edit_move_home(&mut s)) } - Cmd::BackwardChar(count) => { + Cmd::BackwardChar(n) => { editor.kill_ring.reset(); // Move back a character. - try!(edit_move_left(&mut s, count)) + try!(edit_move_left(&mut s, n)) } - Cmd::DeleteChar(count) => { + Cmd::DeleteChar(n) => { editor.kill_ring.reset(); // Delete (forward) one character at point. - try!(edit_delete(&mut s, count)) + try!(edit_delete(&mut s, n)) } - Cmd::Replace(count, c) => { + Cmd::Replace(n, c) => { editor.kill_ring.reset(); - try!(edit_replace_char(&mut s, c, count)); + try!(edit_replace_char(&mut s, c, n)); } Cmd::EndOfFile => { editor.kill_ring.reset(); @@ -901,15 +903,15 @@ fn readline_edit(prompt: &str, // Move to the end of line. try!(edit_move_end(&mut s)) } - Cmd::ForwardChar(count) => { + Cmd::ForwardChar(n) => { editor.kill_ring.reset(); // Move forward a character. - try!(edit_move_right(&mut s, count)) + try!(edit_move_right(&mut s, n)) } - Cmd::BackwardDeleteChar(count) => { + Cmd::BackwardDeleteChar(n) => { editor.kill_ring.reset(); // Delete one character backward. - try!(edit_backspace(&mut s, count)) + try!(edit_backspace(&mut s, n)) } Cmd::KillLine => { // Kill the text from point to the end of the line. @@ -956,10 +958,10 @@ fn readline_edit(prompt: &str, let c = try!(rdr.next_char()); try!(edit_insert(&mut s, c, 1)) // FIXME } - Cmd::Yank(count, anchor) => { + Cmd::Yank(n, anchor) => { // retrieve (yank) last item killed if let Some(text) = editor.kill_ring.yank() { - try!(edit_yank(&mut s, text, anchor, count)) + try!(edit_yank(&mut s, text, anchor, n)) } } // TODO CTRL-_ // undo @@ -969,9 +971,9 @@ fn readline_edit(prompt: &str, try!(edit_move_end(&mut s)); break; } - Cmd::BackwardKillWord(count, word_def) => { + Cmd::BackwardKillWord(n, word_def) => { // kill one word backward - if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def, count)) { + if let Some(text) = try!(edit_delete_prev_word(&mut s, word_def, n)) { editor.kill_ring.kill(&text, Mode::Prepend) } } @@ -985,26 +987,26 @@ fn readline_edit(prompt: &str, editor.kill_ring.reset(); try!(edit_history(&mut s, &editor.history, false)) } - Cmd::BackwardWord(count, word_def) => { + Cmd::BackwardWord(n, word_def) => { // move backwards one word editor.kill_ring.reset(); - try!(edit_move_to_prev_word(&mut s, word_def, count)) + try!(edit_move_to_prev_word(&mut s, word_def, n)) } Cmd::CapitalizeWord => { // capitalize word after point editor.kill_ring.reset(); try!(edit_word(&mut s, WordAction::CAPITALIZE)) } - Cmd::KillWord(count, at, word_def) => { + Cmd::KillWord(n, at, word_def) => { // kill one word forward - if let Some(text) = try!(edit_delete_word(&mut s, at, word_def, count)) { + if let Some(text) = try!(edit_delete_word(&mut s, at, word_def, n)) { editor.kill_ring.kill(&text, Mode::Append) } } - Cmd::ForwardWord(count, at, word_def) => { + Cmd::ForwardWord(n, at, word_def) => { // move forwards one word editor.kill_ring.reset(); - try!(edit_move_to_next_word(&mut s, at, word_def, count)) + try!(edit_move_to_next_word(&mut s, at, word_def, n)) } Cmd::DowncaseWord => { // lowercase word after point @@ -1027,12 +1029,12 @@ fn readline_edit(prompt: &str, try!(edit_yank_pop(&mut s, yank_size, text)) } } - Cmd::ViCharSearch(count, cs) => { + Cmd::ViCharSearch(n, cs) => { editor.kill_ring.reset(); - try!(edit_move_to(&mut s, cs, count)) + try!(edit_move_to(&mut s, cs, n)) } - Cmd::ViDeleteTo(count, cs) => { - if let Some(text) = try!(edit_delete_to(&mut s, cs, count)) { + Cmd::ViDeleteTo(n, cs) => { + if let Some(text) = try!(edit_delete_to(&mut s, cs, n)) { editor.kill_ring.kill(&text, Mode::Append) } } diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 2fc36f660e..447eac0e39 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -109,22 +109,22 @@ impl LineBuffer { /// and advance cursor position accordingly. /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. - pub fn insert(&mut self, ch: char, count: u16) -> Option { - let shift = ch.len_utf8() * count as usize; + pub fn insert(&mut self, ch: char, n: u16) -> Option { + let shift = ch.len_utf8() * n as usize; if self.buf.len() + shift > self.buf.capacity() { return None; } let push = self.pos == self.buf.len(); if push { self.buf.reserve(shift); - for _ in 0..count { + for _ in 0..n { self.buf.push(ch); } } else { - if count == 1 { + if n == 1 { self.buf.insert(self.pos, ch); } else { - let text = iter::repeat(ch).take(count as usize).collect::(); + let text = iter::repeat(ch).take(n as usize).collect::(); let pos = self.pos; self.insert_str(pos, &text); } @@ -136,8 +136,8 @@ impl LineBuffer { /// Yank/paste `text` at current position. /// Return `None` when maximum buffer size has been reached, /// `true` when the character has been appended to the end of the line. - pub fn yank(&mut self, text: &str, anchor: Anchor, count: u16) -> Option { - let shift = text.len() * count as usize; + pub fn yank(&mut self, text: &str, anchor: Anchor, n: u16) -> Option { + let shift = text.len() * n as usize; if text.is_empty() || (self.buf.len() + shift) > self.buf.capacity() { return None; } @@ -147,11 +147,11 @@ impl LineBuffer { let push = self.pos == self.buf.len(); if push { self.buf.reserve(shift); - for _ in 0..count { + for _ in 0..n { self.buf.push_str(text); } } else { - let text = iter::repeat(text).take(count as usize).collect::(); + let text = iter::repeat(text).take(n as usize).collect::(); let pos = self.pos; self.insert_str(pos, &text); } @@ -167,9 +167,9 @@ impl LineBuffer { } /// Move cursor on the left. - pub fn move_left(&mut self, count: u16) -> bool { + pub fn move_left(&mut self, n: u16) -> bool { let mut moved = false; - for _ in 0..count { + for _ in 0..n { if let Some(ch) = self.char_before_cursor() { self.pos -= ch.len_utf8(); moved = true @@ -181,9 +181,9 @@ impl LineBuffer { } /// Move cursor on the right. - pub fn move_right(&mut self, count: u16) -> bool { + pub fn move_right(&mut self, n: u16) -> bool { let mut moved = false; - for _ in 0..count { + for _ in 0..n { if let Some(ch) = self.char_at_cursor() { self.pos += ch.len_utf8(); moved = true @@ -217,24 +217,24 @@ impl LineBuffer { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. /// Return the number of characters deleted. - pub fn delete(&mut self, count: u16) -> u16 { - let mut n = 0; - for _ in 0..count { + pub fn delete(&mut self, n: u16) -> u16 { + let mut count = 0; + for _ in 0..n { if !self.buf.is_empty() && self.pos < self.buf.len() { self.buf.remove(self.pos); - n += 1 + count += 1 } else { break; } } - n + count } /// Delete the character at the left of the cursor. /// Basically that is what happens with the "Backspace" keyboard key. - pub fn backspace(&mut self, count: u16) -> bool { + pub fn backspace(&mut self, n: u16) -> bool { let mut deleted = false; - for _ in 0..count { + for _ in 0..n { if let Some(ch) = self.char_before_cursor() { self.pos -= ch.len_utf8(); self.buf.remove(self.pos); @@ -323,9 +323,9 @@ impl LineBuffer { } /// Moves the cursor to the beginning of previous word. - pub fn move_to_prev_word(&mut self, word_def: Word, count: u16) -> bool { + pub fn move_to_prev_word(&mut self, word_def: Word, n: u16) -> bool { let mut moved = false; - for _ in 0..count { + for _ in 0..n { if let Some(pos) = self.prev_word_pos(self.pos, word_def) { self.pos = pos; moved = true @@ -338,7 +338,7 @@ impl LineBuffer { /// Delete the previous word, maintaining the cursor at the start of the /// current word. - pub fn delete_prev_word(&mut self, word_def: Word, count: u16) -> Option { + pub fn delete_prev_word(&mut self, word_def: Word, n: u16) -> Option { if let Some(pos) = self.prev_word_pos(self.pos, word_def) { let word = self.buf.drain(pos..self.pos).collect(); self.pos = pos; @@ -413,9 +413,9 @@ impl LineBuffer { } /// Moves the cursor to the end of next word. - pub fn move_to_next_word(&mut self, at: At, word_def: Word, count: u16) -> bool { + pub fn move_to_next_word(&mut self, at: At, word_def: Word, n: u16) -> bool { let mut moved = false; - for _ in 0..count { + for _ in 0..n { if let Some(pos) = self.next_pos(self.pos, at, word_def) { self.pos = pos; moved = true @@ -466,9 +466,9 @@ impl LineBuffer { } } - pub fn move_to(&mut self, cs: CharSearch, count: u16) -> bool { + pub fn move_to(&mut self, cs: CharSearch, n: u16) -> bool { let mut moved = false; - for _ in 0..count { + for _ in 0..n { if let Some(pos) = self.search_char_pos(&cs) { self.pos = pos; moved = true @@ -480,7 +480,7 @@ impl LineBuffer { } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. - pub fn delete_word(&mut self, at: At, word_def: Word, count: u16) -> Option { + pub fn delete_word(&mut self, at: At, word_def: Word, n: u16) -> Option { if let Some(pos) = self.next_pos(self.pos, at, word_def) { let word = self.buf.drain(self.pos..pos).collect(); Some(word) @@ -489,7 +489,7 @@ impl LineBuffer { } } - pub fn delete_to(&mut self, cs: CharSearch, count: u16) -> Option { + pub fn delete_to(&mut self, cs: CharSearch, n: u16) -> Option { let search_result = match cs { CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c)), _ => self.search_char_pos(&cs), From 94d5abcf3cc8474434ea566af4a52cbe9cc57833 Mon Sep 17 00:00:00 2001 From: gwenn Date: Wed, 4 Jan 2017 20:04:09 +0100 Subject: [PATCH 0267/1201] Fix repeated word commands --- src/line_buffer.rs | 145 ++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 75 deletions(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 447eac0e39..20fa9d4818 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -297,49 +297,50 @@ impl LineBuffer { } /// Go left until start of word - fn prev_word_pos(&self, pos: usize, word_def: Word) -> Option { + fn prev_word_pos(&self, pos: usize, word_def: Word, n: u16) -> Option { if pos == 0 { return None; } let test = is_break_char(word_def); let mut pos = pos; - // eat any spaces on the left - pos -= self.buf[..pos] - .chars() - .rev() - .take_while(|ch| test(ch)) - .map(char::len_utf8) - .sum(); - if pos > 0 { - // eat any non-spaces on the left + for _ in 0..n { + // eat any spaces on the left pos -= self.buf[..pos] .chars() .rev() - .take_while(|ch| !test(ch)) + .take_while(|ch| test(ch)) .map(char::len_utf8) .sum(); + if pos > 0 { + // eat any non-spaces on the left + pos -= self.buf[..pos] + .chars() + .rev() + .take_while(|ch| !test(ch)) + .map(char::len_utf8) + .sum(); + } + if pos == 0 { + break; + } } Some(pos) } /// Moves the cursor to the beginning of previous word. pub fn move_to_prev_word(&mut self, word_def: Word, n: u16) -> bool { - let mut moved = false; - for _ in 0..n { - if let Some(pos) = self.prev_word_pos(self.pos, word_def) { - self.pos = pos; - moved = true - } else { - break; - } + if let Some(pos) = self.prev_word_pos(self.pos, word_def, n) { + self.pos = pos; + true + } else { + false } - moved } /// Delete the previous word, maintaining the cursor at the start of the /// current word. pub fn delete_prev_word(&mut self, word_def: Word, n: u16) -> Option { - if let Some(pos) = self.prev_word_pos(self.pos, word_def) { + if let Some(pos) = self.prev_word_pos(self.pos, word_def, n) { let word = self.buf.drain(pos..self.pos).collect(); self.pos = pos; Some(word) @@ -348,23 +349,26 @@ impl LineBuffer { } } - fn next_pos(&self, pos: usize, at: At, word_def: Word) -> Option { + fn next_pos(&self, pos: usize, at: At, word_def: Word, n: u16) -> Option { match at { At::End => { - match self.next_word_pos(pos, word_def) { + match self.next_word_pos(pos, word_def, n) { Some((_, end)) => Some(end), _ => None, } } - At::Start => self.next_start_of_word_pos(pos, word_def), + At::Start => self.next_start_of_word_pos(pos, word_def, n), } } /// Go right until start of word - fn next_start_of_word_pos(&self, pos: usize, word_def: Word) -> Option { - if pos < self.buf.len() { - let test = is_break_char(word_def); - let mut pos = pos; + fn next_start_of_word_pos(&self, pos: usize, word_def: Word, n: u16) -> Option { + if pos == self.buf.len() { + return None; + } + let test = is_break_char(word_def); + let mut pos = pos; + for _ in 0..n { // eat any non-spaces pos += self.buf[pos..] .chars() @@ -379,25 +383,30 @@ impl LineBuffer { .map(char::len_utf8) .sum(); } - Some(pos) - } else { - None + if pos == self.buf.len() { + break; + } } + Some(pos) } /// Go right until end of word /// Returns the position (start, end) of the next word. - fn next_word_pos(&self, pos: usize, word_def: Word) -> Option<(usize, usize)> { - if pos < self.buf.len() { - let test = is_break_char(word_def); - let mut pos = pos; + fn next_word_pos(&self, pos: usize, word_def: Word, n: u16) -> Option<(usize, usize)> { + if pos == self.buf.len() { + return None; + } + let test = is_break_char(word_def); + let mut pos = pos; + let mut start = pos; + for _ in 0..n { // eat any spaces pos += self.buf[pos..] .chars() .take_while(test) .map(char::len_utf8) .sum(); - let start = pos; + start = pos; if pos < self.buf.len() { // eat any non-spaces pos += self.buf[pos..] @@ -406,38 +415,28 @@ impl LineBuffer { .map(char::len_utf8) .sum(); } - Some((start, pos)) - } else { - None + if pos == self.buf.len() { + break; + } } + Some((start, pos)) } /// Moves the cursor to the end of next word. pub fn move_to_next_word(&mut self, at: At, word_def: Word, n: u16) -> bool { - let mut moved = false; - for _ in 0..n { - if let Some(pos) = self.next_pos(self.pos, at, word_def) { - self.pos = pos; - moved = true - } else { - break; - } + if let Some(pos) = self.next_pos(self.pos, at, word_def, n) { + self.pos = pos; + true + } else { + false } - moved } - fn search_char_pos(&mut self, cs: &CharSearch) -> Option { + fn search_char_pos(&mut self, cs: &CharSearch, n: u16) -> Option { let mut shift = 0; let search_result = match *cs { CharSearch::Backward(c) | - CharSearch::BackwardAfter(c) => { - self.buf[..self.pos].rfind(c) - // if let Some(pc) = self.char_before_cursor() { - // self.buf[..self.pos - pc.len_utf8()].rfind(c) - // } else { - // None - // } - } + CharSearch::BackwardAfter(c) => self.buf[..self.pos].rfind(c), CharSearch::Forward(c) | CharSearch::ForwardBefore(c) => { if let Some(cc) = self.char_at_cursor() { @@ -467,21 +466,17 @@ impl LineBuffer { } pub fn move_to(&mut self, cs: CharSearch, n: u16) -> bool { - let mut moved = false; - for _ in 0..n { - if let Some(pos) = self.search_char_pos(&cs) { - self.pos = pos; - moved = true - } else { - break; - } + if let Some(pos) = self.search_char_pos(&cs, n) { + self.pos = pos; + true + } else { + false } - moved } /// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word. pub fn delete_word(&mut self, at: At, word_def: Word, n: u16) -> Option { - if let Some(pos) = self.next_pos(self.pos, at, word_def) { + if let Some(pos) = self.next_pos(self.pos, at, word_def, n) { let word = self.buf.drain(self.pos..pos).collect(); Some(word) } else { @@ -491,8 +486,8 @@ impl LineBuffer { pub fn delete_to(&mut self, cs: CharSearch, n: u16) -> Option { let search_result = match cs { - CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c)), - _ => self.search_char_pos(&cs), + CharSearch::ForwardBefore(c) => self.search_char_pos(&CharSearch::Forward(c), n), + _ => self.search_char_pos(&cs, n), }; if let Some(pos) = search_result { let chunk = match cs { @@ -513,7 +508,7 @@ impl LineBuffer { /// Alter the next word. pub fn edit_word(&mut self, a: WordAction) -> bool { - if let Some((start, end)) = self.next_word_pos(self.pos, Word::Emacs) { + if let Some((start, end)) = self.next_word_pos(self.pos, Word::Emacs, 1) { if start == end { return false; } @@ -544,16 +539,16 @@ impl LineBuffer { // ^ ^ ^ // prev_start start self.pos/end let word_def = Word::Emacs; - if let Some(start) = self.prev_word_pos(self.pos, word_def) { - if let Some(prev_start) = self.prev_word_pos(start, word_def) { - let (_, prev_end) = self.next_word_pos(prev_start, word_def).unwrap(); + if let Some(start) = self.prev_word_pos(self.pos, word_def, 1) { + if let Some(prev_start) = self.prev_word_pos(start, word_def, 1) { + let (_, prev_end) = self.next_word_pos(prev_start, word_def, 1).unwrap(); if prev_end >= start { return false; } - let (_, mut end) = self.next_word_pos(start, word_def).unwrap(); + let (_, mut end) = self.next_word_pos(start, word_def, 1).unwrap(); if end < self.pos { if self.pos < self.buf.len() { - let (s, _) = self.next_word_pos(self.pos, word_def).unwrap(); + let (s, _) = self.next_word_pos(self.pos, word_def, 1).unwrap(); end = s; } else { end = self.pos; From c4650462f8e48945bfb0e6fe8d88d76971bb92f4 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 5 Jan 2017 21:36:57 +0100 Subject: [PATCH 0268/1201] Fix Clippy warning --- src/line_buffer.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 20fa9d4818..71358ffddf 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -120,14 +120,12 @@ impl LineBuffer { for _ in 0..n { self.buf.push(ch); } + } else if n == 1 { + self.buf.insert(self.pos, ch); } else { - if n == 1 { - self.buf.insert(self.pos, ch); - } else { - let text = iter::repeat(ch).take(n as usize).collect::(); - let pos = self.pos; - self.insert_str(pos, &text); - } + let text = iter::repeat(ch).take(n as usize).collect::(); + let pos = self.pos; + self.insert_str(pos, &text); } self.pos += shift; Some(push) From 6d063be4236076684bac8f28d4e97b845a6fcba1 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 7 Jan 2017 12:07:29 +0100 Subject: [PATCH 0269/1201] Fix repeated char search --- src/line_buffer.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 71358ffddf..37732e0673 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -434,13 +434,26 @@ impl LineBuffer { let mut shift = 0; let search_result = match *cs { CharSearch::Backward(c) | - CharSearch::BackwardAfter(c) => self.buf[..self.pos].rfind(c), + CharSearch::BackwardAfter(c) => { + self.buf[..self.pos] + .chars() + .rev() + .enumerate() + .filter(|&(_, ch)| ch == c) + .nth(n as usize - 1) + .map(|(i, _)| self.pos - i) + } CharSearch::Forward(c) | CharSearch::ForwardBefore(c) => { if let Some(cc) = self.char_at_cursor() { shift = self.pos + cc.len_utf8(); if shift < self.buf.len() { - self.buf[shift..].find(c) + self.buf[shift..] + .chars() + .enumerate() + .filter(|&(_, ch)| ch == c) + .nth(n as usize - 1) + .map(|(i, _)| i) } else { None } @@ -451,8 +464,8 @@ impl LineBuffer { }; if let Some(pos) = search_result { Some(match *cs { - CharSearch::Backward(_) => pos, - CharSearch::BackwardAfter(c) => pos + c.len_utf8(), + CharSearch::Backward(c) => pos - c.len_utf8(), + CharSearch::BackwardAfter(_) => pos, CharSearch::Forward(_) => shift + pos, CharSearch::ForwardBefore(_) => { shift + pos - self.buf[..shift + pos].chars().next_back().unwrap().len_utf8() From 6e56fe4e830abc64b4387c7ef8e027c56f9b0857 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 7 Jan 2017 16:19:51 +0100 Subject: [PATCH 0270/1201] Fix char search --- src/line_buffer.rs | 96 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 838b13c771..5fc2bbb83a 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -29,10 +29,10 @@ impl LineBuffer { #[cfg(test)] pub fn init(line: &str, pos: usize) -> LineBuffer { - LineBuffer { - buf: String::from(line), - pos: pos, - } + let mut lb = Self::with_capacity(MAX_LINE); + assert!(lb.insert_str(0, line)); + lb.set_pos(pos); + lb } /// Extracts a string slice containing the entire buffer. @@ -436,12 +436,11 @@ impl LineBuffer { CharSearch::Backward(c) | CharSearch::BackwardAfter(c) => { self.buf[..self.pos] - .chars() + .char_indices() .rev() - .enumerate() .filter(|&(_, ch)| ch == c) .nth(n as usize - 1) - .map(|(i, _)| self.pos - i) + .map(|(i, _)| i) } CharSearch::Forward(c) | CharSearch::ForwardBefore(c) => { @@ -449,8 +448,7 @@ impl LineBuffer { shift = self.pos + cc.len_utf8(); if shift < self.buf.len() { self.buf[shift..] - .chars() - .enumerate() + .char_indices() .filter(|&(_, ch)| ch == c) .nth(n as usize - 1) .map(|(i, _)| i) @@ -464,8 +462,8 @@ impl LineBuffer { }; if let Some(pos) = search_result { Some(match *cs { - CharSearch::Backward(c) => pos - c.len_utf8(), - CharSearch::BackwardAfter(_) => pos, + CharSearch::Backward(_) => pos, + CharSearch::BackwardAfter(c) => pos + c.len_utf8(), CharSearch::Forward(_) => shift + pos, CharSearch::ForwardBefore(_) => { shift + pos - self.buf[..shift + pos].chars().next_back().unwrap().len_utf8() @@ -631,7 +629,7 @@ fn is_whitespace(ch: &char) -> bool { #[cfg(test)] mod test { - use keymap::{At, Word}; + use keymap::{Anchor, At, CharSearch, Word}; use super::{LineBuffer, MAX_LINE, WordAction}; #[test] @@ -654,6 +652,24 @@ mod test { assert_eq!(false, push); } + #[test] + fn yank_after() { + let mut s = LineBuffer::init("αß", 2); + let ok = s.yank("γδε", Anchor::After, 1); + assert_eq!(Some(true), ok); + assert_eq!("αßγδε", s.buf); + assert_eq!(10, s.pos); + } + + #[test] + fn yank_before() { + let mut s = LineBuffer::init("αε", 2); + let ok = s.yank("ßγδ", Anchor::Before, 1); + assert_eq!(Some(false), ok); + assert_eq!("αßγδε", s.buf); + assert_eq!(8, s.pos); + } + #[test] fn moves() { let mut s = LineBuffer::init("αß", 4); @@ -739,6 +755,32 @@ mod test { assert_eq!(true, ok); } + #[test] + fn move_to_forward() { + let mut s = LineBuffer::init("αßγδε", 2); + let ok = s.move_to(CharSearch::ForwardBefore('ε'), 1); + assert_eq!(true, ok); + assert_eq!(6, s.pos); + + let mut s = LineBuffer::init("αßγδε", 2); + let ok = s.move_to(CharSearch::Forward('ε'), 1); + assert_eq!(true, ok); + assert_eq!(8, s.pos); + } + + #[test] + fn move_to_backward() { + let mut s = LineBuffer::init("αßγδε", 8); + let ok = s.move_to(CharSearch::BackwardAfter('ß'), 1); + assert_eq!(true, ok); + assert_eq!(4, s.pos); + + let mut s = LineBuffer::init("αßγδε", 8); + let ok = s.move_to(CharSearch::Backward('ß'), 1); + assert_eq!(true, ok); + assert_eq!(2, s.pos); + } + #[test] fn delete_prev_word() { let mut s = LineBuffer::init("a ß c", 6); @@ -784,6 +826,36 @@ mod test { assert_eq!(Some("ß ".to_string()), text); } + #[test] + fn delete_to_forward() { + let mut s = LineBuffer::init("αßγδε", 2); + let text = s.delete_to(CharSearch::ForwardBefore('ε'), 1); + assert_eq!(Some("ßγδ".to_string()), text); + assert_eq!("αε", s.buf); + assert_eq!(2, s.pos); + + let mut s = LineBuffer::init("αßγδε", 2); + let text = s.delete_to(CharSearch::Forward('ε'), 1); + assert_eq!(Some("ßγδε".to_string()), text); + assert_eq!("α", s.buf); + assert_eq!(2, s.pos); + } + + #[test] + fn delete_to_backward() { + let mut s = LineBuffer::init("αßγδε", 8); + let text = s.delete_to(CharSearch::BackwardAfter('α'), 1); + assert_eq!(Some("ßγδ".to_string()), text); + assert_eq!("αε", s.buf); + assert_eq!(2, s.pos); + + let mut s = LineBuffer::init("αßγδε", 8); + let text = s.delete_to(CharSearch::Backward('ß'), 1); + assert_eq!(Some("ßγδ".to_string()), text); + assert_eq!("αε", s.buf); + assert_eq!(2, s.pos); + } + #[test] fn edit_word() { let mut s = LineBuffer::init("a ßeta c", 1); From 809036a5c43403cb5b0decd72babcc13c447b856 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 7 Jan 2017 16:19:51 +0100 Subject: [PATCH 0271/1201] Fix char search --- src/line_buffer.rs | 96 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/src/line_buffer.rs b/src/line_buffer.rs index 37732e0673..4cdff2a865 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -29,10 +29,10 @@ impl LineBuffer { #[cfg(test)] pub fn init(line: &str, pos: usize) -> LineBuffer { - LineBuffer { - buf: String::from(line), - pos: pos, - } + let mut lb = Self::with_capacity(MAX_LINE); + assert!(lb.insert_str(0, line)); + lb.set_pos(pos); + lb } /// Extracts a string slice containing the entire buffer. @@ -436,12 +436,11 @@ impl LineBuffer { CharSearch::Backward(c) | CharSearch::BackwardAfter(c) => { self.buf[..self.pos] - .chars() + .char_indices() .rev() - .enumerate() .filter(|&(_, ch)| ch == c) .nth(n as usize - 1) - .map(|(i, _)| self.pos - i) + .map(|(i, _)| i) } CharSearch::Forward(c) | CharSearch::ForwardBefore(c) => { @@ -449,8 +448,7 @@ impl LineBuffer { shift = self.pos + cc.len_utf8(); if shift < self.buf.len() { self.buf[shift..] - .chars() - .enumerate() + .char_indices() .filter(|&(_, ch)| ch == c) .nth(n as usize - 1) .map(|(i, _)| i) @@ -464,8 +462,8 @@ impl LineBuffer { }; if let Some(pos) = search_result { Some(match *cs { - CharSearch::Backward(c) => pos - c.len_utf8(), - CharSearch::BackwardAfter(_) => pos, + CharSearch::Backward(_) => pos, + CharSearch::BackwardAfter(c) => pos + c.len_utf8(), CharSearch::Forward(_) => shift + pos, CharSearch::ForwardBefore(_) => { shift + pos - self.buf[..shift + pos].chars().next_back().unwrap().len_utf8() @@ -650,7 +648,7 @@ fn is_whitespace(ch: &char) -> bool { #[cfg(test)] mod test { - use keymap::{At, Word}; + use keymap::{Anchor, At, CharSearch, Word}; use super::{LineBuffer, MAX_LINE, WordAction}; #[test] @@ -673,6 +671,24 @@ mod test { assert_eq!(false, push); } + #[test] + fn yank_after() { + let mut s = LineBuffer::init("αß", 2); + let ok = s.yank("γδε", Anchor::After, 1); + assert_eq!(Some(true), ok); + assert_eq!("αßγδε", s.buf); + assert_eq!(10, s.pos); + } + + #[test] + fn yank_before() { + let mut s = LineBuffer::init("αε", 2); + let ok = s.yank("ßγδ", Anchor::Before, 1); + assert_eq!(Some(false), ok); + assert_eq!("αßγδε", s.buf); + assert_eq!(8, s.pos); + } + #[test] fn moves() { let mut s = LineBuffer::init("αß", 4); @@ -758,6 +774,32 @@ mod test { assert_eq!(true, ok); } + #[test] + fn move_to_forward() { + let mut s = LineBuffer::init("αßγδε", 2); + let ok = s.move_to(CharSearch::ForwardBefore('ε'), 1); + assert_eq!(true, ok); + assert_eq!(6, s.pos); + + let mut s = LineBuffer::init("αßγδε", 2); + let ok = s.move_to(CharSearch::Forward('ε'), 1); + assert_eq!(true, ok); + assert_eq!(8, s.pos); + } + + #[test] + fn move_to_backward() { + let mut s = LineBuffer::init("αßγδε", 8); + let ok = s.move_to(CharSearch::BackwardAfter('ß'), 1); + assert_eq!(true, ok); + assert_eq!(4, s.pos); + + let mut s = LineBuffer::init("αßγδε", 8); + let ok = s.move_to(CharSearch::Backward('ß'), 1); + assert_eq!(true, ok); + assert_eq!(2, s.pos); + } + #[test] fn delete_prev_word() { let mut s = LineBuffer::init("a ß c", 6); @@ -803,6 +845,36 @@ mod test { assert_eq!(Some("ß ".to_string()), text); } + #[test] + fn delete_to_forward() { + let mut s = LineBuffer::init("αßγδε", 2); + let text = s.delete_to(CharSearch::ForwardBefore('ε'), 1); + assert_eq!(Some("ßγδ".to_string()), text); + assert_eq!("αε", s.buf); + assert_eq!(2, s.pos); + + let mut s = LineBuffer::init("αßγδε", 2); + let text = s.delete_to(CharSearch::Forward('ε'), 1); + assert_eq!(Some("ßγδε".to_string()), text); + assert_eq!("α", s.buf); + assert_eq!(2, s.pos); + } + + #[test] + fn delete_to_backward() { + let mut s = LineBuffer::init("αßγδε", 8); + let text = s.delete_to(CharSearch::BackwardAfter('α'), 1); + assert_eq!(Some("ßγδ".to_string()), text); + assert_eq!("αε", s.buf); + assert_eq!(2, s.pos); + + let mut s = LineBuffer::init("αßγδε", 8); + let text = s.delete_to(CharSearch::Backward('ß'), 1); + assert_eq!(Some("ßγδ".to_string()), text); + assert_eq!("αε", s.buf); + assert_eq!(2, s.pos); + } + #[test] fn edit_word() { let mut s = LineBuffer::init("a ßeta c", 1); From 3f707e6122d1da0d19002db45879319a213e8a5f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 7 Jan 2017 20:28:32 +0100 Subject: [PATCH 0272/1201] Try to fix CI builds --- .travis.yml | 2 +- appveyor.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 76221a40dd..af468095ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: rust rust: - - 1.12.0 + - stable - beta - nightly script: diff --git a/appveyor.yml b/appveyor.yml index 4aae8469f4..4401d97fd6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ environment: matrix: - - TARGET: 1.12.0-x86_64-pc-windows-msvc - - TARGET: 1.12.0-x86_64-pc-windows-gnu + - TARGET: 1.13.0-x86_64-pc-windows-msvc + - TARGET: 1.13.0-x86_64-pc-windows-gnu - TARGET: beta-x86_64-pc-windows-msvc - TARGET: beta-x86_64-pc-windows-gnu install: From 8bfbfe919c9a9dfa211c2fe659c43948246fca4f Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 8 Jan 2017 10:21:11 +0100 Subject: [PATCH 0273/1201] Move cursor by grapheme (not by char) #107 --- src/keymap.rs | 42 +++++----- src/lib.rs | 35 ++++---- src/line_buffer.rs | 205 ++++++++++++++++++++++++++------------------- 3 files changed, 158 insertions(+), 124 deletions(-) diff --git a/src/keymap.rs b/src/keymap.rs index f7a5acebe8..a769b82838 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -8,34 +8,34 @@ use super::Result; pub enum Cmd { Abort, // Miscellaneous Command AcceptLine, - BackwardChar(u16), - BackwardDeleteChar(u16), - BackwardKillWord(u16, Word), // Backward until start of word - BackwardWord(u16, Word), // Backward until start of word + BackwardChar(usize), + BackwardDeleteChar(usize), + BackwardKillWord(usize, Word), // Backward until start of word + BackwardWord(usize, Word), // Backward until start of word BeginningOfHistory, BeginningOfLine, CapitalizeWord, ClearScreen, Complete, - DeleteChar(u16), + DeleteChar(usize), DowncaseWord, EndOfFile, EndOfHistory, EndOfLine, - ForwardChar(u16), + ForwardChar(usize), ForwardSearchHistory, - ForwardWord(u16, At, Word), // Forward until start/end of word + ForwardWord(usize, At, Word), // Forward until start/end of word Interrupt, KillLine, KillWholeLine, - KillWord(u16, At, Word), // Forward until start/end of word + KillWord(usize, At, Word), // Forward until start/end of word NextHistory, Noop, PreviousHistory, QuotedInsert, - Replace(u16, char), // TODO DeleteChar + SelfInsert + Replace(usize, char), // TODO DeleteChar + SelfInsert ReverseSearchHistory, - SelfInsert(u16, char), + SelfInsert(usize, char), Suspend, TransposeChars, TransposeWords, @@ -43,9 +43,9 @@ pub enum Cmd { UnixLikeDiscard, // UnixWordRubout, // = BackwardKillWord(Word::Big) UpcaseWord, - ViCharSearch(u16, CharSearch), - ViDeleteTo(u16, CharSearch), - Yank(u16, Anchor), + ViCharSearch(usize, CharSearch), + ViDeleteTo(usize, CharSearch), + Yank(usize, Anchor), YankPop, } @@ -375,7 +375,7 @@ impl EditState { rdr: &mut R, config: &Config, key: KeyPress, - n: u16) + n: usize) -> Result { let mut mvt = try!(rdr.next_key(config.keyseq_timeout())); if mvt == key { @@ -432,7 +432,7 @@ impl EditState { }) } - fn common(&mut self, key: KeyPress, n: u16, positive: bool) -> Cmd { + fn common(&mut self, key: KeyPress, n: usize, positive: bool) -> Cmd { match key { KeyPress::Home => Cmd::BeginningOfLine, KeyPress::Left => { @@ -498,25 +498,25 @@ impl EditState { num_args } - fn emacs_num_args(&mut self) -> (u16, bool) { + fn emacs_num_args(&mut self) -> (usize, bool) { let num_args = self.num_args(); if num_args < 0 { if let (n, false) = num_args.overflowing_abs() { - (n as u16, false) + (n as usize, false) } else { - (u16::max_value(), false) + (usize::max_value(), false) } } else { - (num_args as u16, true) + (num_args as usize, true) } } - fn vi_num_args(&mut self) -> u16 { + fn vi_num_args(&mut self) -> usize { let num_args = self.num_args(); if num_args < 0 { unreachable!() } else { - num_args.abs() as u16 + num_args.abs() as usize } } } diff --git a/src/lib.rs b/src/lib.rs index 7a70ef16b8..b087456c4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,7 @@ use std::io::{self, Write}; use std::mem; use std::path::Path; use std::result; +use unicode_segmentation::UnicodeSegmentation; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; use tty::{RawMode, RawReader, Terminal, Term}; @@ -309,7 +310,7 @@ fn calculate_position(s: &str, orig: Position, cols: usize) -> Position { } /// Insert the character `ch` at cursor current position. -fn edit_insert(s: &mut State, ch: char, n: u16) -> Result<()> { +fn edit_insert(s: &mut State, ch: char, n: usize) -> Result<()> { if let Some(push) = s.line.insert(ch, n) { if push { if n == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.cols { @@ -331,9 +332,9 @@ fn edit_insert(s: &mut State, ch: char, n: u16) -> Result<()> { } /// Replace a single (or n) character(s) under the cursor (Vi mode) -fn edit_replace_char(s: &mut State, ch: char, n: u16) -> Result<()> { - let count = s.line.delete(n); - if count > 0 { +fn edit_replace_char(s: &mut State, ch: char, n: usize) -> Result<()> { + if let Some(chars) = s.line.delete(n) { + let count = chars.graphemes(true).count(); s.line.insert(ch, count); s.line.move_left(1); s.refresh_line() @@ -343,7 +344,7 @@ fn edit_replace_char(s: &mut State, ch: char, n: u16) -> Result<()> { } // Yank/paste `text` at current position. -fn edit_yank(s: &mut State, text: &str, anchor: Anchor, n: u16) -> Result<()> { +fn edit_yank(s: &mut State, text: &str, anchor: Anchor, n: usize) -> Result<()> { if s.line.yank(text, anchor, n).is_some() { s.refresh_line() } else { @@ -358,7 +359,7 @@ fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> { } /// Move cursor on the left. -fn edit_move_left(s: &mut State, n: u16) -> Result<()> { +fn edit_move_left(s: &mut State, n: usize) -> Result<()> { if s.line.move_left(n) { s.refresh_line() } else { @@ -367,7 +368,7 @@ fn edit_move_left(s: &mut State, n: u16) -> Result<()> { } /// Move cursor on the right. -fn edit_move_right(s: &mut State, n: u16) -> Result<()> { +fn edit_move_right(s: &mut State, n: usize) -> Result<()> { if s.line.move_right(n) { s.refresh_line() } else { @@ -395,8 +396,8 @@ fn edit_move_end(s: &mut State) -> Result<()> { /// Delete the character at the right of the cursor without altering the cursor /// position. Basically this is what happens with the "Delete" keyboard key. -fn edit_delete(s: &mut State, n: u16) -> Result<()> { - if s.line.delete(n) > 0 { +fn edit_delete(s: &mut State, n: usize) -> Result<()> { + if s.line.delete(n).is_some() { s.refresh_line() } else { Ok(()) @@ -404,8 +405,8 @@ fn edit_delete(s: &mut State, n: u16) -> Result<()> { } /// Backspace implementation. -fn edit_backspace(s: &mut State, n: u16) -> Result<()> { - if s.line.backspace(n) { +fn edit_backspace(s: &mut State, n: usize) -> Result<()> { + if s.line.backspace(n).is_some() { s.refresh_line() } else { Ok(()) @@ -441,7 +442,7 @@ fn edit_transpose_chars(s: &mut State) -> Result<()> { } } -fn edit_move_to_prev_word(s: &mut State, word_def: Word, n: u16) -> Result<()> { +fn edit_move_to_prev_word(s: &mut State, word_def: Word, n: usize) -> Result<()> { if s.line.move_to_prev_word(word_def, n) { s.refresh_line() } else { @@ -451,7 +452,7 @@ fn edit_move_to_prev_word(s: &mut State, word_def: Word, n: u16) -> Result<()> { /// Delete the previous word, maintaining the cursor at the start of the /// current word. -fn edit_delete_prev_word(s: &mut State, word_def: Word, n: u16) -> Result> { +fn edit_delete_prev_word(s: &mut State, word_def: Word, n: usize) -> Result> { if let Some(text) = s.line.delete_prev_word(word_def, n) { try!(s.refresh_line()); Ok(Some(text)) @@ -460,7 +461,7 @@ fn edit_delete_prev_word(s: &mut State, word_def: Word, n: u16) -> Result