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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,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, MatchingBracketHighlighter};
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter, PromptState};
use rustyline::hint::{Hinter, HistoryHinter};
use rustyline::validate::{self, MatchingBracketValidator, Validator};
use rustyline::{Cmd, CompletionType, Config, Context, EditMode, Editor, KeyPress};
Expand Down Expand Up @@ -41,9 +41,9 @@ impl Highlighter for MyHelper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
prompt_state: PromptState,
) -> Cow<'b, str> {
if default {
if prompt_state.default {
Borrowed(&self.colored_prompt)
} else {
Borrowed(prompt)
Expand Down
51 changes: 51 additions & 0 deletions examples/vi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! This example shows how a highlighter can be used to show Vi mode indicators
//! while editing.

use std::borrow::Cow::{self, Borrowed, Owned};

use rustyline::highlight::{Highlighter, PromptState};
use rustyline::{Config, EditMode, Editor, InputMode};
use rustyline_derive::{Completer, Helper, Hinter, Validator};

#[derive(Completer, Helper, Hinter, Validator)]
struct ViHighlighter {
command: &'static str,
insert: &'static str,
replace: &'static str,
}

impl Highlighter for ViHighlighter {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
prompt_state: PromptState,
) -> Cow<'b, str> {
if prompt_state.default {
let indicator = match prompt_state.mode {
Some(InputMode::Command) => self.command,
Some(InputMode::Insert) => self.insert,
Some(InputMode::Replace) => self.replace,
_ => " ",
};
Owned(format!("{}{}", indicator, &prompt[1..]))
} else {
Borrowed(prompt)
}
}
}

fn main() -> rustyline::Result<()> {
let config = Config::builder().edit_mode(EditMode::Vi).build();
let helper = ViHighlighter {
command: ":",
insert: "+",
replace: "#",
};
let mut rl = Editor::with_config(config);
rl.set_helper(Some(helper));

let username = rl.readline(" > ")?;
println!("Echo: {}", username);

Ok(())
}
30 changes: 27 additions & 3 deletions src/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ use std::rc::Rc;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthChar;

use super::{Context, Helper, Result};
use crate::highlight::Highlighter;
use super::{Context, EditMode, Helper, Result};
use crate::config::Config;
use crate::highlight::{Highlighter, PromptState};
use crate::history::Direction;
use crate::keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
use crate::keymap::{InputState, Invoke, Refresher};
use crate::keymap::{InputMode, InputState, Invoke, Refresher};
use crate::layout::{Layout, Position};
use crate::line_buffer::{LineBuffer, WordAction, MAX_LINE};
use crate::tty::{Renderer, Term, Terminal};
Expand All @@ -33,6 +34,7 @@ pub struct State<'out, 'prompt, H: Helper> {
pub ctx: Context<'out>, // Give access to history for `hinter`
pub hint: Option<String>, // last hint displayed
highlight_char: bool, // `true` if a char has been highlighted
input_mode: Option<InputMode>,
Copy link
Copy Markdown
Collaborator

@gwenn gwenn Apr 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may avoid to duplicate input_mode if we move InputState in State ?
I will give a try and do it in a dedicated PR.

}

enum Info<'m> {
Expand All @@ -47,6 +49,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
prompt: &'prompt str,
helper: Option<&'out H>,
ctx: Context<'out>,
config: &'prompt Config,
) -> State<'out, 'prompt, H> {
let prompt_size = out.calculate_position(prompt, Position::default());
State {
Expand All @@ -62,6 +65,10 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
ctx,
hint: None,
highlight_char: false,
input_mode: match &config.edit_mode() {
EditMode::Vi => Some(InputMode::Insert),
EditMode::Emacs => None,
},
}
}

Expand Down Expand Up @@ -164,6 +171,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
&self.layout,
&new_layout,
highlighter,
PromptState::new(default_prompt, self.input_mode),
)?;
self.layout = new_layout;

Expand Down Expand Up @@ -261,10 +269,25 @@ impl<'out, 'prompt, H: Helper> Refresher for State<'out, 'prompt, H> {

fn doing_insert(&mut self) {
self.changes.borrow_mut().begin();
if let Some(mode) = &mut self.input_mode {
*mode = InputMode::Insert;
self.refresh_line().unwrap();
}
}

fn doing_replace(&mut self) {
if let Some(mode) = &mut self.input_mode {
*mode = InputMode::Replace;
self.refresh_line().unwrap();
}
}

fn done_inserting(&mut self) {
self.changes.borrow_mut().end();
if let Some(mode) = &mut self.input_mode {
*mode = InputMode::Command;
self.refresh_line().unwrap();
}
}

fn last_insert(&self) -> Option<String> {
Expand Down Expand Up @@ -675,6 +698,7 @@ pub fn init_state<'out, H: Helper>(
ctx: Context::new(history),
hint: Some("hint".to_owned()),
highlight_char: false,
input_mode: None,
}
}

