First draft of highlight support
diff --git a/examples/example.rs b/examples/example.rs
index 3e75024..ce41cf4 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -2,6 +2,7 @@
extern crate rustyline;
use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
+use std::borrow::Cow::{self, Borrowed, Owned};
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::error::ReadlineError;
@@ -9,13 +10,8 @@
use rustyline::hint::Hinter;
use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, Helper, KeyPress};
-// On unix platforms you can use ANSI escape sequences
-#[cfg(unix)]
-static PROMPT: &'static str = "\x1b[1;32m>>\x1b[0m ";
+static COLORED_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 = ">> ";
struct MyHelper(FilenameCompleter);
@@ -31,18 +27,22 @@
impl Hinter for MyHelper {
fn hint(&self, line: &str, _pos: usize) -> Option<String> {
if line == "hello" {
- if cfg!(target_os = "windows") {
- Some(" World".to_owned())
- } else {
- Some(" \x1b[1mWorld\x1b[m".to_owned())
- }
+ Some(" World".to_owned())
} else {
None
}
}
}
-impl Highlighter for MyHelper {}
+impl Highlighter for MyHelper {
+ fn highlight_prompt<'p>(&self, _: &str) -> Cow<'static, str> {
+ Borrowed(COLORED_PROMPT)
+ }
+
+ fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
+ Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
+ }
+}
impl Helper for MyHelper {}
diff --git a/src/edit.rs b/src/edit.rs
index 6a82da6..f9fcca8 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -7,6 +7,7 @@
use unicode_width::UnicodeWidthChar;
use super::Result;
+use highlight::Highlighter;
use hint::Hinter;
use history::{Direction, History};
use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
@@ -30,6 +31,7 @@
byte_buffer: [u8; 4],
pub changes: Rc<RefCell<Changeset>>, // changes to line, for undo/redo
pub hinter: Option<&'out Hinter>,
+ pub highlighter: Option<&'out Highlighter>,
}
impl<'out, 'prompt> State<'out, 'prompt> {
@@ -38,6 +40,7 @@
prompt: &'prompt str,
history_index: usize,
hinter: Option<&'out Hinter>,
+ highlighter: Option<&'out Highlighter>,
) -> State<'out, 'prompt> {
let capacity = MAX_LINE;
let prompt_size = out.calculate_position(prompt, Position::default());
@@ -53,6 +56,7 @@
byte_buffer: [0; 4],
changes: Rc::new(RefCell::new(Changeset::new())),
hinter,
+ highlighter,
}
}
@@ -96,7 +100,16 @@
if self.cursor == cursor {
return Ok(());
}
- try!(self.out.move_cursor(self.cursor, cursor));
+ if self.highlighter.map_or(false, |h| {
+ self.line
+ .grapheme_at_cursor()
+ .map_or(false, |s| h.highlight_char(s))
+ }) {
+ let prompt_size = self.prompt_size;
+ try!(self.refresh(self.prompt, prompt_size, None));
+ } else {
+ try!(self.out.move_cursor(self.cursor, cursor));
+ }
self.cursor = cursor;
Ok(())
}
@@ -109,6 +122,7 @@
hint,
self.cursor.row,
self.old_rows,
+ self.highlighter,
));
self.cursor = cursor;
@@ -175,7 +189,8 @@
let hint = self.hint();
if n == 1
&& self.cursor.col + ch.width().unwrap_or(0) < self.out.get_columns()
- && hint.is_none()
+ && hint.is_none() // 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.
let cursor = self
@@ -490,6 +505,7 @@
byte_buffer: [0; 4],
changes: Rc::new(RefCell::new(Changeset::new())),
hinter: None,
+ highlighter: None,
}
}
diff --git a/src/highlight.rs b/src/highlight.rs
index eee97ff..04bc042 100644
--- a/src/highlight.rs
+++ b/src/highlight.rs
@@ -1,3 +1,4 @@
+use config::CompletionType;
///! Syntax highlighting
use std::borrow::Cow::{self, Borrowed};
@@ -5,22 +6,28 @@
/// Rustyline will try to handle escape sequence for ansi color on windows
/// when not supported natively (windows <10).
///
-/// TODO to be used
+/// Currently, the highlighted version *must* have the same display width as
+/// the original input.
pub trait Highlighter {
/// Takes the currently edited `line` with the cursor `pos`ition and
- /// returns the highlighted version (with ANSI color)
- /// and new cursor position which may have been shifted by highlighting.
+ /// returns the highlighted version (with ANSI color).
///
/// For example, you can implement
/// [blink-matching-paren](https://www.gnu.org/software/bash/manual/html_node/Readline-Init-File-Syntax.html).
- fn highlight<'l>(&self, line: &'l str, pos: usize) -> (Cow<'l, str>, usize) {
- (Borrowed(line), pos)
+ fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
+ let _ = pos;
+ Borrowed(line)
}
/// Takes the `prompt` and
/// returns the highlighted version (with ANSI color).
fn highlight_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
Borrowed(prompt)
}
+ /// Takes the dynamic `prompt` and
+ /// returns the highlighted version (with ANSI color).
+ fn highlight_dynamic_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
+ Borrowed(prompt)
+ }
/// Takes the `hint` and
/// returns the highlighted version (with ANSI color).
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
@@ -28,9 +35,25 @@
}
/// Takes the completion `canditate` and
/// returns the highlighted version (with ANSI color).
- fn highlight_candidate<'c>(&self, candidate: &'c str) -> Cow<'c, str> {
+ ///
+ /// Currently, used only with `CompletionType::List`.
+ fn highlight_candidate<'c>(
+ &self,
+ candidate: &'c str,
+ completion: CompletionType,
+ ) -> Cow<'c, str> {
+ let _ = completion;
Borrowed(candidate)
}
+ /// Tells if the `ch`ar needs to be highlighted when typed or when cursor
+ /// is moved under.
+ ///
+ /// Used to optimize refresh when a character is inserted or the cursor is
+ /// moved.
+ fn highlight_char(&self, grapheme: &str) -> bool {
+ let _ = grapheme;
+ false
+ }
}
impl Highlighter for () {}
diff --git a/src/lib.rs b/src/lib.rs
index 4f7627a..6c39388 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -77,6 +77,7 @@
s: &mut State,
input_state: &mut InputState,
completer: &C,
+ highlighter: Option<&Highlighter>,
config: &Config,
) -> Result<Option<Cmd>> {
// get a list of completions
@@ -95,7 +96,14 @@
loop {
// Show completion or original buffer
if i < candidates.len() {
- completer.update(&mut s.line, start, candidates[i].replacement());
+ let candidate = candidates[i].replacement();
+ // TODO we can't highlight the line buffer directly
+ /*let candidate = if let Some(highlighter) = s.highlighter {
+ highlighter.highlight_candidate(candidate, CompletionType::Circular)
+ } else {
+ Borrowed(candidate)
+ };*/
+ completer.update(&mut s.line, start, candidate);
try!(s.refresh_line());
} else {
// Restore current edited line
@@ -172,7 +180,7 @@
true
};
if show_completions {
- page_completions(rdr, s, input_state, &candidates)
+ page_completions(rdr, s, input_state, highlighter, &candidates)
} else {
try!(s.refresh_line());
Ok(None)
@@ -186,6 +194,7 @@
rdr: &mut R,
s: &mut State,
input_state: &mut InputState,
+ highlighter: Option<&Highlighter>,
candidates: &[C],
) -> Result<Option<Cmd>> {
use std::cmp;
@@ -240,8 +249,12 @@
let i = (col * num_rows) + row;
if i < candidates.len() {
let candidate = &candidates[i].display();
- ab.push_str(candidate);
let width = candidate.width();
+ if let Some(highlighter) = highlighter {
+ ab.push_str(&highlighter.highlight_candidate(candidate, CompletionType::List));
+ } else {
+ ab.push_str(candidate);
+ }
if ((col + 1) * num_rows) + row < candidates.len() {
for _ in width..max_width {
ab.push(' ');
@@ -353,11 +366,22 @@
) -> Result<String> {
let completer = editor.helper.as_ref();
let hinter = editor.helper.as_ref().map(|h| h as &Hinter);
+ let highlighter = if editor.term.colors_enabled() {
+ editor.helper.as_ref().map(|h| h as &Highlighter)
+ } else {
+ None
+ };
let mut stdout = editor.term.create_writer();
editor.reset_kill_ring(); // TODO recreate a new kill ring vs Arc<Mutex<KillRing>>
- let mut s = State::new(&mut stdout, prompt, editor.history.len(), hinter);
+ let mut s = State::new(
+ &mut stdout,
+ prompt,
+ editor.history.len(),
+ hinter,
+ highlighter,
+ );
let mut input_state = InputState::new(&editor.config, Arc::clone(&editor.custom_bindings));
s.line.set_delete_listener(editor.kill_ring.clone());
@@ -387,6 +411,7 @@
&mut s,
&mut input_state,
completer.unwrap(),
+ highlighter,
&editor.config,
));
if next.is_some() {
@@ -650,6 +675,7 @@
custom_bindings: Arc<RwLock<HashMap<KeyPress, Cmd>>>,
}
+#[allow(new_without_default)]
impl<H: Helper> Editor<H> {
/// Create an editor with the default configuration
pub fn new() -> Editor<H> {
diff --git a/src/line_buffer.rs b/src/line_buffer.rs
index 1c220ca..e17fd62 100644
--- a/src/line_buffer.rs
+++ b/src/line_buffer.rs
@@ -153,7 +153,7 @@
}
/// Returns the character at current cursor position.
- fn grapheme_at_cursor(&self) -> Option<&str> {
+ pub(crate) fn grapheme_at_cursor(&self) -> Option<&str> {
if self.pos == self.buf.len() {
None
} else {
diff --git a/src/test/mod.rs b/src/test/mod.rs
index f2d6ea2..1ce9881 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -45,6 +45,7 @@
&mut s,
&mut input_state,
&completer,
+ None,
&Config::default(),
).unwrap();
assert_eq!(Some(Cmd::AcceptLine), cmd);
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
index e96f676..e8c7db4 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -5,6 +5,7 @@
use config::{ColorMode, Config};
use consts::KeyPress;
+use highlight::Highlighter;
use line_buffer::LineBuffer;
use Result;
@@ -42,6 +43,7 @@
hint: Option<String>,
current_row: usize,
old_rows: usize,
+ highlighter: Option<&Highlighter>,
) -> Result<(Position, Position)>;
/// Calculate the number of columns and rows used to display `s` on a
@@ -86,8 +88,17 @@
hint: Option<String>,
current_row: usize,
old_rows: usize,
+ highlighter: Option<&Highlighter>,
) -> Result<(Position, Position)> {
- (**self).refresh_line(prompt, prompt_size, line, hint, current_row, old_rows)
+ (**self).refresh_line(
+ prompt,
+ prompt_size,
+ line,
+ hint,
+ current_row,
+ old_rows,
+ highlighter,
+ )
}
fn calculate_position(&self, s: &str, orig: Position) -> Position {
diff --git a/src/tty/test.rs b/src/tty/test.rs
index 4166948..752bae3 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -7,6 +7,7 @@
use config::{ColorMode, Config};
use consts::KeyPress;
use error::ReadlineError;
+use highlight::Highlighter;
use line_buffer::LineBuffer;
use Result;
@@ -71,6 +72,7 @@
hint: Option<String>,
_: usize,
_: usize,
+ _: Option<&Highlighter>,
) -> Result<(Position, Position)> {
let cursor = self.calculate_position(&line[..line.pos()], prompt_size);
if let Some(hint) = hint {
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index b99561a..5cc97e3 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -17,6 +17,7 @@
use config::{ColorMode, Config};
use consts::{self, KeyPress};
use error;
+use highlight::Highlighter;
use line_buffer::LineBuffer;
use Result;
@@ -427,6 +428,7 @@
hint: Option<String>,
current_row: usize,
old_rows: usize,
+ highlighter: Option<&Highlighter>,
) -> Result<(Position, Position)> {
use std::fmt::Write;
let mut ab = String::new();
@@ -450,13 +452,25 @@
// clear the line
ab.push_str("\r\x1b[0K");
- // display the prompt
- ab.push_str(prompt);
- // display the input line
- ab.push_str(line);
+ if let Some(highlighter) = highlighter {
+ // display the prompt
+ ab.push_str(&highlighter.highlight_prompt(prompt));
+ // display the input line
+ ab.push_str(&highlighter.highlight(line, line.pos()));
+ } else {
+ // display the prompt
+ ab.push_str(prompt);
+ // display the input line
+ ab.push_str(line);
+ }
// display hint
if let Some(hint) = hint {
- ab.push_str(truncate(&hint, end_pos.col, self.cols));
+ let truncate = truncate(&hint, end_pos.col, self.cols);
+ if let Some(highlighter) = highlighter {
+ ab.push_str(&highlighter.highlight_hint(truncate));
+ } else {
+ ab.push_str(truncate);
+ }
}
// we have to generate our own newline on line wrap
if end_pos.col == 0 && end_pos.row > 0 {
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index a27b9fb..210cefe 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -12,6 +12,7 @@
use config::{ColorMode, Config};
use consts::{self, KeyPress};
use error;
+use highlight::Highlighter;
use line_buffer::LineBuffer;
use Result;
@@ -192,8 +193,7 @@
winuser::VK_F11 => return Ok(KeyPress::F(11)),
winuser::VK_F12 => return Ok(KeyPress::F(12)),
// winuser::VK_BACK is correctly handled because the key_event.UnicodeChar is
- // also
- // set.
+ // also set.
_ => continue,
};
} else if utf16 == 27 {
@@ -291,6 +291,7 @@
hint: Option<String>,
current_row: usize,
old_rows: usize,
+ highlighter: Option<&Highlighter>,
) -> Result<(Position, Position)> {
// calculate the position of the end of the input line
let end_pos = self.calculate_position(line, prompt_size);
@@ -307,14 +308,26 @@
info.dwCursorPosition,
));
let mut ab = String::new();
- // display the prompt
- // TODO handle ansi escape code (SetConsoleTextAttribute)
- ab.push_str(prompt);
- // display the input line
- ab.push_str(&line);
+ if let Some(highlighter) = highlighter {
+ // TODO handle ansi escape code (SetConsoleTextAttribute)
+ // display the prompt
+ ab.push_str(&highlighter.highlight_prompt(prompt));
+ // display the input line
+ ab.push_str(&highlighter.highlight(line, pos));
+ } else {
+ // display the prompt
+ ab.push_str(prompt);
+ // display the input line
+ ab.push_str(line);
+ }
// display hint
if let Some(hint) = hint {
- ab.push_str(truncate(&hint, end_pos.col, self.cols));
+ let truncate = truncate(&hint, end_pos.col, self.cols);
+ if let Some(highlighter) = highlighter {
+ ab.push_str(&highlighter.highlight_hint(truncate));
+ } else {
+ ab.push_str(truncate);
+ }
}
try!(self.write_and_flush(ab.as_bytes()));
@@ -450,8 +463,9 @@
}
fn colors_enabled(&self) -> bool {
+ // TODO ANSI Colors & Windows <10
match self.color_mode {
- ColorMode::Enabled => self.stdout_isatty,
+ ColorMode::Enabled => self.stdout_isatty && self.ansi_colors_supported,
ColorMode::Forced => true,
ColorMode::Disabled => false,
}