Skip to content

Commit

Permalink
Fix Highlighter::highlight_char
Browse files Browse the repository at this point in the history
Introduce `CmdKind` parameter to make the distinction between
edits vs cursor moves vs final refresh
  • Loading branch information
gwenn committed Oct 7, 2024
1 parent e27f961 commit c34ef39
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 54 deletions.
6 changes: 3 additions & 3 deletions examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::borrow::Cow::{self, Borrowed, Owned};

use rustyline::completion::FilenameCompleter;
use rustyline::error::ReadlineError;
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
use rustyline::highlight::{CmdKind, Highlighter, MatchingBracketHighlighter};
use rustyline::hint::HistoryHinter;
use rustyline::validate::MatchingBracketValidator;
use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, KeyEvent};
Expand Down Expand Up @@ -41,8 +41,8 @@ impl Highlighter for MyHelper {
self.highlighter.highlight(line, pos)
}

fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
self.highlighter.highlight_char(line, pos, forced)
fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool {
self.highlighter.highlight_char(line, pos, kind)
}
}

Expand Down
10 changes: 7 additions & 3 deletions examples/read_password.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::borrow::Cow::{self, Borrowed, Owned};

use rustyline::config::Configurer;
use rustyline::highlight::Highlighter;
use rustyline::highlight::{CmdKind, Highlighter};
use rustyline::{ColorMode, Editor, Result};
use rustyline::{Completer, Helper, Hinter, Validator};

Expand All @@ -20,12 +20,16 @@ impl Highlighter for MaskingHighlighter {
}
}

fn highlight_char(&self, _line: &str, _pos: usize, _forced: bool) -> bool {
self.masking
fn highlight_char(&self, _line: &str, _pos: usize, kind: CmdKind) -> bool {
match kind {
CmdKind::MoveCursor => false,
_ => self.masking,
}
}
}

fn main() -> Result<()> {
env_logger::init();
println!("This is just a hack. Reading passwords securely requires more than that.");
let h = MaskingHighlighter { masking: false };
let mut rl = Editor::new()?;
Expand Down
4 changes: 2 additions & 2 deletions rustyline-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream {
::rustyline::highlight::Highlighter::highlight_candidate(&self.#field_name_or_index, candidate, completion)
}

fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
::rustyline::highlight::Highlighter::highlight_char(&self.#field_name_or_index, line, pos, forced)
fn highlight_char(&self, line: &str, pos: usize, kind: ::rustyline::highlight::CmdKind) -> bool {
::rustyline::highlight::Highlighter::highlight_char(&self.#field_name_or_index, line, pos, kind)
}
}
}
Expand Down
7 changes: 3 additions & 4 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::complete_hint_line;
use crate::config::Config;
use crate::edit::State;
use crate::error;
use crate::highlight::CmdKind;
use crate::history::SearchDirection;
use crate::keymap::{Anchor, At, Cmd, Movement, Word};
use crate::keymap::{InputState, Refresher};
Expand All @@ -28,9 +29,7 @@ pub fn execute<H: Helper>(
if s.has_hint() || !s.is_default_prompt() || s.highlight_char {
// Force a refresh without hints to leave the previous
// line as the user typed it after a newline.
s.forced_refresh = true;
s.refresh_line_with_msg(None)?;
s.forced_refresh = false;
s.refresh_line_with_msg(None, CmdKind::ForcedRefresh)?;
}
}
_ => {}
Expand Down Expand Up @@ -190,7 +189,7 @@ pub fn execute<H: Helper>(
}
Cmd::Move(Movement::EndOfBuffer) => {
// Move to the end of the buffer.
s.edit_move_buffer_end()?;
s.edit_move_buffer_end(CmdKind::MoveCursor)?;
}
Cmd::DowncaseWord => {
// lowercase word after point
Expand Down
52 changes: 24 additions & 28 deletions src/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use unicode_width::UnicodeWidthChar;

use super::{Context, Helper, Result};
use crate::error::ReadlineError;
use crate::highlight::Highlighter;
use crate::highlight::{CmdKind, Highlighter};
use crate::hint::Hint;
use crate::history::SearchDirection;
use crate::keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
Expand Down Expand Up @@ -36,7 +36,6 @@ pub struct State<'out, 'prompt, H: Helper> {
pub ctx: Context<'out>, // Give access to history for `hinter`
pub hint: Option<Box<dyn Hint>>, // last hint displayed
pub highlight_char: bool, // `true` if a char has been highlighted
pub forced_refresh: bool, // `true` if line is redraw without hint or highlight_char
}

enum Info<'m> {
Expand Down Expand Up @@ -66,7 +65,6 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
ctx,
hint: None,
highlight_char: false,
forced_refresh: false,
}
}

Expand Down Expand Up @@ -122,15 +120,15 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
);
}