Expand Down
25 changes: 21 additions & 4 deletions src/highlight.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
//! Syntax highlighting

use crate::config::CompletionType;
use crate::keymap::InputMode;
use memchr::memchr;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::cell::Cell;

/// The current state of the prompt.
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct PromptState {
/// Is it the default prompt.
pub default: bool,
/// Current Vi mode.
pub mode: Option<InputMode>,
}

impl PromptState {
/// Construct a new prompt state.
pub fn new(default: bool, mode: Option<InputMode>) -> Self {
Self { default, mode }
}
}

/// Syntax highlighter with [ANSI color](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters).
/// Rustyline will try to handle escape sequence for ANSI color on windows
/// when not supported natively (windows <10).
Expand All @@ -26,9 +43,9 @@ pub trait Highlighter {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
prompt_state: PromptState,
) -> Cow<'b, str> {
let _ = default;
let _ = prompt_state;
Borrowed(prompt)
}
/// Takes the `hint` and
Expand Down Expand Up @@ -69,9 +86,9 @@ impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
prompt_state: PromptState,
) -> Cow<'b, str> {
(**self).highlight_prompt(prompt, default)
(**self).highlight_prompt(prompt, prompt_state)
}

fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Expand Down
16 changes: 13 additions & 3 deletions src/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,9 @@ impl Movement {
}
}

