diff --git a/Cargo.toml b/Cargo.toml index 62f6d7524..b1c3a2caa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,9 @@ required-features = ["derive"] [[example]] name = "sqlite_history" required-features = ["with-sqlite-history"] +[[example]] +name = "continuation_prompt" +required-features = ["custom-bindings", "derive"] [package.metadata.docs.rs] features = [ diff --git a/examples/continuation_prompt.rs b/examples/continuation_prompt.rs new file mode 100644 index 000000000..e6a14afb6 --- /dev/null +++ b/examples/continuation_prompt.rs @@ -0,0 +1,73 @@ +use std::borrow::Cow; + +use rustyline::highlight::Highlighter; +use rustyline::validate::{ValidationContext, ValidationResult, Validator}; +use rustyline::{Cmd, Editor, EventHandler, Helper, KeyCode, KeyEvent, Modifiers, Result}; +use rustyline::{Completer, Hinter}; + +#[derive(Completer, Hinter)] +struct InputValidator { + bracket_level: i32, + /// re-render only when input just changed + /// not render after cursor moving + need_render: bool, +} + +impl Helper for InputValidator { + fn update_after_edit(&mut self, line: &str, _pos: usize, _forced_refresh: bool) { + self.bracket_level = line.chars().fold(0, |level, c| { + if c == '(' { + level + 1 + } else if c == ')' { + level - 1 + } else { + level + } + }); + self.need_render = true; + } +} + +impl Validator for InputValidator { + fn validate(&mut self, _ctx: &mut ValidationContext) -> Result { + if self.bracket_level > 0 { + Ok(ValidationResult::Incomplete) + // Ok(ValidationResult::Incomplete(2)) + } else if self.bracket_level < 0 { + Ok(ValidationResult::Invalid(Some(format!( + " - excess {} close bracket", + -self.bracket_level + )))) + } else { + Ok(ValidationResult::Valid(None)) + } + } +} + +impl Highlighter for InputValidator { + fn highlight_char(&mut self, _line: &str, _pos: usize, _forced: bool) -> bool { + self.need_render + } + fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> Cow<'l, str> { + self.need_render = false; + Cow::Borrowed(&line) + } +} + +fn main() -> Result<()> { + let h = InputValidator { + bracket_level: 0, + need_render: true, + }; + let mut rl = Editor::new()?; + rl.set_helper(Some(h)); + rl.bind_sequence( + KeyEvent(KeyCode::Char('s'), Modifiers::CTRL), + EventHandler::Simple(Cmd::Newline), + ); + + let input = rl.readline(">> ")?; + println!("Input: {input}"); + + Ok(()) +} diff --git a/examples/custom_key_bindings.rs b/examples/custom_key_bindings.rs index 7d6624af7..c5a45505a 100644 --- a/examples/custom_key_bindings.rs +++ b/examples/custom_key_bindings.rs @@ -14,7 +14,7 @@ struct MyHelper(#[rustyline(Hinter)] HistoryHinter); impl Highlighter for MyHelper { fn highlight_prompt<'b, 's: 'b, 'p: 'b>( - &'s self, + &'s mut self, prompt: &'p str, default: bool, ) -> Cow<'b, str> { @@ -25,7 +25,7 @@ impl Highlighter for MyHelper { } } - fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + fn highlight_hint<'h>(&mut self, hint: &'h str) -> Cow<'h, str> { Owned(format!("\x1b[1m{hint}\x1b[m")) } } diff --git a/examples/diy_hints.rs b/examples/diy_hints.rs index af8fadc80..4158097fd 100644 --- a/examples/diy_hints.rs +++ b/examples/diy_hints.rs @@ -52,7 +52,7 @@ impl CommandHint { impl Hinter for DIYHinter { type Hint = CommandHint; - fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option { + fn hint(&mut self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option { if line.is_empty() || pos < line.len() { return None; } diff --git a/examples/example.rs b/examples/example.rs index f2f5c79af..e257e8fbf 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -22,7 +22,7 @@ struct MyHelper { impl Highlighter for MyHelper { fn highlight_prompt<'b, 's: 'b, 'p: 'b>( - &'s self, + &'s mut self, prompt: &'p str, default: bool, ) -> Cow<'b, str> { @@ -33,25 +33,25 @@ impl Highlighter for MyHelper { } } - fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + fn highlight_hint<'h>(&mut self, hint: &'h str) -> Cow<'h, str> { Owned("\x1b[1m".to_owned() + hint + "\x1b[m") } #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] - fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> Cow<'l, str> { self.highlighter.highlight(line, pos) } #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] fn highlight_line<'l>( - &self, + &mut self, line: &'l str, pos: usize, ) -> impl Iterator { self.highlighter.highlight_line(line, pos) } - fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { + fn highlight_char(&mut self, line: &str, pos: usize, forced: bool) -> bool { self.highlighter.highlight_char(line, pos, forced) } } diff --git a/examples/input_validation.rs b/examples/input_validation.rs index 583d94f62..76b83ead2 100644 --- a/examples/input_validation.rs +++ b/examples/input_validation.rs @@ -6,7 +6,7 @@ use rustyline::{Editor, Result}; struct InputValidator {} impl Validator for InputValidator { - fn validate(&self, ctx: &mut ValidationContext) -> Result { + fn validate(&mut self, ctx: &mut ValidationContext) -> Result { use ValidationResult::{Incomplete, Invalid, Valid}; let input = ctx.input(); let result = if !input.starts_with("SELECT") { diff --git a/examples/read_password.rs b/examples/read_password.rs index 249b7d8ed..91afbc57e 100644 --- a/examples/read_password.rs +++ b/examples/read_password.rs @@ -10,7 +10,7 @@ struct MaskingHighlighter { impl Highlighter for MaskingHighlighter { #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] - fn highlight<'l>(&self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> { + fn highlight<'l>(&mut self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> { use unicode_width::UnicodeWidthStr; if self.masking { std::borrow::Cow::Owned(" ".repeat(line.width())) @@ -21,7 +21,7 @@ impl Highlighter for MaskingHighlighter { #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] fn highlight_line<'l>( - &self, + &mut self, line: &'l str, _pos: usize, ) -> impl Iterator { @@ -37,7 +37,7 @@ impl Highlighter for MaskingHighlighter { } } - fn highlight_char(&self, _line: &str, _pos: usize, _forced: bool) -> bool { + fn highlight_char(&mut self, _line: &str, _pos: usize, _forced: bool) -> bool { self.masking } } diff --git a/rustyline-derive/src/lib.rs b/rustyline-derive/src/lib.rs index 2672136f7..b68063083 100644 --- a/rustyline-derive/src/lib.rs +++ b/rustyline-derive/src/lib.rs @@ -51,16 +51,16 @@ pub fn completer_macro_derive(input: TokenStream) -> TokenStream { type Candidate = <#field_type as ::rustyline::completion::Completer>::Candidate; fn complete( - &self, + &mut self, line: &str, pos: usize, ctx: &::rustyline::Context<'_>, ) -> ::rustyline::Result<(usize, ::std::vec::Vec)> { - ::rustyline::completion::Completer::complete(&self.#field_name_or_index, line, pos, ctx) + ::rustyline::completion::Completer::complete(&mut self.#field_name_or_index, line, pos, ctx) } - fn update(&self, line: &mut ::rustyline::line_buffer::LineBuffer, start: usize, elected: &str, cl: &mut ::rustyline::Changeset) { - ::rustyline::completion::Completer::update(&self.#field_name_or_index, line, start, elected, cl) + fn update(&mut self, line: &mut ::rustyline::line_buffer::LineBuffer, start: usize, elected: &str, cl: &mut ::rustyline::Changeset) { + ::rustyline::completion::Completer::update(&mut self.#field_name_or_index, line, start, elected, cl) } } } @@ -103,41 +103,41 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream { #[automatically_derived] impl #impl_generics ::rustyline::highlight::Highlighter for #name #ty_generics #where_clause { #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] - fn highlight<'l>(&self, line: &'l str, pos: usize) -> ::std::borrow::Cow<'l, str> { - ::rustyline::highlight::Highlighter::highlight(&self.#field_name_or_index, line, pos) + fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> ::std::borrow::Cow<'l, str> { + ::rustyline::highlight::Highlighter::highlight(&mut self.#field_name_or_index, line, pos) } #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] fn highlight_line<'l>( - &self, + &mut self, line: &'l str, pos: usize, ) -> impl Iterator { - ::rustyline::highlight::Highlighter::highlight_line(&self.#field_name_or_index, line, pos) + ::rustyline::highlight::Highlighter::highlight_line(&mut self.#field_name_or_index, line, pos) } fn highlight_prompt<'b, 's: 'b, 'p: 'b>( - &'s self, + &'s mut self, prompt: &'p str, default: bool, ) -> ::std::borrow::Cow<'b, str> { - ::rustyline::highlight::Highlighter::highlight_prompt(&self.#field_name_or_index, prompt, default) + ::rustyline::highlight::Highlighter::highlight_prompt(&mut self.#field_name_or_index, prompt, default) } - fn highlight_hint<'h>(&self, hint: &'h str) -> ::std::borrow::Cow<'h, str> { - ::rustyline::highlight::Highlighter::highlight_hint(&self.#field_name_or_index, hint) + fn highlight_hint<'h>(&mut self, hint: &'h str) -> ::std::borrow::Cow<'h, str> { + ::rustyline::highlight::Highlighter::highlight_hint(&mut self.#field_name_or_index, hint) } fn highlight_candidate<'c>( - &self, + &mut self, candidate: &'c str, completion: ::rustyline::config::CompletionType, ) -> ::std::borrow::Cow<'c, str> { - ::rustyline::highlight::Highlighter::highlight_candidate(&self.#field_name_or_index, candidate, completion) + ::rustyline::highlight::Highlighter::highlight_candidate(&mut 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(&mut self, line: &str, pos: usize, forced: bool) -> bool { + ::rustyline::highlight::Highlighter::highlight_char(&mut self.#field_name_or_index, line, pos, forced) } } } @@ -166,8 +166,8 @@ pub fn hinter_macro_derive(input: TokenStream) -> TokenStream { impl #impl_generics ::rustyline::hint::Hinter for #name #ty_generics #where_clause { type Hint = <#field_type as ::rustyline::hint::Hinter>::Hint; - fn hint(&self, line: &str, pos: usize, ctx: &::rustyline::Context<'_>) -> ::std::option::Option { - ::rustyline::hint::Hinter::hint(&self.#field_name_or_index, line, pos, ctx) + fn hint(&mut self, line: &str, pos: usize, ctx: &::rustyline::Context<'_>) -> ::std::option::Option { + ::rustyline::hint::Hinter::hint(&mut self.#field_name_or_index, line, pos, ctx) } } } @@ -195,14 +195,14 @@ pub fn validator_macro_derive(input: TokenStream) -> TokenStream { #[automatically_derived] impl #impl_generics ::rustyline::validate::Validator for #name #ty_generics #where_clause { fn validate( - &self, + &mut self, ctx: &mut ::rustyline::validate::ValidationContext, ) -> ::rustyline::Result<::rustyline::validate::ValidationResult> { - ::rustyline::validate::Validator::validate(&self.#field_name_or_index, ctx) + ::rustyline::validate::Validator::validate(&mut self.#field_name_or_index, ctx) } - fn validate_while_typing(&self) -> bool { - ::rustyline::validate::Validator::validate_while_typing(&self.#field_name_or_index) + fn validate_while_typing(&mut self) -> bool { + ::rustyline::validate::Validator::validate_while_typing(&mut self.#field_name_or_index) } } } diff --git a/src/completion.rs b/src/completion.rs index 114d1aa9e..24827a5df 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -90,7 +90,7 @@ pub trait Completer { /// /// ("ls /usr/loc", 11) => Ok((3, vec!["/usr/local/"])) fn complete( - &self, // FIXME should be `&mut self` + &mut self, // FIXME should be `&mut self` line: &str, pos: usize, ctx: &Context<'_>, @@ -99,7 +99,7 @@ pub trait Completer { Ok((0, Vec::with_capacity(0))) } /// Updates the edited `line` with the `elected` candidate. - fn update(&self, line: &mut LineBuffer, start: usize, elected: &str, cl: &mut Changeset) { + fn update(&mut self, line: &mut LineBuffer, start: usize, elected: &str, cl: &mut Changeset) { let end = line.pos(); line.replace(start..end, elected, cl); } @@ -108,16 +108,16 @@ pub trait Completer { impl Completer for () { type Candidate = String; - fn update(&self, _line: &mut LineBuffer, _start: usize, _elected: &str, _cl: &mut Changeset) { + fn update(&mut self, _line: &mut LineBuffer, _start: usize, _elected: &str, _cl: &mut Changeset) { unreachable!(); } } -impl<'c, C: ?Sized + Completer> Completer for &'c C { +impl<'c, C: ?Sized + Completer> Completer for &'c mut C { type Candidate = C::Candidate; fn complete( - &self, + &mut self, line: &str, pos: usize, ctx: &Context<'_>, @@ -125,31 +125,31 @@ impl<'c, C: ?Sized + Completer> Completer for &'c C { (**self).complete(line, pos, ctx) } - fn update(&self, line: &mut LineBuffer, start: usize, elected: &str, cl: &mut Changeset) { + fn update(&mut self, line: &mut LineBuffer, start: usize, elected: &str, cl: &mut Changeset) { (**self).update(line, start, elected, cl); } } -macro_rules! box_completer { - ($($id: ident)*) => { - $( - impl Completer for $id { - type Candidate = C::Candidate; - - fn complete(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Result<(usize, Vec)> { - (**self).complete(line, pos, ctx) - } - fn update(&self, line: &mut LineBuffer, start: usize, elected: &str, cl: &mut Changeset) { - (**self).update(line, start, elected, cl) - } - } - )* - } -} +// macro_rules! box_completer { +// ($($id: ident)*) => { +// $( +// impl Completer for $id { +// type Candidate = C::Candidate; + +// fn complete(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Result<(usize, Vec)> { +// (**self).complete(line, pos, ctx) +// } +// fn update(&self, line: &mut LineBuffer, start: usize, elected: &str, cl: &mut Changeset) { +// (**self).update(line, start, elected, cl) +// } +// } +// )* +// } +// } use crate::undo::Changeset; use std::rc::Rc; -use std::sync::Arc; -box_completer! { Box Rc Arc } +// use std::sync::Arc; +// box_completer! { Box Rc Arc } /// A `Completer` for file and folder names. pub struct FilenameCompleter { @@ -257,7 +257,7 @@ impl Default for FilenameCompleter { impl Completer for FilenameCompleter { type Candidate = Pair; - fn complete(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Result<(usize, Vec)> { + fn complete(&mut self, line: &str, pos: usize, _ctx: &Context<'_>) -> Result<(usize, Vec)> { self.complete_path(line, pos) } } diff --git a/src/edit.rs b/src/edit.rs index fa02a4249..0afaf9c2a 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -31,7 +31,7 @@ pub struct State<'out, 'prompt, H: Helper> { saved_line_for_history: LineBuffer, // Current edited line before history browsing byte_buffer: [u8; 4], pub changes: Changeset, // changes to line, for undo/redo - pub helper: Option<&'out H>, + pub helper: &'out mut Option, pub ctx: Context<'out>, // Give access to history for `hinter` pub hint: Option>, // last hint displayed pub highlight_char: bool, // `true` if a char has been highlighted @@ -48,7 +48,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { pub fn new( out: &'out mut ::Writer, prompt: &'prompt str, - helper: Option<&'out H>, + helper: &'out mut Option, ctx: Context<'out>, ) -> Self { let prompt_size = out.calculate_position(prompt, Position::default()); @@ -69,13 +69,13 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { } } - pub fn highlighter(&self) -> Option<&H> { - if self.out.colors_enabled() { - self.helper - } else { - None - } - } + // pub fn highlighter(&self) -> Option<&H> { + // if self.out.colors_enabled() { + // self.helper + // } else { + // None + // } + // } pub fn next_cmd( &mut self, @@ -139,6 +139,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { debug_assert!(self.layout.prompt_size <= self.layout.cursor); debug_assert!(self.layout.cursor <= self.layout.end); } + self.update_after_move_cursor(); Ok(()) } @@ -148,11 +149,14 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { } self.out.move_cursor(self.layout.cursor, self.layout.end)?; self.layout.cursor = self.layout.end; + self.update_after_move_cursor(); Ok(()) } pub fn move_cursor_at_leftmost(&mut self, rdr: &mut ::Reader) -> Result<()> { - self.out.move_cursor_at_leftmost(rdr) + self.out.move_cursor_at_leftmost(rdr)?; + self.update_after_move_cursor(); + Ok(()) } fn refresh( @@ -167,11 +171,6 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { Info::Hint => self.hint.as_ref().map(|h| h.display()), Info::Msg(msg) => msg, }; - let highlighter = if self.out.colors_enabled() { - self.helper - } else { - None - }; let new_layout = self .out @@ -179,14 +178,25 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { debug!(target: "rustyline", "old layout: {:?}", self.layout); debug!(target: "rustyline", "new layout: {:?}", new_layout); - self.out.refresh_line( - prompt, - &self.line, - info, - &self.layout, - &new_layout, - highlighter, - )?; + if self.out.colors_enabled() { + self.out.refresh_line( + prompt, + &self.line, + info, + &self.layout, + &new_layout, + self.helper, + )?; + } else { + self.out.refresh_line::( + prompt, + &self.line, + info, + &self.layout, + &new_layout, + &mut None, + )?; + } self.layout = new_layout; Ok(()) @@ -204,17 +214,33 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { } } + fn update_after_edit(&mut self) { + if let Some(helper) = self.helper.as_mut() { + helper.update_after_edit(&self.line, self.line.pos(), self.forced_refresh); + }; + } + + fn update_after_move_cursor(&mut self) { + if let Some(helper) = self.helper.as_mut() { + helper.update_after_move_cursor(&self.line, self.line.pos()); + }; + } + fn highlight_char(&mut self) -> bool { - if let Some(highlighter) = self.highlighter() { - let highlight_char = - highlighter.highlight_char(&self.line, self.line.pos(), self.forced_refresh); - if highlight_char { - self.highlight_char = true; - true - } else if self.highlight_char { - // previously highlighted => force a full refresh - self.highlight_char = false; - true + if self.out.colors_enabled() { + if let Some(highlighter) = self.helper.as_mut() { + let highlight_char = + highlighter.highlight_char(&self.line, self.line.pos(), self.forced_refresh); + if highlight_char { + self.highlight_char = true; + true + } else if self.highlight_char { + // previously highlighted => force a full refresh + self.highlight_char = false; + true + } else { + false + } } else { false } @@ -230,7 +256,8 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { pub fn validate(&mut self) -> Result { if let Some(validator) = self.helper { self.changes.begin(); - let result = validator.validate(&mut ValidationContext::new(self))?; + let result = + validator.validate(&mut ValidationContext::new(&mut self.line.as_str()))?; let corrected = self.changes.end(); match result { ValidationResult::Incomplete => {} @@ -264,6 +291,7 @@ impl<'out, 'prompt, H: Helper> Invoke for State<'out, 'prompt, H> { impl<'out, 'prompt, H: Helper> Refresher for State<'out, 'prompt, H> { fn refresh_line(&mut self) -> Result<()> { let prompt_size = self.prompt_size; + self.update_after_edit(); self.hint(); self.highlight_char(); self.refresh(self.prompt, prompt_size, true, Info::Hint) @@ -271,6 +299,7 @@ impl<'out, 'prompt, H: Helper> Refresher for State<'out, 'prompt, H> { fn refresh_line_with_msg(&mut self, msg: Option<&str>) -> Result<()> { let prompt_size = self.prompt_size; + self.update_after_edit(); self.hint = None; self.highlight_char(); self.refresh(self.prompt, prompt_size, true, Info::Msg(msg)) @@ -278,6 +307,7 @@ impl<'out, 'prompt, H: Helper> Refresher for State<'out, 'prompt, H> { fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> { let prompt_size = self.out.calculate_position(prompt, Position::default()); + self.update_after_edit(); self.hint(); self.highlight_char(); self.refresh(prompt, prompt_size, false, Info::Hint) @@ -351,6 +381,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { /// Insert the character `ch` at cursor current position. pub fn edit_insert(&mut self, ch: char, n: RepeatCount) -> Result<()> { if let Some(push) = self.line.insert(ch, n, &mut self.changes) { + self.update_after_edit(); if push { let prompt_size = self.prompt_size; let no_previous_hint = self.hint.is_none(); @@ -748,7 +779,7 @@ pub fn init_state<'out, H: Helper>( out: &'out mut ::Writer, line: &str, pos: usize, - helper: Option<&'out H>, + helper: &'out mut Option, history: &'out crate::history::DefaultHistory, ) -> State<'out, 'static, H> { State { @@ -781,8 +812,8 @@ mod test { history.add("line0").unwrap(); history.add("line1").unwrap(); let line = "current edited line"; - let helper: Option<()> = None; - let mut s = init_state(&mut out, line, 6, helper.as_ref(), &history); + let mut helper: Option<()> = None; + let mut s = init_state(&mut out, line, 6, &mut helper, &history); s.ctx.history_index = history.len(); for _ in 0..2 { diff --git a/src/highlight.rs b/src/highlight.rs index 56fb11443..8416ceede 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -127,7 +127,7 @@ pub trait Highlighter { docsrs, doc(cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))) )] - fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> Cow<'l, str> { let _ = pos; Borrowed(line) } @@ -140,7 +140,7 @@ pub trait Highlighter { doc(cfg(all(feature = "split-highlight", not(feature = "ansi-str")))) )] fn highlight_line<'l>( - &self, + &mut self, line: &'l str, pos: usize, ) -> impl Iterator { @@ -151,7 +151,7 @@ pub trait Highlighter { /// Takes the `prompt` and /// returns the highlighted version (with ANSI color). fn highlight_prompt<'b, 's: 'b, 'p: 'b>( - &'s self, + &'s mut self, prompt: &'p str, default: bool, ) -> Cow<'b, str> { @@ -160,7 +160,7 @@ pub trait Highlighter { } /// Takes the `hint` and /// returns the highlighted version (with ANSI color). - fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + fn highlight_hint<'h>(&mut self, hint: &'h str) -> Cow<'h, str> { Borrowed(hint) } /// Takes the completion `candidate` and @@ -168,7 +168,7 @@ pub trait Highlighter { /// /// Currently, used only with `CompletionType::List`. fn highlight_candidate<'c>( - &self, + &mut self, candidate: &'c str, // FIXME should be Completer::Candidate completion: CompletionType, ) -> Cow<'c, str> { @@ -182,7 +182,7 @@ pub trait Highlighter { /// /// 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 { + fn highlight_char(&mut self, line: &str, pos: usize, forced: bool) -> bool { let _ = (line, pos, forced); false } @@ -190,15 +190,15 @@ pub trait Highlighter { impl Highlighter for () {} -impl<'r, H: Highlighter> Highlighter for &'r H { +impl<'r, H: Highlighter> Highlighter for &'r mut H { #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] - fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> Cow<'l, str> { (**self).highlight(line, pos) } #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] fn highlight_line<'l>( - &self, + &mut self, line: &'l str, pos: usize, ) -> impl Iterator { @@ -206,26 +206,26 @@ impl<'r, H: Highlighter> Highlighter for &'r H { } fn highlight_prompt<'b, 's: 'b, 'p: 'b>( - &'s self, + &'s mut self, prompt: &'p str, default: bool, ) -> Cow<'b, str> { (**self).highlight_prompt(prompt, default) } - fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + fn highlight_hint<'h>(&mut self, hint: &'h str) -> Cow<'h, str> { (**self).highlight_hint(hint) } fn highlight_candidate<'c>( - &self, + &mut self, candidate: &'c str, completion: CompletionType, ) -> Cow<'c, str> { (**self).highlight_candidate(candidate, completion) } - fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { + fn highlight_char(&mut self, line: &str, pos: usize, forced: bool) -> bool { (**self).highlight_char(line, pos, forced) } } @@ -261,7 +261,7 @@ impl MatchingBracketHighlighter { ))] impl Highlighter for MatchingBracketHighlighter { #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] - fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { + fn highlight<'l>(&mut self, line: &'l str, _pos: usize) -> Cow<'l, str> { if line.len() <= 1 { return Borrowed(line); } @@ -278,7 +278,7 @@ impl Highlighter for MatchingBracketHighlighter { #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] fn highlight_line<'l>( - &self, + &mut self, line: &'l str, _pos: usize, ) -> impl Iterator { @@ -298,7 +298,7 @@ impl Highlighter for MatchingBracketHighlighter { vec![(AnsiStyle::default(), line)].into_iter() } - fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { + fn highlight_char(&mut self, line: &str, pos: usize, forced: bool) -> bool { if forced { self.bracket.set(None); return false; diff --git a/src/hint.rs b/src/hint.rs index efff9357c..71ee0595d 100644 --- a/src/hint.rs +++ b/src/hint.rs @@ -30,7 +30,7 @@ pub trait Hinter { /// returns the string that should be displayed or `None` /// if no hint is available for the text the user currently typed. // TODO Validate: called while editing line but not while moving cursor. - fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { + fn hint(&mut self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { let _ = (line, pos, ctx); None } @@ -40,10 +40,10 @@ impl Hinter for () { type Hint = String; } -impl<'r, H: ?Sized + Hinter> Hinter for &'r H { +impl<'r, H: ?Sized + Hinter> Hinter for &'r mut H { type Hint = H::Hint; - fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { + fn hint(&mut self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { (**self).hint(line, pos, ctx) } } @@ -63,7 +63,7 @@ impl HistoryHinter { impl Hinter for HistoryHinter { type Hint = String; - fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { + fn hint(&mut self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { if line.is_empty() || pos < line.len() { return None; } @@ -96,7 +96,7 @@ mod test { pub fn empty_history() { let history = DefaultHistory::new(); let ctx = Context::new(&history); - let hinter = HistoryHinter {}; + let mut hinter = HistoryHinter {}; let hint = hinter.hint("test", 4, &ctx); assert_eq!(None, hint); } diff --git a/src/lib.rs b/src/lib.rs index b9e2f0fb2..753f768a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,9 +85,8 @@ fn complete_line( unbounded, Skim, SkimItem, SkimItemReceiver, SkimItemSender, SkimOptionsBuilder, }; - let completer = s.helper.unwrap(); // get a list of completions - let (start, candidates) = completer.complete(&s.line, s.line.pos(), &s.ctx)?; + let (start, candidates) = s.helper.as_mut().unwrap().complete(&s.line, s.line.pos(), &s.ctx)?; // if no completions, we are done if candidates.is_empty() { s.out.beep()?; @@ -109,7 +108,7 @@ fn complete_line( } else { Borrowed(candidate) };*/ - completer.update(&mut s.line, start, candidate, &mut s.changes); + s.helper.as_mut().unwrap().update(&mut s.line, start, candidate, &mut s.changes); } else { // Restore current edited line s.line.update(&backup, backup_pos, &mut s.changes); @@ -152,7 +151,7 @@ fn complete_line( if let Some(lcp) = longest_common_prefix(&candidates) { // if we can extend the item, extend it if lcp.len() > s.line.pos() - start || candidates.len() == 1 { - completer.update(&mut s.line, start, lcp, &mut s.changes); + s.helper.as_mut().unwrap().update(&mut s.line, start, lcp, &mut s.changes); s.refresh_line()?; } } @@ -247,7 +246,7 @@ fn complete_line( .downcast_ref::() // downcast to concrete type .expect("something wrong with downcast"); if let Some(candidate) = candidates.get(item.index) { - completer.update( + s.helper.as_mut().unwrap().update( &mut s.line, start, candidate.replacement(), @@ -337,8 +336,12 @@ fn page_completions( if i < candidates.len() { let candidate = &candidates[i].display(); let width = candidate.width(); - if let Some(highlighter) = s.highlighter() { - ab.push_str(&highlighter.highlight_candidate(candidate, CompletionType::List)); + if s.out.colors_enabled() { + if let Some(highlighter) = s.helper.as_mut() { + ab.push_str(&highlighter.highlight_candidate(candidate, CompletionType::List)); + } else { + ab.push_str(candidate); + } } else { ab.push_str(candidate); } @@ -482,7 +485,7 @@ fn apply_backspace_direct(input: &str) -> String { fn readline_direct( mut reader: impl BufRead, mut writer: impl Write, - validator: &Option, + validator: &mut Option, ) -> Result { let mut input = String::new(); @@ -506,7 +509,7 @@ fn readline_direct( input = apply_backspace_direct(&input); - match validator.as_ref() { + match validator.as_mut() { None => return Ok(input), Some(v) => { let mut ctx = input.as_str(); @@ -546,11 +549,39 @@ pub trait Helper where Self: Completer + Hinter + Highlighter + Validator, { + /// Update helper when line has been modified. + /// + /// This is the first-called function just after the editing is done, + /// before all other functions within [Completer], [Hinter], [Highlighter], and [Validator]. + /// + /// You can put the tokenizer/parser here so that other APIs can directly use + /// results generate here, and reduce the overhead. + fn update_after_edit(&mut self, line: &str, pos: usize, forced_refresh: bool) { + _ = (line, forced_refresh, pos); + } + + /// Update helper when cursor has been moved. + /// + /// This is the first-called function just after the cursor moving is done, + /// before all other functions within [Completer], [Hinter], [Highlighter], and [Validator]. + /// + /// You can put the tokenizer/parser here so that other APIs can directly use + /// results generate here, and reduce the overhead. + fn update_after_move_cursor(&mut self, line: &str, pos: usize) { + _ = (line, pos); + } } impl Helper for () {} -impl<'h, H: Helper> Helper for &'h H {} +impl<'h, H: Helper> Helper for &'h mut H { + fn update_after_edit(&mut self, line: &str, pos: usize, forced_refresh: bool) { + (**self).update_after_edit(line, pos, forced_refresh) + } + fn update_after_move_cursor(&mut self, line: &str, pos: usize) { + (**self).update_after_move_cursor(line, pos) + } +} /// Completion/suggestion context pub struct Context<'h> { @@ -660,7 +691,7 @@ impl Editor { stdout.write_all(prompt.as_bytes())?; stdout.flush()?; - readline_direct(io::stdin().lock(), io::stderr(), &self.helper) + readline_direct(io::stdin().lock(), io::stderr(), &mut self.helper) } else if self.term.is_input_tty() { let (original_mode, term_key_map) = self.term.enable_raw_mode()?; let guard = Guard(&original_mode); @@ -676,7 +707,7 @@ impl Editor { } else { debug!(target: "rustyline", "stdin is not a tty"); // Not a tty: read from file / pipe. - readline_direct(io::stdin().lock(), io::stderr(), &self.helper) + readline_direct(io::stdin().lock(), io::stderr(), &mut self.helper) } } @@ -694,7 +725,7 @@ impl Editor { self.kill_ring.reset(); // TODO recreate a new kill ring vs reset let ctx = Context::new(&self.history); - let mut s = State::new(&mut stdout, prompt, self.helper.as_ref(), ctx); + let mut s = State::new(&mut stdout, prompt, &mut self.helper, ctx); let mut input_state = InputState::new(&self.config, &self.custom_bindings); diff --git a/src/test/mod.rs b/src/test/mod.rs index 14ff9052b..12e127564 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -30,7 +30,7 @@ impl Completer for SimpleCompleter { type Candidate = String; fn complete( - &self, + &mut self, line: &str, _pos: usize, _ctx: &Context<'_>, @@ -50,7 +50,7 @@ impl Completer for SimpleCompleter { impl Hinter for SimpleCompleter { type Hint = String; - fn hint(&self, _line: &str, _pos: usize, _ctx: &Context<'_>) -> Option { + fn hint(&mut self, _line: &str, _pos: usize, _ctx: &Context<'_>) -> Option { None } } @@ -63,8 +63,8 @@ impl Validator for SimpleCompleter {} fn complete_line() { let mut out = Sink::default(); let history = crate::history::DefaultHistory::new(); - let helper = Some(SimpleCompleter); - let mut s = init_state(&mut out, "rus", 3, helper.as_ref(), &history); + let mut helper = Some(SimpleCompleter); + let mut s = init_state(&mut out, "rus", 3, &mut helper, &history); let config = Config::default(); let bindings = Bindings::new(); let mut input_state = InputState::new(&config, &bindings); @@ -85,8 +85,8 @@ fn complete_line() { fn complete_symbol() { let mut out = Sink::default(); let history = crate::history::DefaultHistory::new(); - let helper = Some(SimpleCompleter); - let mut s = init_state(&mut out, "\\hbar", 5, helper.as_ref(), &history); + let mut helper = Some(SimpleCompleter); + let mut s = init_state(&mut out, "\\hbar", 5, &mut helper, &history); let config = Config::builder() .completion_type(CompletionType::List) .build(); @@ -188,7 +188,7 @@ fn test_readline_direct() { let output = readline_direct( Cursor::new("([)\n\u{0008}\n\n\r\n])".as_bytes()), Cursor::new(&mut write_buf), - &Some(crate::validate::MatchingBracketValidator::new()), + &mut Some(crate::validate::MatchingBracketValidator::new()), ); assert_eq!( diff --git a/src/tty/mod.rs b/src/tty/mod.rs index f2abf0df5..10a4938d7 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -54,7 +54,7 @@ pub trait Renderer { hint: Option<&str>, old_layout: &Layout, new_layout: &Layout, - highlighter: Option<&H>, + highlighter: &mut Option, ) -> Result<()>; /// Compute layout for rendering prompt + line + some info (either hint, @@ -133,7 +133,7 @@ impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R { hint: Option<&str>, old_layout: &Layout, new_layout: &Layout, - highlighter: Option<&H>, + highlighter: &mut Option, ) -> Result<()> { (**self).refresh_line(prompt, line, hint, old_layout, new_layout, highlighter) } diff --git a/src/tty/test.rs b/src/tty/test.rs index 45828e61f..75c026b86 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -107,7 +107,7 @@ impl Renderer for Sink { _hint: Option<&str>, _old_layout: &Layout, _new_layout: &Layout, - _highlighter: Option<&H>, + _highlighter: &mut Option, ) -> Result<()> { Ok(()) } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index edaea5470..aef3c5bb1 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -986,7 +986,7 @@ impl Renderer for PosixRenderer { hint: Option<&str>, old_layout: &Layout, new_layout: &Layout, - highlighter: Option<&H>, + highlighter: &mut Option, ) -> Result<()> { use std::fmt::Write; self.buffer.clear(); @@ -1710,7 +1710,7 @@ 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) + out.refresh_line::<()>(prompt, &line, None, &old_layout, &new_layout, &mut None) .unwrap(); #[rustfmt::skip] assert_eq!( diff --git a/src/validate.rs b/src/validate.rs index 12b7f3314..b25e16d97 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -67,7 +67,7 @@ pub trait Validator { /// /// For auto-correction like a missing closing quote or to reject invalid /// char while typing, the input will be mutable (TODO). - fn validate(&self, ctx: &mut ValidationContext) -> Result { + fn validate(&mut self, ctx: &mut ValidationContext) -> Result { let _ = ctx; Ok(ValidationResult::Valid(None)) } @@ -79,19 +79,19 @@ pub trait Validator { /// /// This feature is not yet implemented, so this function is currently a /// no-op - fn validate_while_typing(&self) -> bool { + fn validate_while_typing(&mut self) -> bool { false } } impl Validator for () {} -impl<'v, V: ?Sized + Validator> Validator for &'v V { - fn validate(&self, ctx: &mut ValidationContext) -> Result { +impl<'v, V: ?Sized + Validator> Validator for &'v mut V { + fn validate(&mut self, ctx: &mut ValidationContext) -> Result { (**self).validate(ctx) } - fn validate_while_typing(&self) -> bool { + fn validate_while_typing(&mut self) -> bool { (**self).validate_while_typing() } } @@ -111,7 +111,7 @@ impl MatchingBracketValidator { } impl Validator for MatchingBracketValidator { - fn validate(&self, ctx: &mut ValidationContext) -> Result { + fn validate(&mut self, ctx: &mut ValidationContext) -> Result { Ok(validate_brackets(ctx.input())) } }