pub fn move_cursor(&mut self) -> Result<()> {
pub fn move_cursor(&mut self, kind: CmdKind) -> Result<()> {
// calculate the desired position of the cursor
let cursor = self
.out
.calculate_position(&self.line[..self.line.pos()], self.prompt_size);
if self.layout.cursor == cursor {
return Ok(());
}
if self.highlight_char() {
if self.highlight_char(kind) {
let prompt_size = self.prompt_size;
self.refresh(self.prompt, prompt_size, true, Info::NoHint)?;
} else {
Expand Down Expand Up @@ -205,10 +203,9 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
}
}

fn highlight_char(&mut self) -> bool {
fn highlight_char(&mut self, kind: CmdKind) -> bool {
if let Some(highlighter) = self.highlighter() {
let highlight_char =
highlighter.highlight_char(&self.line, self.line.pos(), self.forced_refresh);
let highlight_char = highlighter.highlight_char(&self.line, self.line.pos(), kind);
if highlight_char {
self.highlight_char = true;
true
Expand Down Expand Up @@ -240,12 +237,12 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
if corrected || self.has_hint() || msg.is_some() {
// Force a refresh without hints to leave the previous
// line as the user typed it after a newline.
self.refresh_line_with_msg(msg.as_deref())?;
self.refresh_line_with_msg(msg.as_deref(), CmdKind::ForcedRefresh)?;
}
}
ValidationResult::Invalid(ref msg) => {
if corrected || self.has_hint() || msg.is_some() {
self.refresh_line_with_msg(msg.as_deref())?;
self.refresh_line_with_msg(msg.as_deref(), CmdKind::Other)?;
}
}
}
Expand All @@ -266,21 +263,21 @@ impl<'out, 'prompt, H: Helper> Refresher for State<'out, 'prompt, H> {
fn refresh_line(&mut self) -> Result<()> {
let prompt_size = self.prompt_size;
self.hint();
self.highlight_char();
self.highlight_char(CmdKind::Other);
self.refresh(self.prompt, prompt_size, true, Info::Hint)
}

fn refresh_line_with_msg(&mut self, msg: Option<&str>) -> Result<()> {
fn refresh_line_with_msg(&mut self, msg: Option<&str>, kind: CmdKind) -> Result<()> {
let prompt_size = self.prompt_size;
self.hint = None;
self.highlight_char();
self.highlight_char(kind);
self.refresh(self.prompt, prompt_size, true, Info::Msg(msg))
}

fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> {
let prompt_size = self.out.calculate_position(prompt, Position::default());
self.hint();
self.highlight_char();
self.highlight_char(CmdKind::Other);
self.refresh(prompt, prompt_size, false, Info::Hint)
}

Expand Down Expand Up @@ -361,7 +358,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
&& width != 0 // Ctrl-V + \t or \n ...
&& self.layout.cursor.col + width < self.out.get_columns()
&& (self.hint.is_none() && no_previous_hint) // TODO refresh only current line
&& !self.highlight_char()
&& !self.highlight_char(CmdKind::Other)
{
// Avoid a full update of the line in the trivial case.
self.layout.cursor.col += width;
Expand Down Expand Up @@ -454,7 +451,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
/// Move cursor on the left.
pub fn edit_move_backward(&mut self, n: RepeatCount) -> Result<()> {
if self.line.move_backward(n) {
self.move_cursor()
self.move_cursor(CmdKind::MoveCursor)
} else {
Ok(())
}
Expand All @@ -463,7 +460,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
/// Move cursor on the right.
pub fn edit_move_forward(&mut self, n: RepeatCount) -> Result<()> {
if self.line.move_forward(n) {
self.move_cursor()
self.move_cursor(CmdKind::MoveCursor)
} else {
Ok(())
}
Expand All @@ -472,7 +469,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
/// Move cursor to the start of the line.
pub fn edit_move_home(&mut self) -> Result<()> {
if self.line.move_home() {
self.move_cursor()
self.move_cursor(CmdKind::MoveCursor)
} else {
Ok(())
}
Expand All @@ -481,7 +478,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
/// Move cursor to the end of the line.
pub fn edit_move_end(&mut self) -> Result<()> {
if self.line.move_end() {
self.move_cursor()
self.move_cursor(CmdKind::MoveCursor)
} else {
Ok(())
}
Expand All @@ -490,16 +487,16 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
/// Move cursor to the start of the buffer.
pub fn edit_move_buffer_start(&mut self) -> Result<()> {
if self.line.move_buffer_start() {
self.move_cursor()
self.move_cursor(CmdKind::MoveCursor)
} else {
Ok(())
}
}

/// Move cursor to the end of the buffer.
pub fn edit_move_buffer_end(&mut self) -> Result<()> {
pub fn edit_move_buffer_end(&mut self, kind: CmdKind) -> Result<()> {
if self.line.move_buffer_end() {
self.move_cursor()
self.move_cursor(kind)
} else {
Ok(())
}
Expand Down Expand Up @@ -565,15 +562,15 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {

pub fn edit_move_to_prev_word(&mut self, word_def: Word, n: RepeatCount) -> Result<()> {
if self.line.move_to_prev_word(word_def, n) {
self.move_cursor()
self.move_cursor(CmdKind::MoveCursor)
} else {
Ok(())
}
}

pub fn edit_move_to_next_word(&mut self, at: At, word_def: Word, n: RepeatCount) -> Result<()> {
if self.line.move_to_next_word(at, word_def, n) {
self.move_cursor()
self.move_cursor(CmdKind::MoveCursor)
} else {
Ok(())
}
Expand All @@ -582,7 +579,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
/// Moves the cursor to the same column in the line above
pub fn edit_move_line_up(&mut self, n: RepeatCount) -> Result<bool> {
if self.line.move_to_line_up(n) {
self.move_cursor()?;
self.move_cursor(CmdKind::MoveCursor)?;
Ok(true)
} else {
Ok(false)
Expand All @@ -592,7 +589,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
/// Moves the cursor to the same column in the line above
pub fn edit_move_line_down(&mut self, n: RepeatCount) -> Result<bool> {
if self.line.move_to_line_down(n) {
self.move_cursor()?;
self.move_cursor(CmdKind::MoveCursor)?;
Ok(true)
} else {
Ok(false)
Expand All @@ -601,7 +598,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {

pub fn edit_move_to(&mut self, cs: CharSearch, n: RepeatCount) -> Result<()> {
if self.line.move_to(cs, n) {
self.move_cursor()
self.move_cursor(CmdKind::MoveCursor)
} else {
Ok(())
}
Expand Down Expand Up @@ -765,7 +762,6 @@ pub fn init_state<'out, H: Helper>(
ctx: Context::new(history),
hint: Some(Box::new("hint".to_owned())),
highlight_char: false,
forced_refresh: false,
}
}

Expand Down
26 changes: 18 additions & 8 deletions src/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ use crate::config::CompletionType;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::cell::Cell;

/// Describe which kind of action has been triggering the call to `Highlighter`.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CmdKind {
/// Cursor moved
MoveCursor,
/// Other action
Other,
/// Forced / final refresh (no auto-suggestion / hint, no matching bracket
/// highlighted, ...)
ForcedRefresh,
}

/// Syntax highlighter with [ANSI color](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters).
///
/// Currently, the highlighted version *must* have the same display width as
Expand Down Expand Up @@ -47,13 +59,11 @@ pub trait Highlighter {
}
/// Tells if `line` needs to be highlighted when a specific char is typed or
/// when cursor is moved under a specific char.
/// `forced` flag is `true` mainly when user presses Enter (i.e. transient
/// vs final highlight).
///
/// Used to optimize refresh when a character is inserted or the cursor is
/// moved.
fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
let _ = (line, pos, forced);
fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool {
let _ = (line, pos, kind);
false
}
}
Expand Down Expand Up @@ -85,8 +95,8 @@ impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H {
(**self).highlight_candidate(candidate, completion)
}

fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
(**self).highlight_char(line, pos, forced)
fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool {
(**self).highlight_char(line, pos, kind)
}
}

Expand Down Expand Up @@ -124,8 +134,8 @@ impl Highlighter for MatchingBracketHighlighter {
Borrowed(line)
}

fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
if forced {
fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool {
if kind == CmdKind::ForcedRefresh {
self.bracket.set(None);
return false;
}
Expand Down
3 changes: 2 additions & 1 deletion src/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use log::debug;

use super::Result;
use crate::highlight::CmdKind;
use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
use crate::tty::{self, RawReader, Term, Terminal};
use crate::{Config, EditMode};
Expand Down Expand Up @@ -374,7 +375,7 @@ pub trait Refresher {
/// cursor position, and number of columns of the terminal.
fn refresh_line(&mut self) -> Result<()>;
/// Same as [`refresh_line`] with a specific message instead of hint
fn refresh_line_with_msg(&mut self, msg: Option<&str>) -> Result<()>;
fn refresh_line_with_msg(&mut self, msg: Option<&str>, kind: CmdKind) -> Result<()>;
/// Same as `refresh_line` but with a dynamic prompt.
fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()>;
/// Vi only, switch to insert mode.
Expand Down
6 changes: 2 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ use crate::completion::{longest_common_prefix, Candidate, Completer};
pub use crate::config::{Behavior, ColorMode, CompletionType, Config, EditMode, HistoryDuplicates};
use crate::edit::State;
use crate::error::ReadlineError;
use crate::highlight::Highlighter;
use crate::highlight::{CmdKind, Highlighter};
use crate::hint::Hinter;
use crate::history::{DefaultHistory, History, SearchDirection};
pub use crate::keymap::{Anchor, At, CharSearch, Cmd, InputMode, Movement, RepeatCount, Word};
Expand Down Expand Up @@ -790,9 +790,7 @@ impl<H: Helper, I: History> Editor<H, I> {

// Move to end, in case cursor was in the middle of the line, so that
// next thing application prints goes after the input
s.forced_refresh = true;
s.edit_move_buffer_end()?;
s.forced_refresh = false;
s.edit_move_buffer_end(CmdKind::ForcedRefresh)?;

if cfg!(windows) {
let _ = original_mode; // silent warning
Expand Down
Loading

0 comments on commit c34ef39

Please sign in to comment.