#[derive(PartialEq)]
enum InputMode {
/// Vi input mode.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum InputMode {
/// Vi Command/Alternate
Command,
/// Insert/Input mode
Expand Down Expand Up @@ -332,6 +333,8 @@ pub trait Refresher {
fn doing_insert(&mut self);
/// Vi only, switch to command mode.
fn done_inserting(&mut self);
/// Vi only, switch to replace mode.
fn doing_replace(&mut self);
/// Vi only, last text inserted.
fn last_insert(&self) -> Option<String>;
/// Returns `true` if the cursor is currently at the end of the line.
Expand Down Expand Up @@ -617,12 +620,16 @@ impl InputState {
KeyPress::Char('c') => {
self.input_mode = InputMode::Insert;
match self.vi_cmd_motion(rdr, wrt, key, n)? {
Some(mvt) => Cmd::Replace(mvt, None),
Some(mvt) => {
wrt.doing_insert();
Cmd::Replace(mvt, None)
}
None => Cmd::Unknown,
}
}
KeyPress::Char('C') => {
self.input_mode = InputMode::Insert;
wrt.doing_insert();
Cmd::Replace(Movement::EndOfLine, None)
}
KeyPress::Char('d') => match self.vi_cmd_motion(rdr, wrt, key, n)? {
Expand Down Expand Up @@ -675,16 +682,19 @@ impl InputState {
KeyPress::Char('R') => {
// vi-replace-mode (overwrite-mode)
self.input_mode = InputMode::Replace;
wrt.doing_replace();
Cmd::Replace(Movement::ForwardChar(0), None)
}
KeyPress::Char('s') => {
// vi-substitute-char:
self.input_mode = InputMode::Insert;
wrt.doing_insert();
Cmd::Replace(Movement::ForwardChar(n), None)
}
KeyPress::Char('S') => {
// vi-substitute-line:
self.input_mode = InputMode::Insert;
wrt.doing_insert();
Cmd::Replace(Movement::WholeLine, None)
}
KeyPress::Char('u') => Cmd::Undo(n),
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ use crate::edit::State;
use crate::highlight::Highlighter;
use crate::hint::Hinter;
use crate::history::{Direction, History};
pub use crate::keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
pub use crate::keymap::{Anchor, At, CharSearch, Cmd, InputMode, Movement, RepeatCount, Word};
use crate::keymap::{InputState, Refresher};
pub use crate::keys::KeyPress;
use crate::kill_ring::{KillRing, Mode};
Expand Down Expand Up @@ -424,7 +424,7 @@ fn readline_edit<H: Helper>(

editor.reset_kill_ring(); // TODO recreate a new kill ring vs Arc<Mutex<KillRing>>
let ctx = Context::new(&editor.history);
let mut s = State::new(&mut stdout, prompt, helper, ctx);
let mut s = State::new(&mut stdout, prompt, helper, ctx, &editor.config);
let mut input_state = InputState::new(&editor.config, Arc::clone(&editor.custom_bindings));

s.line.set_delete_listener(editor.kill_ring.clone());
Expand Down
14 changes: 12 additions & 2 deletions src/tty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use unicode_width::UnicodeWidthStr;

use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
use crate::highlight::Highlighter;
use crate::highlight::{Highlighter, PromptState};
use crate::keys::KeyPress;
use crate::layout::{Layout, Position};
use crate::line_buffer::LineBuffer;
Expand Down Expand Up @@ -42,6 +42,7 @@ pub trait Renderer {
old_layout: &Layout,
new_layout: &Layout,
highlighter: Option<&dyn Highlighter>,
prompt_state: PromptState,
) -> Result<()>;

/// Compute layout for rendering prompt + line + some info (either hint,
Expand Down Expand Up @@ -121,8 +122,17 @@ impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
old_layout: &Layout,
new_layout: &Layout,
highlighter: Option<&dyn Highlighter>,
prompt_state: PromptState,
) -> Result<()> {
(**self).refresh_line(prompt, line, hint, old_layout, new_layout, highlighter)
(**self).refresh_line(
prompt,
line,
hint,
old_layout,
new_layout,
highlighter,
prompt_state,
)
}

fn calculate_position(&self, s: &str, orig: Position) -> Position {
Expand Down
3 changes: 2 additions & 1 deletion src/tty/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::vec::IntoIter;
use super::{RawMode, RawReader, Renderer, Term};
use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
use crate::error::ReadlineError;
use crate::highlight::Highlighter;
use crate::highlight::{Highlighter, PromptState};
use crate::keys::KeyPress;
use crate::layout::{Layout, Position};
use crate::line_buffer::LineBuffer;
Expand Down Expand Up @@ -83,6 +83,7 @@ impl Renderer for Sink {
_old_layout: &Layout,
_new_layout: &Layout,
_highlighter: Option<&dyn Highlighter>,
_indicator: PromptState,
) -> Result<()> {
Ok(())
}
Expand Down
20 changes: 14 additions & 6 deletions src/tty/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use utf8parse::{Parser, Receiver};
use super::{width, RawMode, RawReader, Renderer, Term};
use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
use crate::error;
use crate::highlight::Highlighter;
use crate::highlight::{Highlighter, PromptState};
use crate::keys::{self, KeyPress};
use crate::layout::{Layout, Position};
use crate::line_buffer::LineBuffer;
Expand Down Expand Up @@ -539,11 +539,11 @@ impl Renderer for PosixRenderer {
old_layout: &Layout,
new_layout: &Layout,
highlighter: Option<&dyn Highlighter>,
prompt_state: PromptState,
) -> Result<()> {
use std::fmt::Write;
self.buffer.clear();

let default_prompt = new_layout.default_prompt;
let cursor = new_layout.cursor;
let end_pos = new_layout.end;
let current_row = old_layout.cursor.row;
Expand All @@ -566,7 +566,7 @@ impl Renderer for PosixRenderer {
if let Some(highlighter) = highlighter {
// display the prompt
self.buffer
.push_str(&highlighter.highlight_prompt(prompt, default_prompt));
.push_str(&highlighter.highlight_prompt(prompt, prompt_state));
// display the input line
self.buffer
.push_str(&highlighter.highlight(line, line.pos()));
Expand Down Expand Up @@ -891,7 +891,7 @@ fn write_and_flush(out: OutputStreamType, buf: &[u8]) -> Result<()> {

#[cfg(test)]
mod test {
use super::{Position, PosixRenderer, PosixTerminal, Renderer};
use super::{Position, PosixRenderer, PosixTerminal, PromptState, Renderer};
use crate::config::{BellStyle, OutputStreamType};
use crate::line_buffer::LineBuffer;

Expand Down Expand Up @@ -941,8 +941,16 @@ mod test {
let new_layout = out.compute_layout(prompt_size, default_prompt, &line, None);
assert_eq!(Position { col: 1, row: 1 }, new_layout.cursor);
assert_eq!(new_layout.cursor, new_layout.end);
out.refresh_line(prompt, &line, None, &old_layout, &new_layout, None)
.unwrap();
out.refresh_line(
prompt,
&line,
None,
&old_layout,
&new_layout,
None,
PromptState::new(true, None),
)
.unwrap();
#[rustfmt::skip]
assert_eq!(
"\r\u{1b}[0K> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\u{1b}[1C",
Expand Down
Loading