Merge pull request #155 from gwenn/surrogate
Simplify surrogate pair handling on windows
diff --git a/Cargo.toml b/Cargo.toml
index 30a2fa1..a8ed288 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rustyline"
-version = "2.0.1"
+version = "2.1.0"
authors = ["Katsu Kawakami <kkawa1570@gmail.com>"]
description = "Rustyline, a readline implementation based on Antirez's Linenoise"
documentation = "http://docs.rs/rustyline"
diff --git a/README.md b/README.md
index 5468a87..ea66ccc 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@
```toml
[dependencies]
-rustyline = "2.0.1"
+rustyline = "2.1.0"
```
## Features
diff --git a/TODO.md b/TODO.md
index d4cd263..75b2667 100644
--- a/TODO.md
+++ b/TODO.md
@@ -9,7 +9,7 @@
Color
- [X] ANSI Colors & Windows 10+
- [ ] ANSI Colors & Windows <10 (https://docs.rs/console/0.6.1/console/fn.strip_ansi_codes.html ? https://github.com/mattn/go-colorable/blob/master/colorable_windows.go)
-- [ ] Syntax highlighting
+- [ ] Syntax highlighting (https://github.com/trishume/syntect/)
- [ ] clicolors spec (https://docs.rs/console/0.6.1/console/fn.colors_enabled.html)
Completion
@@ -76,6 +76,8 @@
Unix
- [ ] Terminfo (https://github.com/Stebalien/term)
- [ ] [ncurses](https://crates.io/crates/ncurses) alternative backend ?
+- [ ] [bracketed paste mode](https://cirw.in/blog/bracketed-paste)
+- [ ] async stdin (https://github.com/Rufflewind/tokio-file-unix)
Windows
- [ ] is_atty is not working with cygwin/msys (https://github.com/softprops/atty works but then how to make `enable_raw_mode` works ?)
diff --git a/examples/example.rs b/examples/example.rs
index e02f665..497bc34 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -5,6 +5,7 @@
use std::borrow::Cow::{self, Borrowed, Owned};
use rustyline::completion::{Completer, FilenameCompleter, Pair};
+use rustyline::config::OutputStreamType;
use rustyline::error::ReadlineError;
use rustyline::highlight::Highlighter;
use rustyline::hint::Hinter;
@@ -56,6 +57,7 @@
.history_ignore_space(true)
.completion_type(CompletionType::List)
.edit_mode(EditMode::Emacs)
+ .output_stream(OutputStreamType::Stdout)
.build();
let h = MyHelper(FilenameCompleter::new());
let mut rl = Editor::with_config(config);
diff --git a/src/completion.rs b/src/completion.rs
index ab7a020..39fe213 100644
--- a/src/completion.rs
+++ b/src/completion.rs
@@ -299,20 +299,30 @@
};
let mut entries: Vec<Pair> = Vec::new();
- 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) {
- if let Ok(metadata) = fs::metadata(entry.path()) {
- let mut path = String::from(dir_name) + s;
- if metadata.is_dir() {
- path.push(sep);
+
+ // if dir doesn't exist, then don't offer any completions
+ if !dir.exists() {
+ return Ok(entries);
+ }
+
+ // if any of the below IO operations have errors, just ignore them
+ if let Ok(read_dir) = dir.read_dir() {
+ for entry in read_dir {
+ if let Ok(entry) = entry {
+ if let Some(s) = entry.file_name().to_str() {
+ if s.starts_with(file_name) {
+ if let Ok(metadata) = fs::metadata(entry.path()) {
+ let mut path = String::from(dir_name) + s;
+ if metadata.is_dir() {
+ path.push(sep);
+ }
+ entries.push(Pair {
+ display: String::from(s),
+ replacement: escape(path, esc_char, break_chars, quote),
+ });
+ } // else ignore PermissionDenied
}
- entries.push(Pair {
- display: String::from(s),
- replacement: escape(path, esc_char, break_chars, quote),
- });
- } // else ignore PermissionDenied
+ }
}
}
}
diff --git a/src/config.rs b/src/config.rs
index c82a234..e59a308 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -22,6 +22,8 @@
auto_add_history: bool,
/// if colors should be enabled.
color_mode: ColorMode,
+ /// Whether to use stdout or stderr
+ output_stream: OutputStreamType,
}
impl Config {
@@ -99,6 +101,14 @@
pub(crate) fn set_color_mode(&mut self, color_mode: ColorMode) {
self.color_mode = color_mode;
}
+
+ pub fn output_stream(&self) -> OutputStreamType {
+ self.output_stream
+ }
+
+ pub(crate) fn set_output_stream(&mut self, stream: OutputStreamType) {
+ self.output_stream = stream;
+ }
}
impl Default for Config {
@@ -113,6 +123,7 @@
edit_mode: EditMode::Emacs,
auto_add_history: false,
color_mode: ColorMode::Enabled,
+ output_stream: OutputStreamType::Stdout,
}
}
}
@@ -149,6 +160,13 @@
Disabled,
}
+/// Should the editor use stdout or stderr
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum OutputStreamType {
+ Stderr,
+ Stdout,
+}
+
/// Configuration builder
#[derive(Debug, Default)]
pub struct Builder {
@@ -231,6 +249,14 @@
self
}
+ /// Whether to use stdout or stderr.
+ ///
+ /// Be default, use stdout
+ pub fn output_stream(mut self, stream: OutputStreamType) -> Builder {
+ self.set_output_stream(stream);
+ self
+ }
+
pub fn build(self) -> Config {
self.p
}
@@ -303,4 +329,11 @@
fn set_color_mode(&mut self, color_mode: ColorMode) {
self.config_mut().set_color_mode(color_mode);
}
+
+ /// Whether to use stdout or stderr
+ ///
+ /// By default, use stdout
+ fn set_output_stream(&mut self, stream: OutputStreamType) {
+ self.config_mut().set_output_stream(stream);
+ }
}
diff --git a/src/edit.rs b/src/edit.rs
index f9fcca8..7d706f5 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -32,6 +32,7 @@
pub changes: Rc<RefCell<Changeset>>, // changes to line, for undo/redo
pub hinter: Option<&'out Hinter>,
pub highlighter: Option<&'out Highlighter>,
+ no_hint: bool, // `false` if an hint has been displayed
}
impl<'out, 'prompt> State<'out, 'prompt> {
@@ -57,6 +58,7 @@
changes: Rc::new(RefCell::new(Changeset::new())),
hinter,
highlighter,
+ no_hint: true,
}
}
@@ -130,10 +132,12 @@
Ok(())
}
- fn hint(&self) -> Option<String> {
+ fn hint(&mut self) -> Option<String> {
if let Some(hinter) = self.hinter {
+ self.no_hint = false;
hinter.hint(self.line.as_str(), self.line.pos())
} else {
+ self.no_hint = true;
None
}
}
@@ -186,10 +190,11 @@
if let Some(push) = self.line.insert(ch, n) {
if push {
let prompt_size = self.prompt_size;
+ let no_previous_hint = self.no_hint;
let hint = self.hint();
if n == 1
&& self.cursor.col + ch.width().unwrap_or(0) < self.out.get_columns()
- && hint.is_none() // TODO refresh only current line
+ && (hint.is_none() && no_previous_hint) // TODO refresh only current line
&& !self.highlighter.map_or(true, |h| h.highlight_char(ch.encode_utf8(&mut self.byte_buffer)))
{
// Avoid a full update of the line in the trivial case.
@@ -322,13 +327,12 @@
}
pub fn edit_insert_text(&mut self, text: &str) -> Result<()> {
- if !text.is_empty() {
- let cursor = self.line.pos();
- self.line.insert_str(cursor, text);
- self.refresh_line()
- } else {
- Ok(())
+ if text.is_empty() {
+ return Ok(());
}
+ let cursor = self.line.pos();
+ self.line.insert_str(cursor, text);
+ self.refresh_line()
}
pub fn edit_delete(&mut self, n: RepeatCount) -> Result<()> {
@@ -506,6 +510,7 @@
changes: Rc::new(RefCell::new(Changeset::new())),
hinter: None,
highlighter: None,
+ no_hint: true,
}
}
diff --git a/src/error.rs b/src/error.rs
index f7c1abd..c49595b 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -10,6 +10,7 @@
/// The error type for Rustyline errors that can arise from
/// I/O related errors or Errno when using the nix-rust library
+/// #[non_exhaustive]
#[derive(Debug)]
pub enum ReadlineError {
/// I/O Error
diff --git a/src/keymap.rs b/src/keymap.rs
index 877a12d..9841f78 100644
--- a/src/keymap.rs
+++ b/src/keymap.rs
@@ -5,13 +5,14 @@
use super::Result;
use config::Config;
use config::EditMode;
-use consts::KeyPress;
+use keys::KeyPress;
use tty::RawReader;
/// The number of times one command should be repeated.
pub type RepeatCount = usize;
/// Commands
+/// #[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum Cmd {
/// abort
@@ -479,20 +480,17 @@
loop {
try!(wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args)));
let key = try!(rdr.next_key(false));
- match key {
- KeyPress::Char(digit @ '0'...'9') => {
- if self.num_args.abs() < 1000 {
- // shouldn't ever need more than 4 digits
- self.num_args = self
- .num_args
- .saturating_mul(10)
- .saturating_add(digit.to_digit(10).unwrap() as i16);
- }
+ if let KeyPress::Char(digit @ '0'...'9') = key {
+ if self.num_args.abs() < 1000 {
+ // shouldn't ever need more than 4 digits
+ self.num_args = self
+ .num_args
+ .saturating_mul(10)
+ .saturating_add(digit.to_digit(10).unwrap() as i16);
}
- _ => {
- try!(wrt.refresh_line());
- return Ok(key);
- }
+ } else {
+ try!(wrt.refresh_line());
+ return Ok(key);
};
}
}
diff --git a/src/consts.rs b/src/keys.rs
similarity index 94%
rename from src/consts.rs
rename to src/keys.rs
index 5841016..c355e5a 100644
--- a/src/consts.rs
+++ b/src/keys.rs
@@ -1,9 +1,11 @@
//! Key constants
+/// #[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyPress {
UnknownEscSeq,
- Backspace,
+ Backspace, // Ctrl('H')
+ BackTab,
Char(char),
ControlDown,
ControlLeft,
@@ -14,7 +16,7 @@
Down,
End,
Enter, // Ctrl('M')
- Esc,
+ Esc, // Ctrl('[')
F(u8),
Home,
Insert,
@@ -32,7 +34,7 @@
Up,
}
-#[allow(match_same_arms)]
+//#[allow(clippy::match_same_arms)]
pub fn char_to_key_press(c: char) -> KeyPress {
if !c.is_control() {
return KeyPress::Char(c);
diff --git a/src/kill_ring.rs b/src/kill_ring.rs
index b5d8778..03a202f 100644
--- a/src/kill_ring.rs
+++ b/src/kill_ring.rs
@@ -41,34 +41,31 @@
/// Add `text` to the kill-ring.
pub fn kill(&mut self, text: &str, dir: Mode) {
- match self.last_action {
- Action::Kill => {
- if self.slots.capacity() == 0 {
- // disabled
- return;
- }
- match dir {
- Mode::Append => self.slots[self.index].push_str(text),
- Mode::Prepend => self.slots[self.index].insert_str(0, text),
- };
+ if let Action::Kill = self.last_action {
+ if self.slots.capacity() == 0 {
+ // disabled
+ return;
}
- _ => {
- self.last_action = Action::Kill;
- if self.slots.capacity() == 0 {
- // disabled
- return;
- }
- if self.index == self.slots.capacity() - 1 {
- // full
- self.index = 0;
- } else if !self.slots.is_empty() {
- self.index += 1;
- }
- if self.index == self.slots.len() {
- self.slots.push(String::from(text))
- } else {
- self.slots[self.index] = String::from(text);
- }
+ match dir {
+ Mode::Append => self.slots[self.index].push_str(text),
+ Mode::Prepend => self.slots[self.index].insert_str(0, text),
+ };
+ } else {
+ self.last_action = Action::Kill;
+ if self.slots.capacity() == 0 {
+ // disabled
+ return;
+ }
+ if self.index == self.slots.capacity() - 1 {
+ // full
+ self.index = 0;
+ } else if !self.slots.is_empty() {
+ self.index += 1;
+ }
+ if self.index == self.slots.len() {
+ self.slots.push(String::from(text))
+ } else {
+ self.slots[self.index] = String::from(text);
}
}
}
diff --git a/src/lib.rs b/src/lib.rs
index f6f68ea..90b3ac2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,6 +16,8 @@
//! }
//! ```
#![allow(unknown_lints)]
+// #![feature(non_exhaustive)]
+// #![feature(tool_lints)]
extern crate dirs;
extern crate libc;
@@ -33,13 +35,13 @@
pub mod completion;
pub mod config;
-mod consts;
mod edit;
pub mod error;
pub mod highlight;
pub mod hint;
pub mod history;
mod keymap;
+mod keys;
mod kill_ring;
pub mod line_buffer;
mod undo;
@@ -58,13 +60,13 @@
use completion::{longest_common_prefix, Candidate, Completer};
pub use config::{ColorMode, CompletionType, Config, EditMode, HistoryDuplicates};
-pub use consts::KeyPress;
use edit::State;
use highlight::Highlighter;
use hint::Hinter;
use history::{Direction, History};
pub use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
use keymap::{InputState, Refresher};
+pub use keys::KeyPress;
use kill_ring::{KillRing, Mode};
use line_buffer::WordAction;
@@ -638,7 +640,11 @@
}
}
drop(guard); // try!(disable_raw_mode(original_mode));
- println!();
+ editor
+ .term
+ .create_writer()
+ .write_and_flush("\n".as_bytes())
+ .unwrap();
user_input
}
@@ -675,7 +681,7 @@
custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>,
}
-#[allow(new_without_default)]
+//#[allow(clippy::new_without_default)]
impl<H: Helper> Editor<H> {
/// Create an editor with the default configuration
pub fn new() -> Editor<H> {
@@ -684,7 +690,7 @@
/// Create an editor with a specific configuration.
pub fn with_config(config: Config) -> Editor<H> {
- let term = Terminal::new(config.color_mode());
+ let term = Terminal::new(config.color_mode(), config.output_stream());
Editor {
term,
history: History::with_config(config),
@@ -876,6 +882,61 @@
}
}
+enum StdStream {
+ Stdout(io::Stdout),
+ Stderr(io::Stderr),
+}
+impl StdStream {
+ fn from_stream_type(t: config::OutputStreamType) -> StdStream {
+ match t {
+ config::OutputStreamType::Stderr => StdStream::Stderr(io::stderr()),
+ config::OutputStreamType::Stdout => StdStream::Stdout(io::stdout()),
+ }
+ }
+}
+#[cfg(unix)]
+impl std::os::unix::io::AsRawFd for StdStream {
+ fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
+ match self {
+ StdStream::Stdout(e) => e.as_raw_fd(),
+ StdStream::Stderr(e) => e.as_raw_fd(),
+ }
+ }
+}
+impl io::Write for StdStream {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ match self {
+ StdStream::Stdout(ref mut e) => e.write(buf),
+ StdStream::Stderr(ref mut e) => e.write(buf),
+ }
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ match self {
+ StdStream::Stdout(ref mut e) => e.flush(),
+ StdStream::Stderr(ref mut e) => e.flush(),
+ }
+ }
+
+ fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+ match self {
+ StdStream::Stdout(ref mut e) => e.write_all(buf),
+ StdStream::Stderr(ref mut e) => e.write_all(buf),
+ }
+ }
+
+ fn write_fmt(&mut self, fmt: std::fmt::Arguments) -> io::Result<()> {
+ match self {
+ StdStream::Stdout(ref mut e) => e.write_fmt(fmt),
+ StdStream::Stderr(ref mut e) => e.write_fmt(fmt),
+ }
+ }
+
+ fn by_ref(&mut self) -> &mut StdStream {
+ self
+ }
+}
+
#[cfg(test)]
#[macro_use]
extern crate assert_matches;
diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index e17fd62..ae3fe70 100644
--- a/src/line_buffer.rs
+++ b/src/line_buffer.rs
@@ -364,25 +364,19 @@
sow = 0;
let mut gj = gis.next();
'inner: loop {
- match gj {
- Some((j, y)) => {
- let gi = gis.next();
- match gi {
- Some((_, x)) => {
- if is_start_of_word(word_def, x, y) {
- sow = j;
- break 'inner;
- }
- gj = gi;
- }
- None => {
- break 'outer;
- }
+ if let Some((j, y)) = gj {
+ let gi = gis.next();
+ if let Some((_, x)) = gi {
+ if is_start_of_word(word_def, x, y) {
+ sow = j;
+ break 'inner;
}
- }
- None => {
+ gj = gi;
+ } else {
break 'outer;
}
+ } else {
+ break 'outer;
}
}
}
@@ -428,32 +422,26 @@
wp = 0;
gi = gis.next();
'inner: loop {
- match gi {
- Some((i, x)) => {
- let gj = gis.next();
- match gj {
- Some((j, y)) => {
- if at == At::Start && is_start_of_word(word_def, x, y) {
- wp = j;
- break 'inner;
- } else if at != At::Start && is_end_of_word(word_def, x, y) {
- if word_def == Word::Emacs || at == At::AfterEnd {
- wp = j;
- } else {
- wp = i;
- }
- break 'inner;
- }
- gi = gj;
+ if let Some((i, x)) = gi {
+ let gj = gis.next();
+ if let Some((j, y)) = gj {
+ if at == At::Start && is_start_of_word(word_def, x, y) {
+ wp = j;
+ break 'inner;
+ } else if at != At::Start && is_end_of_word(word_def, x, y) {
+ if word_def == Word::Emacs || at == At::AfterEnd {
+ wp = j;
+ } else {
+ wp = i;
}
- None => {
- break 'outer;
- }
+ break 'inner;
}
- }
- None => {
+ gi = gj;
+ } else {
break 'outer;
}
+ } else {
+ break 'outer;
}
}
}
diff --git a/src/test/common.rs b/src/test/common.rs
index 7fe8b17..29bde74 100644
--- a/src/test/common.rs
+++ b/src/test/common.rs
@@ -1,8 +1,8 @@
///! Basic commands tests.
use super::{assert_cursor, assert_line, assert_line_with_initial, init_editor};
use config::EditMode;
-use consts::KeyPress;
use error::ReadlineError;
+use keys::KeyPress;
#[test]
fn home_key() {
diff --git a/src/test/emacs.rs b/src/test/emacs.rs
index 67a1b10..a4c738e 100644
--- a/src/test/emacs.rs
+++ b/src/test/emacs.rs
@@ -1,7 +1,7 @@
//! Emacs specific key bindings
use super::{assert_cursor, assert_history};
use config::EditMode;
-use consts::KeyPress;
+use keys::KeyPress;
#[test]
fn ctrl_a() {
diff --git a/src/test/history.rs b/src/test/history.rs
index faa7a3f..dc3f8f0 100644
--- a/src/test/history.rs
+++ b/src/test/history.rs
@@ -1,7 +1,7 @@
//! History related commands tests
use super::assert_history;
use config::EditMode;
-use consts::KeyPress;
+use keys::KeyPress;
#[test]
fn down_key() {
diff --git a/src/test/mod.rs b/src/test/mod.rs
index 1ce9881..8642b03 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -4,9 +4,9 @@
use super::{Editor, Result};
use completion::Completer;
use config::{Config, EditMode};
-use consts::KeyPress;
use edit::init_state;
use keymap::{Cmd, InputState};
+use keys::KeyPress;
use tty::Sink;
mod common;
diff --git a/src/test/vi_cmd.rs b/src/test/vi_cmd.rs
index 1c1e4e9..d8ec793 100644
--- a/src/test/vi_cmd.rs
+++ b/src/test/vi_cmd.rs
@@ -1,7 +1,7 @@
//! Vi command mode specific key bindings
use super::{assert_cursor, assert_history};
use config::EditMode;
-use consts::KeyPress;
+use keys::KeyPress;
#[test]
fn dollar() {
diff --git a/src/test/vi_insert.rs b/src/test/vi_insert.rs
index cf86680..fa3c881 100644
--- a/src/test/vi_insert.rs
+++ b/src/test/vi_insert.rs
@@ -1,7 +1,7 @@
//! Vi insert mode specific key bindings
use super::assert_cursor;
use config::EditMode;
-use consts::KeyPress;
+use keys::KeyPress;
#[test]
fn insert_mode_by_default() {
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
index d6767fc..1c6bf41 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -3,9 +3,9 @@
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
-use config::{ColorMode, Config};
-use consts::KeyPress;
+use config::{ColorMode, Config, OutputStreamType};
use highlight::Highlighter;
+use keys::KeyPress;
use line_buffer::LineBuffer;
use Result;
@@ -139,7 +139,7 @@
type Writer: Renderer; // rl_outstream
type Mode: RawMode;
- fn new(color_mode: ColorMode) -> Self;
+ fn new(color_mode: ColorMode, stream: OutputStreamType) -> Self;
/// Check if current terminal can provide a rich line-editing user
/// interface.
fn is_unsupported(&self) -> bool;
diff --git a/src/tty/test.rs b/src/tty/test.rs
index 75844be..8ea87a4 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -4,10 +4,10 @@
use std::vec::IntoIter;
use super::{truncate, Position, RawMode, RawReader, Renderer, Term};
-use config::{ColorMode, Config};
-use consts::KeyPress;
+use config::{ColorMode, Config, OutputStreamType};
use error::ReadlineError;
use highlight::Highlighter;
+use keys::KeyPress;
use line_buffer::LineBuffer;
use Result;
@@ -129,7 +129,7 @@
type Reader = IntoIter<KeyPress>;
type Writer = Sink;
- fn new(color_mode: ColorMode) -> DummyTerminal {
+ fn new(color_mode: ColorMode, _stream: OutputStreamType) -> DummyTerminal {
DummyTerminal {
keys: Vec::new(),
cursor: 0,
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index 5a8b20f..f07a02d 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -1,6 +1,7 @@
//! Unix specific definitions
use std;
-use std::io::{self, Read, Stdout, Write};
+use std::io::{self, Read, Write};
+use std::os::unix::io::AsRawFd;
use std::sync;
use std::sync::atomic;
@@ -14,12 +15,13 @@
use utf8parse::{Parser, Receiver};
use super::{truncate, width, Position, RawMode, RawReader, Renderer, Term};
-use config::{ColorMode, Config};
-use consts::{self, KeyPress};
+use config::{ColorMode, Config, OutputStreamType};
use error;
use highlight::Highlighter;
+use keys::{self, KeyPress};
use line_buffer::LineBuffer;
use Result;
+use StdStream;
const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO;
const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO;
@@ -27,15 +29,15 @@
/// Unsupported Terminals that don't support RAW mode
static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"];
-#[allow(identity_conversion)]
-fn get_win_size() -> (usize, usize) {
+//#[allow(clippy::identity_conversion)]
+fn get_win_size<T: AsRawFd + ?Sized>(fileno: &T) -> (usize, usize) {
use std::mem::zeroed;
unsafe {
let mut size: libc::winsize = zeroed();
// https://github.com/rust-lang/libc/pull/704
// FIXME: ".into()" used as a temporary fix for a libc bug
- match libc::ioctl(STDOUT_FILENO, libc::TIOCGWINSZ.into(), &mut size) {
+ match libc::ioctl(fileno.as_raw_fd(), libc::TIOCGWINSZ.into(), &mut size) {
0 => (size.ws_col as usize, size.ws_row as usize), // TODO getCursorPosition
_ => (80, 24),
}
@@ -163,6 +165,20 @@
self.extended_escape(seq2)
}
}
+ } else if seq2 == '[' {
+ let seq3 = try!(self.next_char());
+ // Linux console
+ Ok(match seq3 {
+ 'A' => KeyPress::F(1),
+ 'B' => KeyPress::F(2),
+ 'C' => KeyPress::F(3),
+ 'D' => KeyPress::F(4),
+ 'E' => KeyPress::F(5),
+ _ => {
+ debug!(target: "rustyline", "unsupported esc sequence: ESC [ [ {:?}", seq3);
+ KeyPress::UnknownEscSeq
+ }
+ })
} else {
// ANSI
Ok(match seq2 {
@@ -172,6 +188,7 @@
'D' => KeyPress::Left, // kcub1
'F' => KeyPress::End,
'H' => KeyPress::Home, // khome
+ 'Z' => KeyPress::BackTab,
_ => {
debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2);
KeyPress::UnknownEscSeq
@@ -251,13 +268,13 @@
('2', 'D') => KeyPress::ShiftLeft,
_ => {
debug!(target: "rustyline",
- "unsupported esc sequence: ESC [ {} ; {} {}", seq2, seq4, seq5);
+ "unsupported esc sequence: ESC [ 1 ; {} {:?}", seq4, seq5);
KeyPress::UnknownEscSeq
}
})
} else {
debug!(target: "rustyline",
- "unsupported esc sequence: ESC [ {} ; {} {}", seq2, seq4, seq5);
+ "unsupported esc sequence: ESC [ {} ; {} {:?}", seq2, seq4, seq5);
Ok(KeyPress::UnknownEscSeq)
}
} else {
@@ -296,8 +313,8 @@
'S' => KeyPress::F(4), // kf4
'a' => KeyPress::ControlUp,
'b' => KeyPress::ControlDown,
- 'c' => KeyPress::ControlRight,
- 'd' => KeyPress::ControlLeft,
+ 'c' => KeyPress::ControlRight, // rxvt
+ 'd' => KeyPress::ControlLeft, // rxvt
_ => {
debug!(target: "rustyline", "unsupported esc sequence: ESC O {:?}", seq2);
KeyPress::UnknownEscSeq
@@ -310,7 +327,7 @@
fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyPress> {
let c = try!(self.next_char());
- let mut key = consts::char_to_key_press(c);
+ let mut key = keys::char_to_key_press(c);
if key == KeyPress::Esc {
let timeout_ms = if single_esc_abort && self.timeout_ms == -1 {
0
@@ -367,16 +384,17 @@
/// Console output writer
pub struct PosixRenderer {
- out: Stdout,
+ out: StdStream,
cols: usize, // Number of columns in terminal
buffer: String,
}
impl PosixRenderer {
- fn new() -> PosixRenderer {
- let (cols, _) = get_win_size();
+ fn new(stream_type: OutputStreamType) -> PosixRenderer {
+ let out = StdStream::from_stream_type(stream_type);
+ let (cols, _) = get_win_size(&out);
PosixRenderer {
- out: io::stdout(),
+ out: out,
cols,
buffer: String::with_capacity(1024),
}
@@ -542,7 +560,7 @@
/// Try to update the number of columns in the current terminal,
fn update_size(&mut self) {
- let (cols, _) = get_win_size();
+ let (cols, _) = get_win_size(&self.out);
self.cols = cols;
}
@@ -553,13 +571,13 @@
/// Try to get the number of rows in the current terminal,
/// or assume 24 if it fails.
fn get_rows(&self) -> usize {
- let (_, rows) = get_win_size();
+ let (_, rows) = get_win_size(&self.out);
rows
}
}
static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT;
-static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT;
+static SIGWINCH: atomic::AtomicBool = atomic::AtomicBool::new(false);
fn install_sigwinch_handler() {
SIGWINCH_ONCE.call_once(|| unsafe {
@@ -585,6 +603,7 @@
stdin_isatty: bool,
stdout_isatty: bool,
pub(crate) color_mode: ColorMode,
+ stream_type: OutputStreamType,
}
impl Term for PosixTerminal {
@@ -592,12 +611,13 @@
type Reader = PosixRawReader;
type Writer = PosixRenderer;
- fn new(color_mode: ColorMode) -> PosixTerminal {
+ fn new(color_mode: ColorMode, stream_type: OutputStreamType) -> PosixTerminal {
let term = PosixTerminal {
unsupported: is_unsupported_term(),
stdin_isatty: is_a_tty(STDIN_FILENO),
stdout_isatty: is_a_tty(STDOUT_FILENO),
color_mode,
+ stream_type,
};
if !term.unsupported && term.stdin_isatty && term.stdout_isatty {
install_sigwinch_handler();
@@ -645,8 +665,9 @@
| InputFlags::ISTRIP
| InputFlags::IXON);
// we don't want raw output, it turns newlines into straight linefeeds
- // raw.c_oflag = raw.c_oflag & !(OutputFlags::OPOST); // disable all output
- // processing
+ // disable all output processing
+ // raw.c_oflag = raw.c_oflag & !(OutputFlags::OPOST);
+
// character-size mark (8 bits)
raw.control_flags |= ControlFlags::CS8;
// disable echoing, canonical mode, extended input processing and signals
@@ -664,7 +685,7 @@
}
fn create_writer(&self) -> PosixRenderer {
- PosixRenderer::new()
+ PosixRenderer::new(self.stream_type)
}
}
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index 857fbfc..afe70ce 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -1,5 +1,5 @@
//! Windows specific definitions
-use std::io::{self, Stdout, Write};
+use std::io::{self, Write};
use std::mem;
use std::sync::atomic;
@@ -9,15 +9,18 @@
use winapi::um::{consoleapi, handleapi, processenv, winbase, wincon, winuser};
use super::{truncate, Position, RawMode, RawReader, Renderer, Term};
+use config::OutputStreamType;
use config::{ColorMode, Config};
-use consts::{self, KeyPress};
use error;
use highlight::Highlighter;
+use keys::{self, KeyPress};
use line_buffer::LineBuffer;
use Result;
+use StdStream;
const STDIN_FILENO: DWORD = winbase::STD_INPUT_HANDLE;
const STDOUT_FILENO: DWORD = winbase::STD_OUTPUT_HANDLE;
+const STDERR_FILENO: DWORD = winbase::STD_ERROR_HANDLE;
fn get_std_handle(fd: DWORD) -> Result<HANDLE> {
let handle = unsafe { processenv::GetStdHandle(fd) };
@@ -66,8 +69,8 @@
pub struct ConsoleMode {
original_stdin_mode: DWORD,
stdin_handle: HANDLE,
- original_stdout_mode: Option<DWORD>,
- stdout_handle: HANDLE,
+ original_stdstream_mode: Option<DWORD>,
+ stdstream_handle: HANDLE,
}
impl RawMode for Mode {
@@ -77,10 +80,10 @@
self.stdin_handle,
self.original_stdin_mode,
));
- if let Some(original_stdout_mode) = self.original_stdout_mode {
+ if let Some(original_stdstream_mode) = self.original_stdstream_mode {
check!(consoleapi::SetConsoleMode(
- self.stdout_handle,
- original_stdout_mode,
+ self.stdstream_handle,
+ original_stdstream_mode,
));
}
Ok(())
@@ -93,7 +96,7 @@
}
impl ConsoleRawReader {
- pub fn new() -> Result<ConsoleRawReader> {
+ pub fn new(stream: OutputStreamType) -> Result<ConsoleRawReader> {
let handle = try!(get_std_handle(STDIN_FILENO));
Ok(ConsoleRawReader { handle })
}
@@ -221,7 +224,13 @@
if meta {
return Ok(KeyPress::Meta(c));
} else {
- return Ok(consts::char_to_key_press(c));
+ let mut key = keys::char_to_key_press(c);
+ if key == KeyPress::Tab && shift {
+ key = KeyPress::BackTab;
+ } else if key == KeyPress::Char(' ') && ctrl {
+ key = KeyPress::Ctrl(' ');
+ }
+ return Ok(key);
}
}
}
@@ -229,18 +238,18 @@
}
pub struct ConsoleRenderer {
- out: Stdout,
+ out: StdStream,
handle: HANDLE,
cols: usize, // Number of columns in terminal
buffer: String,
}
impl ConsoleRenderer {
- fn new(handle: HANDLE) -> ConsoleRenderer {
+ fn new(handle: HANDLE, stream_type: OutputStreamType) -> ConsoleRenderer {
// Multi line editing is enabled by ENABLE_WRAP_AT_EOL_OUTPUT mode
let (cols, _) = get_win_size(handle);
ConsoleRenderer {
- out: io::stdout(),
+ out: StdStream::from_stream_type(stream_type),
handle,
cols,
buffer: String::with_capacity(1024),
@@ -417,10 +426,11 @@
pub struct Console {
stdin_isatty: bool,
stdin_handle: HANDLE,
- stdout_isatty: bool,
- stdout_handle: HANDLE,
+ stdstream_isatty: bool,
+ stdstream_handle: HANDLE,
pub(crate) color_mode: ColorMode,
ansi_colors_supported: bool,
+ stream_type: OutputStreamType,
}
impl Console {}
@@ -430,7 +440,7 @@
type Reader = ConsoleRawReader;
type Writer = ConsoleRenderer;
- fn new(color_mode: ColorMode) -> Console {
+ fn new(color_mode: ColorMode, stream_type: OutputStreamType) -> Console {
use std::ptr;
let stdin_handle = get_std_handle(STDIN_FILENO);
let stdin_isatty = match stdin_handle {
@@ -440,8 +450,13 @@
}
Err(_) => false,
};
- let stdout_handle = get_std_handle(STDOUT_FILENO);
- let stdout_isatty = match stdout_handle {
+
+ let stdstream_handle = get_std_handle(if stream_type == OutputStreamType::Stdout {
+ STDOUT_FILENO
+ } else {
+ STDERR_FILENO
+ });
+ let stdstream_isatty = match stdstream_handle {
Ok(handle) => {
// If this function doesn't fail then fd is a TTY
get_console_mode(handle).is_ok()
@@ -452,10 +467,11 @@
Console {
stdin_isatty,
stdin_handle: stdin_handle.unwrap_or(ptr::null_mut()),
- stdout_isatty,
- stdout_handle: stdout_handle.unwrap_or(ptr::null_mut()),
+ stdstream_isatty,
+ stdstream_handle: stdstream_handle.unwrap_or(ptr::null_mut()),
color_mode,
ansi_colors_supported: false,
+ stream_type,
}
}
@@ -471,7 +487,7 @@
fn colors_enabled(&self) -> bool {
// TODO ANSI Colors & Windows <10
match self.color_mode {
- ColorMode::Enabled => self.stdout_isatty && self.ansi_colors_supported,
+ ColorMode::Enabled => self.stdstream_isatty && self.ansi_colors_supported,
ColorMode::Forced => true,
ColorMode::Disabled => false,
}
@@ -501,16 +517,16 @@
raw |= wincon::ENABLE_WINDOW_INPUT;
check!(consoleapi::SetConsoleMode(self.stdin_handle, raw));
- let original_stdout_mode = if self.stdout_isatty {
- let original_stdout_mode = try!(get_console_mode(self.stdout_handle));
+ let original_stdstream_mode = if self.stdstream_isatty {
+ let original_stdstream_mode = try!(get_console_mode(self.stdstream_handle));
// To enable ANSI colors (Windows 10 only):
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
- if original_stdout_mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
- let raw = original_stdout_mode | wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+ if original_stdstream_mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
+ let raw = original_stdstream_mode | wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
self.ansi_colors_supported =
- unsafe { consoleapi::SetConsoleMode(self.stdout_handle, raw) != 0 };
+ unsafe { consoleapi::SetConsoleMode(self.stdstream_handle, raw) != 0 };
}
- Some(original_stdout_mode)
+ Some(original_stdstream_mode)
} else {
None
};
@@ -518,16 +534,16 @@
Ok(Mode {
original_stdin_mode,
stdin_handle: self.stdin_handle,
- original_stdout_mode,
- stdout_handle: self.stdout_handle,
+ original_stdstream_mode,
+ stdstream_handle: self.stdstream_handle,
})
}
fn create_reader(&self, _: &Config) -> Result<ConsoleRawReader> {
- ConsoleRawReader::new()
+ ConsoleRawReader::new(self.stream_type)
}
fn create_writer(&self) -> ConsoleRenderer {
- ConsoleRenderer::new(self.stdout_handle)
+ ConsoleRenderer::new(self.stdstream_handle, self.stream_type)
}
}