From 79b48ed5a42a6244ce3570c6548daf6d5500ac27 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 31 May 2016 16:36:56 +0200 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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