From f53ba9e5554a4879a840835d42262d3c86d18715 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Wed, 25 Sep 2024 16:21:08 +0800 Subject: [PATCH 01/13] new `Helper` to reduce parser overhead and support continuation prompt --- Cargo.toml | 4 + examples/continuation_prompt.rs | 76 +++++++++++++++++ examples/custom_key_bindings.rs | 7 +- examples/diy_hints.rs | 5 +- examples/example.rs | 15 ++-- examples/external_print.rs | 2 +- examples/input_multiline.rs | 3 +- examples/input_validation.rs | 7 +- examples/minimal.rs | 2 +- examples/numeric_input.rs | 2 +- examples/read_password.rs | 9 +- examples/sqlite_history.rs | 2 +- rustyline-derive/src/lib.rs | 44 +++++----- src/command.rs | 4 + src/completion.rs | 61 ++++++++------ src/edit.rs | 136 ++++++++++++++++-------------- src/highlight.rs | 32 ++++---- src/hint.rs | 10 +-- src/lib.rs | 141 +++++++++++++++++++------------- src/test/mod.rs | 16 ++-- src/tty/mod.rs | 4 +- src/tty/test.rs | 2 +- src/tty/unix.rs | 97 +++++++++++++--------- src/validate.rs | 26 ++++-- 24 files changed, 434 insertions(+), 273 deletions(-) create mode 100644 examples/continuation_prompt.rs diff --git a/Cargo.toml b/Cargo.toml index 62f6d7524..b04783b98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ with-fuzzy = ["skim"] case_insensitive_history_search = ["regex"] # For continuation prompt, indentation, scrolling split-highlight = [] +continuation-prompt = [] [[example]] name = "custom_key_bindings" @@ -97,6 +98,9 @@ required-features = ["derive"] [[example]] name = "sqlite_history" required-features = ["with-sqlite-history"] +[[example]] +name = "continuation_prompt" +required-features = ["custom-bindings", "derive", "continuation-prompt", "split-highlight"] [package.metadata.docs.rs] features = [ diff --git a/examples/continuation_prompt.rs b/examples/continuation_prompt.rs new file mode 100644 index 000000000..83390de16 --- /dev/null +++ b/examples/continuation_prompt.rs @@ -0,0 +1,76 @@ +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(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_line<'l>( + &mut self, + line: &'l str, + _pos: usize, + ) -> impl Iterator { + use core::iter::once; + let mut lines = line.split('\n'); + self.need_render = false; + once(((), lines.next().unwrap())) + .chain(lines.flat_map(|line| once(((), "\n.. ")).chain(once(((), line))))) + } +} + +fn main() -> Result<()> { + let h = InputValidator { + bracket_level: 0, + need_render: true, + }; + let mut rl = Editor::new(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..5159acd01 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")) } } @@ -85,8 +85,7 @@ impl ConditionalEventHandler for TabEventHandler { } fn main() -> Result<()> { - let mut rl = Editor::::new()?; - rl.set_helper(Some(MyHelper(HistoryHinter::new()))); + let mut rl = Editor::::new(MyHelper(HistoryHinter::new()))?; let ceh = Box::new(CompleteHintHandler); rl.bind_sequence(KeyEvent::ctrl('E'), EventHandler::Conditional(ceh.clone())); diff --git a/examples/diy_hints.rs b/examples/diy_hints.rs index af8fadc80..7bc67caa0 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; } @@ -86,8 +86,7 @@ fn main() -> Result<()> { println!("This is a DIY hint hack of rustyline"); let h = DIYHinter { hints: diy_hints() }; - let mut rl: Editor = Editor::new()?; - rl.set_helper(Some(h)); + let mut rl: Editor = Editor::new(h)?; loop { let input = rl.readline("> ")?; diff --git a/examples/example.rs b/examples/example.rs index f2f5c79af..efc915e96 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) } } @@ -72,8 +72,7 @@ fn main() -> rustyline::Result<()> { colored_prompt: "".to_owned(), validator: MatchingBracketValidator::new(), }; - let mut rl = Editor::with_config(config)?; - rl.set_helper(Some(h)); + let mut rl = Editor::with_config(config, h)?; rl.bind_sequence(KeyEvent::alt('n'), Cmd::HistorySearchForward); rl.bind_sequence(KeyEvent::alt('p'), Cmd::HistorySearchBackward); if rl.load_history("history.txt").is_err() { @@ -82,7 +81,7 @@ fn main() -> rustyline::Result<()> { let mut count = 1; loop { let p = format!("{count}> "); - rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{p}\x1b[0m"); + rl.helper_mut().colored_prompt = format!("\x1b[1;32m{p}\x1b[0m"); let readline = rl.readline(&p); match readline { Ok(line) => { diff --git a/examples/external_print.rs b/examples/external_print.rs index f93769e95..b85dce890 100644 --- a/examples/external_print.rs +++ b/examples/external_print.rs @@ -6,7 +6,7 @@ use rand::{thread_rng, Rng}; use rustyline::{DefaultEditor, ExternalPrinter, Result}; fn main() -> Result<()> { - let mut rl = DefaultEditor::new()?; + let mut rl = DefaultEditor::new(())?; let mut printer = rl.create_external_printer()?; thread::spawn(move || { let mut rng = thread_rng(); diff --git a/examples/input_multiline.rs b/examples/input_multiline.rs index ead5e8825..29c01fe47 100644 --- a/examples/input_multiline.rs +++ b/examples/input_multiline.rs @@ -16,8 +16,7 @@ fn main() -> Result<()> { brackets: MatchingBracketValidator::new(), highlighter: MatchingBracketHighlighter::new(), }; - let mut rl = Editor::new()?; - rl.set_helper(Some(h)); + let mut rl = Editor::new(h)?; rl.bind_sequence( KeyEvent(KeyCode::Char('s'), Modifiers::CTRL), EventHandler::Simple(Cmd::Newline), diff --git a/examples/input_validation.rs b/examples/input_validation.rs index 583d94f62..9c5f17f0c 100644 --- a/examples/input_validation.rs +++ b/examples/input_validation.rs @@ -6,13 +6,13 @@ 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") { Invalid(Some(" --< Expect: SELECT stmt".to_owned())) } else if !input.ends_with(';') { - Incomplete + Incomplete(0) } else { Valid(None) }; @@ -22,8 +22,7 @@ impl Validator for InputValidator { fn main() -> Result<()> { let h = InputValidator {}; - let mut rl = Editor::new()?; - rl.set_helper(Some(h)); + let mut rl = Editor::new(h)?; let input = rl.readline("> ")?; println!("Input: {input}"); diff --git a/examples/minimal.rs b/examples/minimal.rs index 9fc627ee4..d1d562b19 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -3,7 +3,7 @@ use rustyline::{DefaultEditor, Result}; /// Minimal REPL fn main() -> Result<()> { env_logger::init(); - let mut rl = DefaultEditor::new()?; + let mut rl = DefaultEditor::new(())?; loop { let line = rl.readline("> ")?; // read println!("Line: {line}"); // eval / print diff --git a/examples/numeric_input.rs b/examples/numeric_input.rs index 49eb6f94b..1060af660 100644 --- a/examples/numeric_input.rs +++ b/examples/numeric_input.rs @@ -19,7 +19,7 @@ impl ConditionalEventHandler for FilteringEventHandler { } fn main() -> Result<()> { - let mut rl = DefaultEditor::new()?; + let mut rl = DefaultEditor::new(())?; rl.bind_sequence( Event::Any, diff --git a/examples/read_password.rs b/examples/read_password.rs index 249b7d8ed..b92a2715d 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())) @@ -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 } } @@ -45,13 +45,12 @@ impl Highlighter for MaskingHighlighter { fn main() -> Result<()> { println!("This is just a hack. Reading passwords securely requires more than that."); let h = MaskingHighlighter { masking: false }; - let mut rl = Editor::new()?; - rl.set_helper(Some(h)); + let mut rl = Editor::new(h)?; let username = rl.readline("Username:")?; println!("Username: {username}"); - rl.helper_mut().expect("No helper").masking = true; + rl.helper_mut().masking = true; rl.set_color_mode(ColorMode::Forced); // force masking rl.set_auto_add_history(false); // make sure password is not added to history let mut guard = rl.set_cursor_visibility(false)?; diff --git a/examples/sqlite_history.rs b/examples/sqlite_history.rs index a1efac840..04f9c3910 100644 --- a/examples/sqlite_history.rs +++ b/examples/sqlite_history.rs @@ -9,7 +9,7 @@ fn main() -> Result<()> { // file rustyline::sqlite_history::SQLiteHistory::open(config, "history.sqlite3")? }; - let mut rl: Editor<(), _> = Editor::with_history(config, history)?; + let mut rl: Editor<(), _> = Editor::with_history(config, history, ())?; loop { let line = rl.readline("> ")?; println!("{line}"); 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/command.rs b/src/command.rs index f0185e2cb..1bad231f9 100644 --- a/src/command.rs +++ b/src/command.rs @@ -133,6 +133,7 @@ pub fn execute( Cmd::AcceptLine | Cmd::AcceptOrInsertLine { .. } => { let validation_result = s.validate()?; let valid = validation_result.is_valid(); + let incomplete = validation_result.is_incomplete(); let end = s.line.is_end_of_input(); match (cmd, valid, end) { (Cmd::AcceptLine, ..) @@ -151,6 +152,9 @@ pub fn execute( if valid || !validation_result.has_message() { s.edit_insert('\n', 1)?; } + if let Some(indent) = incomplete { + s.edit_insert(' ', indent)?; + } } _ => unreachable!(), } diff --git a/src/completion.rs b/src/completion.rs index 114d1aa9e..ca8620281 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,22 @@ 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 +131,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(&mut self, line: &str, pos: usize, ctx: &Context<'_>) -> Result<(usize, Vec)> { +// (**self).complete(line, pos, ctx) +// } +// fn update(&mut 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 +263,12 @@ 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..f51309864 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 H, 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 H, 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(&mut self) -> Option<&mut H> { + // if self.out.colors_enabled() { + // Some(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,13 +178,14 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { debug!(target: "rustyline", "old layout: {:?}", self.layout); debug!(target: "rustyline", "new layout: {:?}", new_layout); + // if self.out.colors_enabled() { self.out.refresh_line( prompt, &self.line, info, &self.layout, &new_layout, - highlighter, + self.helper, )?; self.layout = new_layout; @@ -193,34 +193,43 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { } pub fn hint(&mut self) { - if let Some(hinter) = self.helper { - let hint = hinter.hint(self.line.as_str(), self.line.pos(), &self.ctx); - self.hint = match hint { - Some(val) if !val.display().is_empty() => Some(Box::new(val) as Box), - _ => None, - }; - } else { - self.hint = None; - } + let hint = self + .helper + .hint(self.line.as_str(), self.line.pos(), &self.ctx); + self.hint = match hint { + Some(val) if !val.display().is_empty() => Some(Box::new(val) as Box), + _ => None, + }; + } + + fn update_after_edit(&mut self) { + self.helper + .update_after_edit(&self.line, self.line.pos(), self.forced_refresh); + } + + fn update_after_move_cursor(&mut self) { + self.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 - } else { - false - } + // if let Some(highlighter) = self.highlighter() { + let highlight_char = + self.helper + .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 + // } } pub fn is_default_prompt(&self) -> bool { @@ -228,30 +237,31 @@ 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 corrected = self.changes.end(); - match result { - ValidationResult::Incomplete => {} - ValidationResult::Valid(ref msg) => { - // Accept the line regardless of where the cursor is. - 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.changes.begin(); + let mut invoke: &str = &self.line; + let result = self + .helper + .validate(&mut ValidationContext::new(&mut invoke))?; + let corrected = self.changes.end(); + match result { + ValidationResult::Incomplete(_) => { + self.refresh_line()?; + } + ValidationResult::Valid(ref msg) => { + // Accept the line regardless of where the cursor is. + 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())?; } - ValidationResult::Invalid(ref msg) => { - if corrected || self.has_hint() || msg.is_some() { - self.refresh_line_with_msg(msg.as_deref())?; - } + } + ValidationResult::Invalid(ref msg) => { + if corrected || self.has_hint() || msg.is_some() { + self.refresh_line_with_msg(msg.as_deref())?; } } - Ok(result) - } else { - Ok(ValidationResult::Valid(None)) } + Ok(result) } } @@ -264,6 +274,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) @@ -272,12 +283,14 @@ 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.hint = None; + self.update_after_edit(); self.highlight_char(); 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.update_after_edit(); self.hint(); self.highlight_char(); self.refresh(prompt, prompt_size, false, Info::Hint) @@ -351,6 +364,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 +762,7 @@ pub fn init_state<'out, H: Helper>( out: &'out mut ::Writer, line: &str, pos: usize, - helper: Option<&'out H>, + helper: &'out mut H, history: &'out crate::history::DefaultHistory, ) -> State<'out, 'static, H> { State { @@ -781,8 +795,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 binding = (); + let mut s = init_state(&mut out, line, 6, &mut binding, &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..2fac786e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,9 +85,9 @@ fn complete_line( unbounded, Skim, SkimItem, SkimItemReceiver, SkimItemSender, SkimOptionsBuilder, }; - let completer = s.helper.unwrap(); + // let completer = &s.helper; // get a list of completions - let (start, candidates) = completer.complete(&s.line, s.line.pos(), &s.ctx)?; + let (start, candidates) = s.helper.complete(&s.line, s.line.pos(), &s.ctx)?; // if no completions, we are done if candidates.is_empty() { s.out.beep()?; @@ -109,7 +109,8 @@ fn complete_line( } else { Borrowed(candidate) };*/ - completer.update(&mut s.line, start, candidate, &mut s.changes); + s.helper + .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 +153,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.update(&mut s.line, start, lcp, &mut s.changes); s.refresh_line()?; } } @@ -247,7 +248,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.update( &mut s.line, start, candidate.replacement(), @@ -337,11 +338,15 @@ 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)); - } else { - ab.push_str(candidate); - } + ab.push_str( + &s.helper + .highlight_candidate(candidate, CompletionType::List), + ); + // if let Some(highlighter) = s.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(' '); @@ -482,7 +487,7 @@ fn apply_backspace_direct(input: &str) -> String { fn readline_direct( mut reader: impl BufRead, mut writer: impl Write, - validator: &Option, + validator: &mut impl Validator, ) -> Result { let mut input = String::new(); @@ -506,35 +511,36 @@ fn readline_direct( input = apply_backspace_direct(&input); - match validator.as_ref() { - None => return Ok(input), - Some(v) => { - let mut ctx = input.as_str(); - let mut ctx = validate::ValidationContext::new(&mut ctx); - - match v.validate(&mut ctx)? { - validate::ValidationResult::Valid(msg) => { - if let Some(msg) = msg { - writer.write_all(msg.as_bytes())?; - } - return Ok(input); - } - validate::ValidationResult::Invalid(Some(msg)) => { - writer.write_all(msg.as_bytes())?; - } - validate::ValidationResult::Incomplete => { - // Add newline and keep on taking input - if trailing_r { - input.push('\r'); - } - if trailing_n { - input.push('\n'); - } - } - _ => {} + // match validator.as_ref() { + // None => return Ok(input), + // Some(v) => { + let mut ctx = input.as_str(); + let mut ctx = validate::ValidationContext::new(&mut ctx); + + match validator.validate(&mut ctx)? { + validate::ValidationResult::Valid(msg) => { + if let Some(msg) = msg { + writer.write_all(msg.as_bytes())?; + } + return Ok(input); + } + validate::ValidationResult::Invalid(Some(msg)) => { + writer.write_all(msg.as_bytes())?; + } + validate::ValidationResult::Incomplete(indent) => { + // Add newline and keep on taking input + if trailing_r { + input.push('\r'); } + if trailing_n { + input.push('\n'); + } + input += &" ".repeat(indent); } + _ => {} } + // } + // } } } @@ -546,11 +552,32 @@ 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 {} /// Completion/suggestion context pub struct Context<'h> { @@ -587,7 +614,7 @@ pub struct Editor { term: Terminal, buffer: Option, history: I, - helper: Option, + helper: H, kill_ring: KillRing, config: Config, custom_bindings: Bindings, @@ -599,19 +626,19 @@ pub type DefaultEditor = Editor<(), DefaultHistory>; #[allow(clippy::new_without_default)] impl Editor { /// Create an editor with the default configuration - pub fn new() -> Result { - Self::with_config(Config::default()) + pub fn new(helper: H) -> Result { + Self::with_config(Config::default(), helper) } /// Create an editor with a specific configuration. - pub fn with_config(config: Config) -> Result { - Self::with_history(config, DefaultHistory::with_config(config)) + pub fn with_config(config: Config, helper: H) -> Result { + Self::with_history(config, DefaultHistory::with_config(config), helper) } } impl Editor { /// Create an editor with a custom history impl. - pub fn with_history(config: Config, history: I) -> Result { + pub fn with_history(config: Config, history: I, helper: H) -> Result { let term = Terminal::new( config.color_mode(), config.behavior(), @@ -624,7 +651,7 @@ impl Editor { term, buffer: None, history, - helper: None, + helper, kill_ring: KillRing::new(60), config, custom_bindings: Bindings::new(), @@ -660,11 +687,12 @@ 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); - let user_input = self.readline_edit(prompt, initial, &original_mode, term_key_map); + let user_input: result::Result = + self.readline_edit(prompt, initial, &original_mode, term_key_map); if self.config.auto_add_history() { if let Ok(ref line) = user_input { self.add_history_entry(line.as_str())?; @@ -676,7 +704,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 +722,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); @@ -729,7 +757,7 @@ impl Editor { // First trigger commands that need extra input - if cmd == Cmd::Complete && s.helper.is_some() { + if cmd == Cmd::Complete { let next = complete_line(&mut rdr, &mut s, &mut input_state, &self.config)?; if let Some(next) = next { cmd = next; @@ -839,18 +867,17 @@ impl Editor { /// Register a callback function to be called for tab-completion /// or to show hints to the user at the right of the prompt. - pub fn set_helper(&mut self, helper: Option) { - self.helper = helper; - } + #[deprecated = "reason"] + pub fn set_helper(&mut self) {} /// Return a mutable reference to the helper. - pub fn helper_mut(&mut self) -> Option<&mut H> { - self.helper.as_mut() + pub fn helper_mut(&mut self) -> &mut H { + &mut self.helper } /// Return an immutable reference to the helper. - pub fn helper(&self) -> Option<&H> { - self.helper.as_ref() + pub fn helper(&self) -> &H { + &self.helper } /// Bind a sequence to a command. diff --git a/src/test/mod.rs b/src/test/mod.rs index 14ff9052b..0c030a694 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -20,7 +20,7 @@ mod vi_insert; fn init_editor(mode: EditMode, keys: &[KeyEvent]) -> DefaultEditor { let config = Config::builder().edit_mode(mode).build(); - let mut editor = DefaultEditor::with_config(config).unwrap(); + let mut editor = DefaultEditor::with_config(config, ()).unwrap(); editor.term.keys.extend(keys.iter().copied()); editor } @@ -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 = 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 = 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 crate::validate::MatchingBracketValidator::new(), ); assert_eq!( diff --git a/src/tty/mod.rs b/src/tty/mod.rs index f2abf0df5..cfdf934f4 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 H, ) -> 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 H, ) -> 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..6e2cd9313 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 H, ) -> Result<()> { Ok(()) } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index edaea5470..ace9978d1 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 H, ) -> Result<()> { use std::fmt::Write; self.buffer.clear(); @@ -997,41 +997,41 @@ impl Renderer for PosixRenderer { self.clear_old_rows(old_layout); - if let Some(highlighter) = highlighter { - // display the prompt - self.buffer - .push_str(&highlighter.highlight_prompt(prompt, default_prompt)); - // display the input line - cfg_if::cfg_if! { - if #[cfg(not(feature = "split-highlight"))] { - self.buffer - .push_str(&highlighter.highlight(line, line.pos())); - } else if #[cfg(feature = "ansi-str")] { - self.buffer - .push_str(&highlighter.highlight(line, line.pos())); - } else { - use crate::highlight::{Style, StyledBlock}; - for sb in highlighter.highlight_line(line, line.pos()) { - let style = sb.style(); - write!(self.buffer, "{}", style.start())?; - self.buffer.push_str(sb.text()); - write!(self.buffer, "{}", style.end())?; - } + // if let Some(highlighter) = highlighter { + // display the prompt + self.buffer + .push_str(&highlighter.highlight_prompt(prompt, default_prompt)); + // display the input line + cfg_if::cfg_if! { + if #[cfg(not(feature = "split-highlight"))] { + self.buffer + .push_str(&highlighter.highlight(line, line.pos())); + } else if #[cfg(feature = "ansi-str")] { + self.buffer + .push_str(&highlighter.highlight(line, line.pos())); + } else { + use crate::highlight::{Style, StyledBlock}; + for sb in highlighter.highlight_line(line, line.pos()) { + let style = sb.style(); + write!(self.buffer, "{}", style.start())?; + self.buffer.push_str(sb.text()); + write!(self.buffer, "{}", style.end())?; } } - } else { - // display the prompt - self.buffer.push_str(prompt); - // display the input line - self.buffer.push_str(line); } + // } else { + // // display the prompt + // self.buffer.push_str(prompt); + // // display the input line + // self.buffer.push_str(line); + // } // display hint if let Some(hint) = hint { - if let Some(highlighter) = highlighter { - self.buffer.push_str(&highlighter.highlight_hint(hint)); - } else { - self.buffer.push_str(hint); - } + // if let Some(highlighter) = highlighter { + self.buffer.push_str(&highlighter.highlight_hint(hint)); + // } else { + // self.buffer.push_str(hint); + // } } // we have to generate our own newline on line wrap if end_pos.col == 0 @@ -1088,6 +1088,14 @@ impl Renderer for PosixRenderer { pos.col = 0; pos.row += 1; } + cfg_if::cfg_if! { + if #[cfg(feature = "continuation-prompt")] { + // add continuation prompt offset + if pos.row > orig.row { + pos.col += orig.col; + } + } + } pos } @@ -1708,14 +1716,29 @@ mod test { line.insert('a', out.cols - prompt_size.col + 1, &mut NoListener) ); let new_layout = out.compute_layout(prompt_size, default_prompt, &line, None); - assert_eq!(Position { col: 1, row: 1 }, new_layout.cursor); + cfg_if::cfg_if! { + if #[cfg(feature = "continuation-prompt")] { + assert_eq!(Position { col: 3, row: 1 }, new_layout.cursor); + } else { + 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 ()) .unwrap(); #[rustfmt::skip] - assert_eq!( - "\r\u{1b}[K> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\u{1b}[1C", - out.buffer - ); + cfg_if::cfg_if! { + if #[cfg(feature = "continuation-prompt")] { + assert_eq!( + "\r\u{1b}[K> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\u{1b}[3C", + out.buffer + ); + } else { + assert_eq!( + "\r\u{1b}[K> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\u{1b}[1C", + out.buffer + ); + } + } } } diff --git a/src/validate.rs b/src/validate.rs index 12b7f3314..2829e6f35 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -6,8 +6,8 @@ use crate::Result; /// Input validation result #[non_exhaustive] pub enum ValidationResult { - /// Incomplete input - Incomplete, + /// Incomplete input with indent count + Incomplete(usize), /// Validation fails with an optional error message. User must fix the /// input. Invalid(Option), @@ -20,6 +20,14 @@ impl ValidationResult { matches!(self, Self::Valid(_)) } + pub(crate) fn is_incomplete(&self) -> Option { + if let Self::Incomplete(indent) = self { + Some(*indent) + } else { + None + } + } + pub(crate) fn has_message(&self) -> bool { matches!(self, Self::Valid(Some(_)) | Self::Invalid(Some(_))) } @@ -67,7 +75,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 +87,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 +119,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())) } } @@ -140,6 +148,6 @@ fn validate_brackets(input: &str) -> ValidationResult { if stack.is_empty() { ValidationResult::Valid(None) } else { - ValidationResult::Incomplete + ValidationResult::Incomplete(0) } } From 7d5ac69a63184b9f8365c3bae885006f56160214 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Fri, 27 Sep 2024 07:34:47 +0800 Subject: [PATCH 02/13] fix colors_enabled error --- src/edit.rs | 9 -------- src/lib.rs | 17 +++++++-------- src/tty/unix.rs | 58 ++++++++++++++++++++++++------------------------- 3 files changed, 37 insertions(+), 47 deletions(-) diff --git a/src/edit.rs b/src/edit.rs index f51309864..ee50ff86b 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -69,14 +69,6 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { } } - // pub fn highlighter(&mut self) -> Option<&mut H> { - // if self.out.colors_enabled() { - // Some(self.helper) - // } else { - // None - // } - // } - pub fn next_cmd( &mut self, input_state: &mut InputState, @@ -178,7 +170,6 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { debug!(target: "rustyline", "old layout: {:?}", self.layout); debug!(target: "rustyline", "new layout: {:?}", new_layout); - // if self.out.colors_enabled() { self.out.refresh_line( prompt, &self.line, diff --git a/src/lib.rs b/src/lib.rs index 2fac786e2..d680d4e23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -338,15 +338,14 @@ fn page_completions( if i < candidates.len() { let candidate = &candidates[i].display(); let width = candidate.width(); - ab.push_str( - &s.helper - .highlight_candidate(candidate, CompletionType::List), - ); - // if let Some(highlighter) = s.highlighter() { - // ab.push_str(&highlighter.highlight_candidate(candidate, CompletionType::List)); - // } else { - // ab.push_str(candidate); - // } + if s.out.colors_enabled() { + ab.push_str( + &s.helper + .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(' '); diff --git a/src/tty/unix.rs b/src/tty/unix.rs index ace9978d1..331bdf6b8 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -997,41 +997,41 @@ impl Renderer for PosixRenderer { self.clear_old_rows(old_layout); - // if let Some(highlighter) = highlighter { // display the prompt - self.buffer - .push_str(&highlighter.highlight_prompt(prompt, default_prompt)); - // display the input line - cfg_if::cfg_if! { - if #[cfg(not(feature = "split-highlight"))] { - self.buffer - .push_str(&highlighter.highlight(line, line.pos())); - } else if #[cfg(feature = "ansi-str")] { - self.buffer - .push_str(&highlighter.highlight(line, line.pos())); - } else { - use crate::highlight::{Style, StyledBlock}; - for sb in highlighter.highlight_line(line, line.pos()) { - let style = sb.style(); - write!(self.buffer, "{}", style.start())?; - self.buffer.push_str(sb.text()); - write!(self.buffer, "{}", style.end())?; + if self.colors_enabled() { + self.buffer + .push_str(&highlighter.highlight_prompt(prompt, default_prompt)); + // display the input line + cfg_if::cfg_if! { + if #[cfg(not(feature = "split-highlight"))] { + self.buffer + .push_str(&highlighter.highlight(line, line.pos())); + } else if #[cfg(feature = "ansi-str")] { + self.buffer + .push_str(&highlighter.highlight(line, line.pos())); + } else { + use crate::highlight::{Style, StyledBlock}; + for sb in highlighter.highlight_line(line, line.pos()) { + let style = sb.style(); + write!(self.buffer, "{}", style.start())?; + self.buffer.push_str(sb.text()); + write!(self.buffer, "{}", style.end())?; + } } } + } else { + // display the prompt + self.buffer.push_str(prompt); + // display the input line + self.buffer.push_str(line); } - // } else { - // // display the prompt - // self.buffer.push_str(prompt); - // // display the input line - // self.buffer.push_str(line); - // } // display hint if let Some(hint) = hint { - // if let Some(highlighter) = highlighter { - self.buffer.push_str(&highlighter.highlight_hint(hint)); - // } else { - // self.buffer.push_str(hint); - // } + if self.colors_enabled() { + self.buffer.push_str(&highlighter.highlight_hint(hint)); + } else { + self.buffer.push_str(hint); + } } // we have to generate our own newline on line wrap if end_pos.col == 0 From 92d4155379eb4f16d49c733863020f3d77844844 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Fri, 27 Sep 2024 07:43:00 +0800 Subject: [PATCH 03/13] fix colors_enabled error --- src/edit.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/edit.rs b/src/edit.rs index ee50ff86b..38f7abcb8 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -204,23 +204,23 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { } fn highlight_char(&mut self) -> bool { - // if let Some(highlighter) = self.highlighter() { - let highlight_char = - self.helper - .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() { + let highlight_char = + self.helper + .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 } - // } else { - // false - // } } pub fn is_default_prompt(&self) -> bool { From e0e960c15c987b11b56f7c9a552de63be103db81 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Fri, 27 Sep 2024 16:01:11 +0800 Subject: [PATCH 04/13] user specified continuation_prompt_width --- examples/continuation_prompt.rs | 1 + examples/example.rs | 4 +- examples/read_password.rs | 10 ++-- rustyline-derive/src/lib.rs | 4 +- src/edit.rs | 28 ++++++---- src/highlight.rs | 92 +++++++++++++++++---------------- src/lib.rs | 19 ++++++- src/tty/mod.rs | 23 ++++++--- src/tty/test.rs | 8 ++- src/tty/unix.rs | 53 +++++++------------ src/tty/windows.rs | 8 ++- 11 files changed, 137 insertions(+), 113 deletions(-) diff --git a/examples/continuation_prompt.rs b/examples/continuation_prompt.rs index 83390de16..dbf7f4193 100644 --- a/examples/continuation_prompt.rs +++ b/examples/continuation_prompt.rs @@ -45,6 +45,7 @@ impl Highlighter for InputValidator { fn highlight_char(&mut self, _line: &str, _pos: usize, _forced: bool) -> bool { self.need_render } + #[cfg(feature = "split-highlight")] fn highlight_line<'l>( &mut self, line: &'l str, diff --git a/examples/example.rs b/examples/example.rs index efc915e96..017262762 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -37,12 +37,12 @@ impl Highlighter for MyHelper { Owned("\x1b[1m".to_owned() + hint + "\x1b[m") } - #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] + #[cfg(not(feature = "split-highlight"))] 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")))] + #[cfg(feature = "split-highlight")] fn highlight_line<'l>( &mut self, line: &'l str, diff --git a/examples/read_password.rs b/examples/read_password.rs index b92a2715d..368985815 100644 --- a/examples/read_password.rs +++ b/examples/read_password.rs @@ -9,7 +9,7 @@ struct MaskingHighlighter { } impl Highlighter for MaskingHighlighter { - #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] + #[cfg(not(feature = "split-highlight"))] fn highlight<'l>(&mut self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> { use unicode_width::UnicodeWidthStr; if self.masking { @@ -19,21 +19,21 @@ impl Highlighter for MaskingHighlighter { } } - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + #[cfg(feature = "split-highlight")] fn highlight_line<'l>( - &self, + &mut self, line: &'l str, _pos: usize, ) -> impl Iterator { use unicode_width::UnicodeWidthStr; if self.masking { vec![( - rustyline::highlight::AnsiStyle::default(), + (), " ".repeat(line.width()), )] .into_iter() } else { - vec![(rustyline::highlight::AnsiStyle::default(), line.to_owned())].into_iter() + vec![((), line.to_owned())].into_iter() } } diff --git a/rustyline-derive/src/lib.rs b/rustyline-derive/src/lib.rs index b68063083..1fc99bb1d 100644 --- a/rustyline-derive/src/lib.rs +++ b/rustyline-derive/src/lib.rs @@ -102,12 +102,12 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream { quote! { #[automatically_derived] impl #impl_generics ::rustyline::highlight::Highlighter for #name #ty_generics #where_clause { - #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] + #[cfg(not(feature = "split-highlight"))] 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")))] + #[cfg(feature = "split-highlight")] fn highlight_line<'l>( &mut self, line: &'l str, diff --git a/src/edit.rs b/src/edit.rs index 38f7abcb8..0b7c3cf10 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -51,7 +51,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { helper: &'out mut H, ctx: Context<'out>, ) -> Self { - let prompt_size = out.calculate_position(prompt, Position::default()); + let prompt_size = out.calculate_position(prompt, Position::default(), helper.continuation_prompt_width(prompt)); Self { out, prompt, @@ -86,9 +86,9 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { if new_cols != old_cols && (self.layout.end.row > 0 || self.layout.end.col >= new_cols) { - self.prompt_size = self - .out - .calculate_position(self.prompt, Position::default()); + self.prompt_size = + self.out + .calculate_position(self.prompt, Position::default(), self.helper.continuation_prompt_width(self.prompt)); self.refresh_line()?; } continue; @@ -115,9 +115,11 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { pub fn move_cursor(&mut self) -> Result<()> { // calculate the desired position of the cursor - let cursor = self - .out - .calculate_position(&self.line[..self.line.pos()], self.prompt_size); + let cursor = self.out.calculate_position( + &self.line[..self.line.pos()], + self.prompt_size, + self.helper.continuation_prompt_width(&self.prompt), + ); if self.layout.cursor == cursor { return Ok(()); } @@ -164,9 +166,13 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { Info::Msg(msg) => msg, }; - let new_layout = self - .out - .compute_layout(prompt_size, default_prompt, &self.line, info); + let new_layout = self.out.compute_layout( + prompt_size, + default_prompt, + self.helper.continuation_prompt_width(prompt), + &self.line, + info, + ); debug!(target: "rustyline", "old layout: {:?}", self.layout); debug!(target: "rustyline", "new layout: {:?}", new_layout); @@ -280,7 +286,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()); + let prompt_size = self.out.calculate_position(prompt, Position::default(), self.helper.continuation_prompt_width(prompt)); self.update_after_edit(); self.hint(); self.highlight_char(); diff --git a/src/highlight.rs b/src/highlight.rs index 8416ceede..a9ab49389 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -50,14 +50,6 @@ impl Style for anstyle::Style { } } -/// ANSI Style -#[cfg(feature = "anstyle")] -#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))] -pub type AnsiStyle = anstyle::Style; -/// ANSI Style -#[cfg(not(feature = "anstyle"))] -pub type AnsiStyle = (); - /// Styled text #[cfg(feature = "split-highlight")] #[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] @@ -86,25 +78,13 @@ impl StyledBlock for ansi_str::AnsiBlock<'_> { self.style() } }*/ -#[cfg(feature = "split-highlight")] -#[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] -impl StyledBlock for (AnsiStyle, &str) { - type Style = AnsiStyle; - fn text(&self) -> &str { - self.1 - } - - fn style(&self) -> &Self::Style { - &self.0 - } -} -#[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] -impl StyledBlock for (AnsiStyle, String) { - type Style = AnsiStyle; +#[cfg(feature = "split-highlight")] +impl> StyledBlock for (S, T) { + type Style = S; fn text(&self) -> &str { - &self.1 + self.1.as_ref() } fn style(&self) -> &Self::Style { @@ -122,10 +102,10 @@ pub trait Highlighter { /// /// For example, you can implement /// [blink-matching-paren](https://www.gnu.org/software/bash/manual/html_node/Readline-Init-File-Syntax.html). - #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] + #[cfg(not(feature = "split-highlight"))] #[cfg_attr( docsrs, - doc(cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))) + doc(cfg(not(feature = "split-highlight"))) )] fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> Cow<'l, str> { let _ = pos; @@ -134,10 +114,10 @@ pub trait Highlighter { /// Takes the currently edited `line` with the cursor `pos`ition and /// returns the styled blocks. - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + #[cfg(feature = "split-highlight")] #[cfg_attr( docsrs, - doc(cfg(all(feature = "split-highlight", not(feature = "ansi-str")))) + doc(cfg(feature = "split-highlight")) )] fn highlight_line<'l>( &mut self, @@ -145,7 +125,7 @@ pub trait Highlighter { pos: usize, ) -> impl Iterator { let _ = (line, pos); - vec![(AnsiStyle::default(), line)].into_iter() + vec![((), line)].into_iter() } /// Takes the `prompt` and @@ -191,12 +171,12 @@ pub trait Highlighter { impl Highlighter for () {} impl<'r, H: Highlighter> Highlighter for &'r mut H { - #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] + #[cfg(not(feature = "split-highlight"))] 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")))] + #[cfg(feature = "split-highlight")] fn highlight_line<'l>( &mut self, line: &'l str, @@ -260,7 +240,7 @@ impl MatchingBracketHighlighter { feature = "ansi-str" ))] impl Highlighter for MatchingBracketHighlighter { - #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] + #[cfg(not(feature = "split-highlight"))] fn highlight<'l>(&mut self, line: &'l str, _pos: usize) -> Cow<'l, str> { if line.len() <= 1 { return Borrowed(line); @@ -276,26 +256,48 @@ impl Highlighter for MatchingBracketHighlighter { Borrowed(line) } - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + #[cfg(feature = "split-highlight")] fn highlight_line<'l>( &mut self, line: &'l str, _pos: usize, ) -> impl Iterator { - if line.len() <= 1 { - return vec![(AnsiStyle::default(), line)].into_iter(); - } - if let Some((bracket, pos)) = self.bracket.get() { - if let Some((_, idx)) = find_matching_bracket(line, pos, bracket) { - return vec![ - (AnsiStyle::default(), &line[0..idx]), - (self.style, &line[idx..=idx]), - (AnsiStyle::default(), &line[idx + 1..]), - ] - .into_iter(); + cfg_if::cfg_if!{ + if #[cfg(feature = "anstyle")]{ + if line.len() <= 1 { + return vec![(anstyle::Style::new(), line)].into_iter(); + } + if let Some((bracket, pos)) = self.bracket.get() { + if let Some((_, idx)) = find_matching_bracket(line, pos, bracket) { + #[cfg(feature = "anstyle")] + return vec![ + (anstyle::Style::new(), &line[0..idx]), + (self.style, &line[idx..=idx]), + (anstyle::Style::new(), &line[idx + 1..]), + ] + .into_iter(); + } + } + vec![(anstyle::Style::new(), line)].into_iter() + }else{ + if line.len() <= 1 { + return vec![((), line)].into_iter(); + } + if let Some((bracket, pos)) = self.bracket.get() { + if let Some((_, idx)) = find_matching_bracket(line, pos, bracket) { + #[cfg(feature = "anstyle")] + return vec![ + ((), &line[0..idx]), + ((), &line[idx..=idx]), + ((), &line[idx + 1..]), + ] + .into_iter(); + } + } + vec![((), line)].into_iter() } } - vec![(AnsiStyle::default(), line)].into_iter() + } fn highlight_char(&mut self, line: &str, pos: usize, forced: bool) -> bool { diff --git a/src/lib.rs b/src/lib.rs index d680d4e23..3846fade6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -572,11 +572,28 @@ where fn update_after_move_cursor(&mut self, line: &str, pos: usize) { _ = (line, pos); } + + /// Return the width of continuation prompt, + /// the continuation prompt should be implemented at highlight + fn continuation_prompt_width<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str) -> usize { + _ = prompt; + 0 + } } impl Helper for () {} -impl<'h, H: Helper> Helper for &'h mut 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) + } + fn continuation_prompt_width<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str) -> usize { + (**self).continuation_prompt_width(prompt) + } +} /// Completion/suggestion context pub struct Context<'h> { diff --git a/src/tty/mod.rs b/src/tty/mod.rs index cfdf934f4..2dcdf6568 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -64,20 +64,21 @@ pub trait Renderer { &self, prompt_size: Position, default_prompt: bool, + continuation_prompt_width: usize, line: &LineBuffer, info: Option<&str>, ) -> Layout { // calculate the desired position of the cursor let pos = line.pos(); - let cursor = self.calculate_position(&line[..pos], prompt_size); + let cursor = self.calculate_position(&line[..pos], prompt_size, continuation_prompt_width); // calculate the position of the end of the input line let mut end = if pos == line.len() { cursor } else { - self.calculate_position(&line[pos..], cursor) + self.calculate_position(&line[pos..], cursor, continuation_prompt_width) }; if let Some(info) = info { - end = self.calculate_position(info, end); + end = self.calculate_position(info, end, continuation_prompt_width); } let new_layout = Layout { @@ -93,7 +94,12 @@ pub trait Renderer { /// Calculate the number of columns and rows used to display `s` on a /// `cols` width terminal starting at `orig`. - fn calculate_position(&self, s: &str, orig: Position) -> Position; + fn calculate_position( + &self, + s: &str, + orig: Position, + continuation_prompt_width: usize, + ) -> Position; fn write_and_flush(&mut self, buf: &str) -> Result<()>; @@ -138,8 +144,13 @@ impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R { (**self).refresh_line(prompt, line, hint, old_layout, new_layout, highlighter) } - fn calculate_position(&self, s: &str, orig: Position) -> Position { - (**self).calculate_position(s, orig) + fn calculate_position( + &self, + s: &str, + orig: Position, + continuation_prompt_width: usize, + ) -> Position { + (**self).calculate_position(s, orig, continuation_prompt_width) } fn write_and_flush(&mut self, buf: &str) -> Result<()> { diff --git a/src/tty/test.rs b/src/tty/test.rs index 6e2cd9313..56c230bbc 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -112,7 +112,13 @@ impl Renderer for Sink { Ok(()) } - fn calculate_position(&self, s: &str, orig: Position) -> Position { + fn calculate_position( + &self, + s: &str, + orig: Position, + continuation_prompt_width: usize, + ) -> Position { + _ = continuation_prompt_width; let mut pos = orig; pos.col += s.len(); pos diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 331bdf6b8..a97e98eb5 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1006,9 +1006,6 @@ impl Renderer for PosixRenderer { if #[cfg(not(feature = "split-highlight"))] { self.buffer .push_str(&highlighter.highlight(line, line.pos())); - } else if #[cfg(feature = "ansi-str")] { - self.buffer - .push_str(&highlighter.highlight(line, line.pos())); } else { use crate::highlight::{Style, StyledBlock}; for sb in highlighter.highlight_line(line, line.pos()) { @@ -1064,7 +1061,12 @@ impl Renderer for PosixRenderer { /// Control characters are treated as having zero width. /// Characters with 2 column width are correctly handled (not split). - fn calculate_position(&self, s: &str, orig: Position) -> Position { + fn calculate_position( + &self, + s: &str, + orig: Position, + continuation_prompt_width: usize, + ) -> Position { let mut pos = orig; let mut esc_seq = 0; for c in s.graphemes(true) { @@ -1088,13 +1090,9 @@ impl Renderer for PosixRenderer { pos.col = 0; pos.row += 1; } - cfg_if::cfg_if! { - if #[cfg(feature = "continuation-prompt")] { - // add continuation prompt offset - if pos.row > orig.row { - pos.col += orig.col; - } - } + // add continuation prompt offset + if pos.row > orig.row { + pos.col += continuation_prompt_width; } pos } @@ -1673,7 +1671,7 @@ mod test { #[ignore] fn prompt_with_ansi_escape_codes() { let out = PosixRenderer::new(libc::STDOUT_FILENO, 4, true, BellStyle::default()); - let pos = out.calculate_position("\x1b[1;32m>>\x1b[0m ", Position::default()); + let pos = out.calculate_position("\x1b[1;32m>>\x1b[0m ", Position::default(), 0); assert_eq!(3, pos.col); assert_eq!(0, pos.row); } @@ -1704,10 +1702,10 @@ mod test { let mut out = PosixRenderer::new(libc::STDOUT_FILENO, 4, true, BellStyle::default()); let prompt = "> "; let default_prompt = true; - let prompt_size = out.calculate_position(prompt, Position::default()); + let prompt_size = out.calculate_position(prompt, Position::default(), 0); let mut line = LineBuffer::init("", 0); - let old_layout = out.compute_layout(prompt_size, default_prompt, &line, None); + let old_layout = out.compute_layout(prompt_size, default_prompt, 0, &line, None); assert_eq!(Position { col: 2, row: 0 }, old_layout.cursor); assert_eq!(old_layout.cursor, old_layout.end); @@ -1715,30 +1713,15 @@ mod test { Some(true), line.insert('a', out.cols - prompt_size.col + 1, &mut NoListener) ); - let new_layout = out.compute_layout(prompt_size, default_prompt, &line, None); - cfg_if::cfg_if! { - if #[cfg(feature = "continuation-prompt")] { - assert_eq!(Position { col: 3, row: 1 }, new_layout.cursor); - } else { - assert_eq!(Position { col: 1, row: 1 }, new_layout.cursor); - } - } + let new_layout = out.compute_layout(prompt_size, default_prompt, 0, &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, &mut ()) .unwrap(); #[rustfmt::skip] - cfg_if::cfg_if! { - if #[cfg(feature = "continuation-prompt")] { - assert_eq!( - "\r\u{1b}[K> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\u{1b}[3C", - out.buffer - ); - } else { - assert_eq!( - "\r\u{1b}[K> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\u{1b}[1C", - out.buffer - ); - } - } + assert_eq!( + "\r\u{1b}[K> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\u{1b}[1C", + out.buffer + ); } } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 2d2c04796..431f1052d 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -444,7 +444,7 @@ impl Renderer for ConsoleRenderer { self.buffer.clear(); let mut col = 0; - if let Some(highlighter) = highlighter { + if self.colors_enabled() { // TODO handle ansi escape code (SetConsoleTextAttribute) // append the prompt col = self.wrap_at_eol(&highlighter.highlight_prompt(prompt, default_prompt), col); @@ -476,12 +476,10 @@ impl Renderer for ConsoleRenderer { // append the input line self.buffer.push_str(line); } - // append hint + // display hint if let Some(hint) = hint { - if let Some(highlighter) = highlighter { + if self.colors_enabled() { self.wrap_at_eol(&highlighter.highlight_hint(hint), col); - } else if self.colors_enabled { - self.wrap_at_eol(hint, col); } else { self.buffer.push_str(hint); } From 1dd5ba0a2867e7a64754e3dd0ecfb4d51a0f6693 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Sat, 28 Sep 2024 06:56:44 +0800 Subject: [PATCH 05/13] fix lint error --- examples/read_password.rs | 6 +----- src/edit.rs | 20 +++++++++++++++----- src/highlight.rs | 26 ++++++-------------------- src/lib.rs | 2 +- 4 files changed, 23 insertions(+), 31 deletions(-) diff --git a/examples/read_password.rs b/examples/read_password.rs index 368985815..6c8b640ad 100644 --- a/examples/read_password.rs +++ b/examples/read_password.rs @@ -27,11 +27,7 @@ impl Highlighter for MaskingHighlighter { ) -> impl Iterator { use unicode_width::UnicodeWidthStr; if self.masking { - vec![( - (), - " ".repeat(line.width()), - )] - .into_iter() + vec![((), " ".repeat(line.width()))].into_iter() } else { vec![((), line.to_owned())].into_iter() } diff --git a/src/edit.rs b/src/edit.rs index 0b7c3cf10..3cedbd6c6 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -51,7 +51,11 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { helper: &'out mut H, ctx: Context<'out>, ) -> Self { - let prompt_size = out.calculate_position(prompt, Position::default(), helper.continuation_prompt_width(prompt)); + let prompt_size = out.calculate_position( + prompt, + Position::default(), + helper.continuation_prompt_width(prompt), + ); Self { out, prompt, @@ -86,9 +90,11 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { if new_cols != old_cols && (self.layout.end.row > 0 || self.layout.end.col >= new_cols) { - self.prompt_size = - self.out - .calculate_position(self.prompt, Position::default(), self.helper.continuation_prompt_width(self.prompt)); + self.prompt_size = self.out.calculate_position( + self.prompt, + Position::default(), + self.helper.continuation_prompt_width(self.prompt), + ); self.refresh_line()?; } continue; @@ -286,7 +292,11 @@ 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.helper.continuation_prompt_width(prompt)); + let prompt_size = self.out.calculate_position( + prompt, + Position::default(), + self.helper.continuation_prompt_width(prompt), + ); self.update_after_edit(); self.hint(); self.highlight_char(); diff --git a/src/highlight.rs b/src/highlight.rs index a9ab49389..82e046319 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -1,22 +1,18 @@ //! Syntax highlighting use crate::config::CompletionType; +use core::fmt::Display; use std::borrow::Cow::{self, Borrowed}; use std::cell::Cell; -#[cfg(feature = "split-highlight")] -use std::fmt::Display; /// ANSI style -#[cfg(feature = "split-highlight")] -#[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] -pub trait Style: Default { +pub trait Style { /// Produce a ansi sequences which sets the graphic mode fn start(&self) -> impl Display; /// Produce a ansi sequences which ends the graphic mode fn end(&self) -> impl Display; } -#[cfg(feature = "split-highlight")] impl Style for () { fn start(&self) -> impl Display { "" @@ -51,8 +47,6 @@ impl Style for anstyle::Style { } /// Styled text -#[cfg(feature = "split-highlight")] -#[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] pub trait StyledBlock { /// Style impl type Style: Style @@ -79,8 +73,7 @@ impl StyledBlock for ansi_str::AnsiBlock<'_> { } }*/ -#[cfg(feature = "split-highlight")] -impl> StyledBlock for (S, T) { +impl> StyledBlock for (S, T) { type Style = S; fn text(&self) -> &str { @@ -103,10 +96,7 @@ pub trait Highlighter { /// For example, you can implement /// [blink-matching-paren](https://www.gnu.org/software/bash/manual/html_node/Readline-Init-File-Syntax.html). #[cfg(not(feature = "split-highlight"))] - #[cfg_attr( - docsrs, - doc(cfg(not(feature = "split-highlight"))) - )] + #[cfg_attr(docsrs, doc(cfg(not(feature = "split-highlight"))))] fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> Cow<'l, str> { let _ = pos; Borrowed(line) @@ -115,10 +105,7 @@ pub trait Highlighter { /// Takes the currently edited `line` with the cursor `pos`ition and /// returns the styled blocks. #[cfg(feature = "split-highlight")] - #[cfg_attr( - docsrs, - doc(cfg(feature = "split-highlight")) - )] + #[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] fn highlight_line<'l>( &mut self, line: &'l str, @@ -262,7 +249,7 @@ impl Highlighter for MatchingBracketHighlighter { line: &'l str, _pos: usize, ) -> impl Iterator { - cfg_if::cfg_if!{ + cfg_if::cfg_if! { if #[cfg(feature = "anstyle")]{ if line.len() <= 1 { return vec![(anstyle::Style::new(), line)].into_iter(); @@ -297,7 +284,6 @@ impl Highlighter for MatchingBracketHighlighter { vec![((), line)].into_iter() } } - } fn highlight_char(&mut self, line: &str, pos: usize, forced: bool) -> bool { diff --git a/src/lib.rs b/src/lib.rs index 3846fade6..ecefcdd80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -588,7 +588,7 @@ impl<'h, H: Helper> Helper for &'h mut H { (**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) + (**self).update_after_move_cursor(line, pos) } fn continuation_prompt_width<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str) -> usize { (**self).continuation_prompt_width(prompt) From 824b3c092ca120369482363425c8461c00a24281 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Sat, 28 Sep 2024 07:15:02 +0800 Subject: [PATCH 06/13] update continuation_prompt example --- examples/continuation_prompt.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/continuation_prompt.rs b/examples/continuation_prompt.rs index dbf7f4193..8aee54099 100644 --- a/examples/continuation_prompt.rs +++ b/examples/continuation_prompt.rs @@ -24,6 +24,9 @@ impl Helper for InputValidator { }); self.need_render = true; } + fn continuation_prompt_width<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str) -> usize { + 3 + } } impl Validator for InputValidator { From 41ac30a8ecaa5c460fb02b442cf97db21b9b4499 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Sun, 29 Sep 2024 13:53:41 +0800 Subject: [PATCH 07/13] remove unused `continuation-prompt` --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b04783b98..bf180dbdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,6 @@ with-fuzzy = ["skim"] case_insensitive_history_search = ["regex"] # For continuation prompt, indentation, scrolling split-highlight = [] -continuation-prompt = [] [[example]] name = "custom_key_bindings" From 0260bae4bcec45e86ca8dbc075730f7db569d03a Mon Sep 17 00:00:00 2001 From: junzhuo Date: Sun, 29 Sep 2024 13:54:39 +0800 Subject: [PATCH 08/13] remove unused `continuation-prompt` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bf180dbdb..94340eb60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ name = "sqlite_history" required-features = ["with-sqlite-history"] [[example]] name = "continuation_prompt" -required-features = ["custom-bindings", "derive", "continuation-prompt", "split-highlight"] +required-features = ["custom-bindings", "derive", "split-highlight"] [package.metadata.docs.rs] features = [ From 496d2767ef5168d1629ae7f7cc712f8e6d8000cf Mon Sep 17 00:00:00 2001 From: junzhuo Date: Sun, 29 Sep 2024 19:04:27 +0800 Subject: [PATCH 09/13] use consistent & back-compatible highlight output type `impl 'b + DisplayOnce` --- Cargo.toml | 2 +- examples/continuation_prompt.rs | 14 +- examples/custom_key_bindings.rs | 2 +- examples/example.rs | 18 +-- examples/input_multiline.rs | 2 +- examples/read_password.rs | 21 +-- rustyline-derive/src/lib.rs | 22 +-- src/error.rs | 2 +- src/highlight.rs | 235 +++++++++++++++++++------------- src/lib.rs | 8 +- src/tty/unix.rs | 27 ++-- src/tty/windows.rs | 31 +---- 12 files changed, 190 insertions(+), 194 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 94340eb60..01f7d142f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = ["rustyline-derive"] # For convenience / compatibilty, you can highlight the whole input buffer, ansi-str helps to split ansi-str = { version = "0.8.0", optional = true } # ansi_str::Style is immutable so we use anstyle::Style instead -anstyle = { version = "1.0.8", optional = true } +anstyle = "1.0.8" bitflags = "2.6" cfg-if = "1.0" # For file completion diff --git a/examples/continuation_prompt.rs b/examples/continuation_prompt.rs index 8aee54099..065e28622 100644 --- a/examples/continuation_prompt.rs +++ b/examples/continuation_prompt.rs @@ -1,4 +1,4 @@ -use rustyline::highlight::Highlighter; +use rustyline::highlight::{DisplayOnce, Highlighter, StyledBlocks}; use rustyline::validate::{ValidationContext, ValidationResult, Validator}; use rustyline::{Cmd, Editor, EventHandler, Helper, KeyCode, KeyEvent, Modifiers, Result}; use rustyline::{Completer, Hinter}; @@ -48,17 +48,17 @@ impl Highlighter for InputValidator { fn highlight_char(&mut self, _line: &str, _pos: usize, _forced: bool) -> bool { self.need_render } - #[cfg(feature = "split-highlight")] - fn highlight_line<'l>( - &mut self, + fn highlight<'b, 's: 'b, 'l: 'b>( + &'s mut self, line: &'l str, _pos: usize, - ) -> impl Iterator { + ) -> impl 'b + DisplayOnce { use core::iter::once; let mut lines = line.split('\n'); self.need_render = false; - once(((), lines.next().unwrap())) - .chain(lines.flat_map(|line| once(((), "\n.. ")).chain(once(((), line))))) + let iter = once(((), lines.next().unwrap())) + .chain(lines.flat_map(|line| once(((), "\n.. ")).chain(once(((), line))))); + StyledBlocks::new(iter) } } diff --git a/examples/custom_key_bindings.rs b/examples/custom_key_bindings.rs index 5159acd01..ac0a09e26 100644 --- a/examples/custom_key_bindings.rs +++ b/examples/custom_key_bindings.rs @@ -25,7 +25,7 @@ impl Highlighter for MyHelper { } } - fn highlight_hint<'h>(&mut self, hint: &'h str) -> Cow<'h, str> { + fn highlight_hint<'b, 's: 'b, 'h: 'b>(&'s mut self, hint: &'h str) -> Cow<'b, str> { Owned(format!("\x1b[1m{hint}\x1b[m")) } } diff --git a/examples/example.rs b/examples/example.rs index 017262762..df501e7f2 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -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::{DisplayOnce, Highlighter, MatchingBracketHighlighter}; use rustyline::hint::HistoryHinter; use rustyline::validate::MatchingBracketValidator; use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, KeyEvent}; @@ -33,22 +33,16 @@ impl Highlighter for MyHelper { } } - fn highlight_hint<'h>(&mut self, hint: &'h str) -> Cow<'h, str> { + fn highlight_hint<'b, 's: 'b, 'h: 'b>(&'s mut self, hint: &'h str) -> Cow<'b, str> { Owned("\x1b[1m".to_owned() + hint + "\x1b[m") } - #[cfg(not(feature = "split-highlight"))] - fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> Cow<'l, str> { - self.highlighter.highlight(line, pos) - } - - #[cfg(feature = "split-highlight")] - fn highlight_line<'l>( - &mut self, + fn highlight<'b, 's: 'b, 'l: 'b>( + &'s mut self, line: &'l str, pos: usize, - ) -> impl Iterator { - self.highlighter.highlight_line(line, pos) + ) -> impl 'b + DisplayOnce { + self.highlighter.highlight(line, pos) } fn highlight_char(&mut self, line: &str, pos: usize, forced: bool) -> bool { diff --git a/examples/input_multiline.rs b/examples/input_multiline.rs index 29c01fe47..e72e49556 100644 --- a/examples/input_multiline.rs +++ b/examples/input_multiline.rs @@ -1,4 +1,4 @@ -use rustyline::highlight::MatchingBracketHighlighter; +use rustyline::highlight::{DisplayOnce, MatchingBracketHighlighter}; use rustyline::validate::MatchingBracketValidator; use rustyline::{Cmd, Editor, EventHandler, KeyCode, KeyEvent, Modifiers, Result}; use rustyline::{Completer, Helper, Highlighter, Hinter, Validator}; diff --git a/examples/read_password.rs b/examples/read_password.rs index 6c8b640ad..79d0f5d60 100644 --- a/examples/read_password.rs +++ b/examples/read_password.rs @@ -9,8 +9,11 @@ struct MaskingHighlighter { } impl Highlighter for MaskingHighlighter { - #[cfg(not(feature = "split-highlight"))] - fn highlight<'l>(&mut self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> { + fn highlight<'b, 's: 'b, 'l: 'b>( + &'s mut self, + line: &'l str, + pos: usize, + ) -> std::borrow::Cow<'b, str> { use unicode_width::UnicodeWidthStr; if self.masking { std::borrow::Cow::Owned(" ".repeat(line.width())) @@ -19,20 +22,6 @@ impl Highlighter for MaskingHighlighter { } } - #[cfg(feature = "split-highlight")] - fn highlight_line<'l>( - &mut self, - line: &'l str, - _pos: usize, - ) -> impl Iterator { - use unicode_width::UnicodeWidthStr; - if self.masking { - vec![((), " ".repeat(line.width()))].into_iter() - } else { - vec![((), line.to_owned())].into_iter() - } - } - 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 1fc99bb1d..8b80481a7 100644 --- a/rustyline-derive/src/lib.rs +++ b/rustyline-derive/src/lib.rs @@ -102,37 +102,27 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream { quote! { #[automatically_derived] impl #impl_generics ::rustyline::highlight::Highlighter for #name #ty_generics #where_clause { - #[cfg(not(feature = "split-highlight"))] - fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> ::std::borrow::Cow<'l, str> { + fn highlight<'b,'h:'b,'l:'b>(&'h mut self, line: &'l str, pos: usize) -> impl 'b + ::rustyline::highlight::DisplayOnce { ::rustyline::highlight::Highlighter::highlight(&mut self.#field_name_or_index, line, pos) } - #[cfg(feature = "split-highlight")] - fn highlight_line<'l>( - &mut self, - line: &'l str, - pos: usize, - ) -> impl Iterator { - ::rustyline::highlight::Highlighter::highlight_line(&mut self.#field_name_or_index, line, pos) - } - fn highlight_prompt<'b, 's: 'b, 'p: 'b>( &'s mut self, prompt: &'p str, default: bool, - ) -> ::std::borrow::Cow<'b, str> { + ) -> impl 'b + ::rustyline::highlight::DisplayOnce { ::rustyline::highlight::Highlighter::highlight_prompt(&mut self.#field_name_or_index, prompt, default) } - fn highlight_hint<'h>(&mut self, hint: &'h str) -> ::std::borrow::Cow<'h, str> { + fn highlight_hint<'b, 's: 'b, 'h: 'b>(&'s mut self, hint: &'h str) -> impl 'b + ::rustyline::highlight::DisplayOnce { ::rustyline::highlight::Highlighter::highlight_hint(&mut self.#field_name_or_index, hint) } - fn highlight_candidate<'c>( - &mut self, + fn highlight_candidate<'b, 's: 'b, 'c: 'b>( + &'s mut self, candidate: &'c str, completion: ::rustyline::config::CompletionType, - ) -> ::std::borrow::Cow<'c, str> { + ) -> impl 'b + ::rustyline::highlight::DisplayOnce { ::rustyline::highlight::Highlighter::highlight_candidate(&mut self.#field_name_or_index, candidate, completion) } diff --git a/src/error.rs b/src/error.rs index 1ddfc5f64..b75101199 100644 --- a/src/error.rs +++ b/src/error.rs @@ -125,7 +125,7 @@ impl From for ReadlineError { } } -#[cfg(any(unix, all(feature = "split-highlight", not(feature = "ansi-str"))))] +#[cfg(unix)] impl From for ReadlineError { fn from(err: fmt::Error) -> Self { Self::Io(io::Error::new(io::ErrorKind::Other, err)) diff --git a/src/highlight.rs b/src/highlight.rs index 82e046319..1cd608176 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -4,6 +4,7 @@ use crate::config::CompletionType; use core::fmt::Display; use std::borrow::Cow::{self, Borrowed}; use std::cell::Cell; +use std::marker::PhantomData; /// ANSI style pub trait Style { @@ -13,6 +14,116 @@ pub trait Style { fn end(&self) -> impl Display; } +/// The general trait that consume self to display +/// +/// For **normal** highlight, all types that impl [`core::fmt::Display`] will auto-impl [`DisplayOnce`] +/// +/// For **split-highlight**, you can use `impl Iterator` +/// to get a [`StyledBlocks`], which is also impl [`DisplayOnce`]: +/// +/// ``` +/// use rustyline::highlight::{DisplayOnce, StyledBlock, StyledBlocks}; +/// use anstyle::{Ansi256Color, Style}; +/// struct Helper; +/// fn highlight<'b, 's: 'b, 'l: 'b>( +/// helper: &'s mut Helper, +/// line: &'l str, +/// ) -> impl 'b + DisplayOnce { +/// fn get_style(i: usize) -> Style { +/// Style::new().fg_color(Some(Ansi256Color((i % 16) as u8).into())) +/// } +/// let iter = (0..line.len()).map(move |i| (get_style(i), &line[i..i + 1])); +/// StyledBlocks::new(iter) +/// } +/// let mut helper = Helper; +/// highlight(&mut helper, "hello world\n").print(); +/// ``` +pub trait DisplayOnce { + /// consume self to display + fn fmt(self, f: &mut W) -> core::fmt::Result; + /// consume self to print + fn print(self) -> core::fmt::Result + where + Self: Sized, + { + struct StdoutWriter; + impl core::fmt::Write for StdoutWriter { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + use std::io::Write; + std::io::stdout() + .write_all(s.as_bytes()) + .map_err(|_| core::fmt::Error) + } + } + let mut stdout = StdoutWriter; + Self::fmt(self, &mut stdout) + } +} + +impl<'l, T: Display> DisplayOnce for T { + fn fmt(self, f: &mut W) -> core::fmt::Result { + write!(f, "{}", self) + } +} + +/// A wrapper of `impl Iterator` +/// that impl [`DisplayOnce`]: +/// +/// ``` +/// use rustyline::highlight::{DisplayOnce, StyledBlock, StyledBlocks}; +/// use anstyle::{Ansi256Color, Style}; +/// struct Helper; +/// fn highlight<'b, 's: 'b, 'l: 'b>( +/// helper: &'s mut Helper, +/// line: &'l str, +/// ) -> impl 'b + DisplayOnce { +/// fn get_style(i: usize) -> Style { +/// Style::new().fg_color(Some(Ansi256Color((i % 16) as u8).into())) +/// } +/// let iter = (0..line.len()).map(move |i| (get_style(i), &line[i..i + 1])); +/// StyledBlocks::new(iter) +/// } +/// let mut helper = Helper; +/// highlight(&mut helper, "hello world\n").print(); +/// ``` +pub struct StyledBlocks<'l, B, I> +where + B: 'l + StyledBlock, + I: Iterator, +{ + iter: I, + _marker: PhantomData<&'l ()>, +} + +impl<'l, B, I> StyledBlocks<'l, B, I> +where + B: 'l + StyledBlock, + I: Iterator, +{ + /// create a new [`StyledBlocks`] wrapper + pub const fn new(iter: I) -> Self { + Self { + iter, + _marker: PhantomData, + } + } +} + +impl<'l, B, I> DisplayOnce for StyledBlocks<'l, B, I> +where + B: 'l + StyledBlock, + I: Iterator, +{ + fn fmt(self, f: &mut W) -> core::fmt::Result { + self.iter + .map(|block| { + let style = block.style(); + write!(f, "{}{}{}", style.start(), block.text(), style.end()) + }) + .collect() + } +} + impl Style for () { fn start(&self) -> impl Display { "" @@ -34,8 +145,8 @@ impl Style for ansi_str::Style { self.end() } }*/ -#[cfg(feature = "anstyle")] -#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))] +// #[cfg(feature = "anstyle")] +// #[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))] impl Style for anstyle::Style { fn start(&self) -> impl Display { self.render() @@ -95,24 +206,13 @@ pub trait Highlighter { /// /// For example, you can implement /// [blink-matching-paren](https://www.gnu.org/software/bash/manual/html_node/Readline-Init-File-Syntax.html). - #[cfg(not(feature = "split-highlight"))] - #[cfg_attr(docsrs, doc(cfg(not(feature = "split-highlight"))))] - fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> Cow<'l, str> { - let _ = pos; - Borrowed(line) - } - - /// Takes the currently edited `line` with the cursor `pos`ition and - /// returns the styled blocks. - #[cfg(feature = "split-highlight")] - #[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] - fn highlight_line<'l>( - &mut self, + fn highlight<'b, 's: 'b, 'l: 'b>( + &'s mut self, line: &'l str, pos: usize, - ) -> impl Iterator { - let _ = (line, pos); - vec![((), line)].into_iter() + ) -> impl 'b + DisplayOnce { + let _ = pos; + line } /// Takes the `prompt` and @@ -121,26 +221,26 @@ pub trait Highlighter { &'s mut self, prompt: &'p str, default: bool, - ) -> Cow<'b, str> { + ) -> impl 'b + DisplayOnce { let _ = default; - Borrowed(prompt) + prompt } /// Takes the `hint` and /// returns the highlighted version (with ANSI color). - fn highlight_hint<'h>(&mut self, hint: &'h str) -> Cow<'h, str> { - Borrowed(hint) + fn highlight_hint<'b, 's: 'b, 'h: 'b>(&'s mut self, hint: &'h str) -> impl 'b + DisplayOnce { + hint } /// Takes the completion `candidate` and /// returns the highlighted version (with ANSI color). /// /// Currently, used only with `CompletionType::List`. - fn highlight_candidate<'c>( - &mut self, + fn highlight_candidate<'b, 's: 'b, 'c: 'b>( + &'s mut self, candidate: &'c str, // FIXME should be Completer::Candidate completion: CompletionType, - ) -> Cow<'c, str> { + ) -> impl 'b + DisplayOnce { let _ = completion; - Borrowed(candidate) + candidate } /// Tells if `line` needs to be highlighted when a specific char is typed or /// when cursor is moved under a specific char. @@ -158,37 +258,31 @@ pub trait Highlighter { impl Highlighter for () {} impl<'r, H: Highlighter> Highlighter for &'r mut H { - #[cfg(not(feature = "split-highlight"))] - fn highlight<'l>(&mut self, line: &'l str, pos: usize) -> Cow<'l, str> { - (**self).highlight(line, pos) - } - - #[cfg(feature = "split-highlight")] - fn highlight_line<'l>( - &mut self, + fn highlight<'b, 's: 'b, 'l: 'b>( + &'s mut self, line: &'l str, pos: usize, - ) -> impl Iterator { - (**self).highlight_line(line, pos) + ) -> impl 'b + DisplayOnce { + (**self).highlight(line, pos) } fn highlight_prompt<'b, 's: 'b, 'p: 'b>( &'s mut self, prompt: &'p str, default: bool, - ) -> Cow<'b, str> { + ) -> impl 'b + DisplayOnce { (**self).highlight_prompt(prompt, default) } - fn highlight_hint<'h>(&mut self, hint: &'h str) -> Cow<'h, str> { + fn highlight_hint<'b, 's: 'b, 'h: 'b>(&'s mut self, hint: &'h str) -> impl 'b + DisplayOnce { (**self).highlight_hint(hint) } - fn highlight_candidate<'c>( - &mut self, + fn highlight_candidate<'b, 's: 'b, 'c: 'b>( + &'s mut self, candidate: &'c str, completion: CompletionType, - ) -> Cow<'c, str> { + ) -> impl 'b + DisplayOnce { (**self).highlight_candidate(candidate, completion) } @@ -202,7 +296,7 @@ impl<'r, H: Highlighter> Highlighter for &'r mut H { /// Highlight matching bracket when typed or cursor moved on. #[derive(Default)] pub struct MatchingBracketHighlighter { - #[cfg(feature = "anstyle")] + // #[cfg(feature = "anstyle")] style: anstyle::Style, bracket: Cell>, // memorize the character to search... } @@ -212,7 +306,7 @@ impl MatchingBracketHighlighter { #[must_use] pub fn new() -> Self { Self { - #[cfg(feature = "anstyle")] + // #[cfg(feature = "anstyle")] style: anstyle::Style::new() .bold() .fg_color(Some(anstyle::AnsiColor::Blue.into())), @@ -221,14 +315,12 @@ impl MatchingBracketHighlighter { } } -#[cfg(any( - not(feature = "split-highlight"), - feature = "anstyle", - feature = "ansi-str" -))] impl Highlighter for MatchingBracketHighlighter { - #[cfg(not(feature = "split-highlight"))] - fn highlight<'l>(&mut self, line: &'l str, _pos: usize) -> Cow<'l, str> { + fn highlight<'b, 's: 'b, 'l: 'b>( + &'s mut self, + line: &'l str, + _pos: usize, + ) -> impl 'b + DisplayOnce { if line.len() <= 1 { return Borrowed(line); } @@ -243,49 +335,6 @@ impl Highlighter for MatchingBracketHighlighter { Borrowed(line) } - #[cfg(feature = "split-highlight")] - fn highlight_line<'l>( - &mut self, - line: &'l str, - _pos: usize, - ) -> impl Iterator { - cfg_if::cfg_if! { - if #[cfg(feature = "anstyle")]{ - if line.len() <= 1 { - return vec![(anstyle::Style::new(), line)].into_iter(); - } - if let Some((bracket, pos)) = self.bracket.get() { - if let Some((_, idx)) = find_matching_bracket(line, pos, bracket) { - #[cfg(feature = "anstyle")] - return vec![ - (anstyle::Style::new(), &line[0..idx]), - (self.style, &line[idx..=idx]), - (anstyle::Style::new(), &line[idx + 1..]), - ] - .into_iter(); - } - } - vec![(anstyle::Style::new(), line)].into_iter() - }else{ - if line.len() <= 1 { - return vec![((), line)].into_iter(); - } - if let Some((bracket, pos)) = self.bracket.get() { - if let Some((_, idx)) = find_matching_bracket(line, pos, bracket) { - #[cfg(feature = "anstyle")] - return vec![ - ((), &line[0..idx]), - ((), &line[idx..=idx]), - ((), &line[idx + 1..]), - ] - .into_iter(); - } - } - vec![((), line)].into_iter() - } - } - } - fn highlight_char(&mut self, line: &str, pos: usize, forced: bool) -> bool { if forced { self.bracket.set(None); diff --git a/src/lib.rs b/src/lib.rs index ecefcdd80..7668cf580 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,7 @@ use std::io::{self, BufRead, Write}; use std::path::Path; use std::result; +use highlight::DisplayOnce; use log::debug; #[cfg(feature = "derive")] #[cfg_attr(docsrs, doc(cfg(feature = "derive")))] @@ -339,10 +340,11 @@ fn page_completions( let candidate = &candidates[i].display(); let width = candidate.width(); if s.out.colors_enabled() { - ab.push_str( - &s.helper + DisplayOnce::fmt( + s.helper .highlight_candidate(candidate, CompletionType::List), - ); + &mut ab, + )?; } else { ab.push_str(candidate); } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index a97e98eb5..73c8999c1 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -27,7 +27,7 @@ use utf8parse::{Parser, Receiver}; use super::{width, Event, RawMode, RawReader, Renderer, Term}; use crate::config::{Behavior, BellStyle, ColorMode, Config}; -use crate::highlight::Highlighter; +use crate::highlight::{DisplayOnce, Highlighter}; use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M}; use crate::layout::{Layout, Position}; use crate::line_buffer::LineBuffer; @@ -997,25 +997,14 @@ impl Renderer for PosixRenderer { self.clear_old_rows(old_layout); - // display the prompt if self.colors_enabled() { - self.buffer - .push_str(&highlighter.highlight_prompt(prompt, default_prompt)); + // display the prompt + DisplayOnce::fmt( + highlighter.highlight_prompt(prompt, default_prompt), + &mut self.buffer, + )?; // display the input line - cfg_if::cfg_if! { - if #[cfg(not(feature = "split-highlight"))] { - self.buffer - .push_str(&highlighter.highlight(line, line.pos())); - } else { - use crate::highlight::{Style, StyledBlock}; - for sb in highlighter.highlight_line(line, line.pos()) { - let style = sb.style(); - write!(self.buffer, "{}", style.start())?; - self.buffer.push_str(sb.text()); - write!(self.buffer, "{}", style.end())?; - } - } - } + DisplayOnce::fmt(highlighter.highlight(line, line.pos()), &mut self.buffer)?; } else { // display the prompt self.buffer.push_str(prompt); @@ -1025,7 +1014,7 @@ impl Renderer for PosixRenderer { // display hint if let Some(hint) = hint { if self.colors_enabled() { - self.buffer.push_str(&highlighter.highlight_hint(hint)); + DisplayOnce::fmt(highlighter.highlight_hint(hint), &mut self.buffer)?; } else { self.buffer.push_str(hint); } diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 431f1052d..4a323deeb 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -21,7 +21,7 @@ use windows_sys::Win32::UI::Input::KeyboardAndMouse; use super::{width, Event, RawMode, RawReader, Renderer, Term}; use crate::config::{Behavior, BellStyle, ColorMode, Config}; -use crate::highlight::Highlighter; +use crate::highlight::{DisplayOnce, Highlighter}; use crate::keys::{KeyCode as K, KeyEvent, Modifiers as M}; use crate::layout::{Layout, Position}; use crate::line_buffer::LineBuffer; @@ -447,29 +447,12 @@ impl Renderer for ConsoleRenderer { if self.colors_enabled() { // TODO handle ansi escape code (SetConsoleTextAttribute) // append the prompt - col = self.wrap_at_eol(&highlighter.highlight_prompt(prompt, default_prompt), col); + DisplayOnce::fmt( + highlighter.highlight_prompt(prompt, default_prompt), + &mut self.buffer, + )?; // append the input line - cfg_if::cfg_if! { - if #[cfg(not(feature = "split-highlight"))] { - col = self.wrap_at_eol(&highlighter.highlight(line, line.pos()), col); - } else if #[cfg(feature = "ansi-str")] { - col = self.wrap_at_eol(&highlighter.highlight(line, line.pos()), col); - } else { - use std::fmt::Write; - use crate::highlight::{Style, StyledBlock}; - for sb in highlighter.highlight_line(line, line.pos()) { - let style = sb.style(); - write!(self.buffer, "{}", style.start())?; - col = self.wrap_at_eol(sb.text(), col); - write!(self.buffer, "{}", style.end())?; - } - } - } - } else if self.colors_enabled { - // append the prompt - col = self.wrap_at_eol(prompt, col); - // append the input line - col = self.wrap_at_eol(line, col); + DisplayOnce::fmt(highlighter.highlight(line, line.pos()), &mut self.buffer)?; } else { // append the prompt self.buffer.push_str(prompt); @@ -479,7 +462,7 @@ impl Renderer for ConsoleRenderer { // display hint if let Some(hint) = hint { if self.colors_enabled() { - self.wrap_at_eol(&highlighter.highlight_hint(hint), col); + DisplayOnce::fmt(highlighter.highlight_hint(hint), &mut self.buffer)?; } else { self.buffer.push_str(hint); } From 2e7b13af772ea52842b49d06c06f0b22f6f19a1c Mon Sep 17 00:00:00 2001 From: junzhuo Date: Mon, 30 Sep 2024 06:25:52 +0800 Subject: [PATCH 10/13] update `MatchingBracketHighlighter::highlight` with `StyledBlocks` method --- src/highlight.rs | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/highlight.rs b/src/highlight.rs index 1cd608176..671497244 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -2,7 +2,6 @@ use crate::config::CompletionType; use core::fmt::Display; -use std::borrow::Cow::{self, Borrowed}; use std::cell::Cell; use std::marker::PhantomData; @@ -145,8 +144,7 @@ impl Style for ansi_str::Style { self.end() } }*/ -// #[cfg(feature = "anstyle")] -// #[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))] + impl Style for anstyle::Style { fn start(&self) -> impl Display { self.render() @@ -296,7 +294,6 @@ impl<'r, H: Highlighter> Highlighter for &'r mut H { /// Highlight matching bracket when typed or cursor moved on. #[derive(Default)] pub struct MatchingBracketHighlighter { - // #[cfg(feature = "anstyle")] style: anstyle::Style, bracket: Cell>, // memorize the character to search... } @@ -306,7 +303,6 @@ impl MatchingBracketHighlighter { #[must_use] pub fn new() -> Self { Self { - // #[cfg(feature = "anstyle")] style: anstyle::Style::new() .bold() .fg_color(Some(anstyle::AnsiColor::Blue.into())), @@ -321,18 +317,24 @@ impl Highlighter for MatchingBracketHighlighter { line: &'l str, _pos: usize, ) -> impl 'b + DisplayOnce { - if line.len() <= 1 { - return Borrowed(line); - } - // highlight matching brace/bracket/parenthesis if it exists - if let Some((bracket, pos)) = self.bracket.get() { - if let Some((matching, idx)) = find_matching_bracket(line, pos, bracket) { - let mut copy = line.to_owned(); - copy.replace_range(idx..=idx, &format!("\x1b[1;34m{}\x1b[0m", matching as char)); - return Cow::Owned(copy); - } - } - Borrowed(line) + let empty_style = anstyle::Style::new(); + let iter = if line.len() <= 1 { + vec![(empty_style, line)].into_iter() + } else { + // highlight matching brace/bracket/parenthesis if it exists + self.bracket + .get() + .and_then(|(bracket, pos)| find_matching_bracket(line, pos, bracket)) + .map_or(vec![(empty_style, line)].into_iter(), |(_, idx)| { + vec![ + (empty_style, &line[0..idx]), + (self.style, &line[idx..=idx]), + (empty_style, &line[idx + 1..]), + ] + .into_iter() + }) + }; + StyledBlocks::new(iter) } fn highlight_char(&mut self, line: &str, pos: usize, forced: bool) -> bool { From 31a745538ebc01683f6f7ee188b7df8b2c2bca58 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Mon, 30 Sep 2024 06:30:40 +0800 Subject: [PATCH 11/13] remove split-highlight feature --- .github/workflows/rust.yml | 14 +++++++------- Cargo.toml | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 41c690aa2..b3d981db1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -48,13 +48,13 @@ jobs: run: cargo check --workspace --no-default-features env: RUSTFLAGS: "-D warnings" - - name: Check split-highlight feature - run: | - cargo check --workspace --all-targets --features 'split-highlight' - cargo check --workspace --all-targets --features 'split-highlight ansi-str' - cargo check --workspace --all-targets --features 'split-highlight anstyle' - cargo check --workspace --all-targets --features 'split-highlight ansi-str derive' - cargo check --workspace --all-targets --features 'split-highlight anstyle derive' + # - name: Check split-highlight feature + # run: | + # cargo check --workspace --all-targets --features 'split-highlight' + # cargo check --workspace --all-targets --features 'split-highlight ansi-str' + # cargo check --workspace --all-targets --features 'split-highlight anstyle' + # cargo check --workspace --all-targets --features 'split-highlight ansi-str derive' + # cargo check --workspace --all-targets --features 'split-highlight anstyle derive' direct-minimal-versions: name: Test min versions diff --git a/Cargo.toml b/Cargo.toml index 01f7d142f..1b35864b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ with-sqlite-history = ["rusqlite"] with-fuzzy = ["skim"] case_insensitive_history_search = ["regex"] # For continuation prompt, indentation, scrolling -split-highlight = [] +# split-highlight = [] [[example]] name = "custom_key_bindings" @@ -99,7 +99,7 @@ name = "sqlite_history" required-features = ["with-sqlite-history"] [[example]] name = "continuation_prompt" -required-features = ["custom-bindings", "derive", "split-highlight"] +required-features = ["custom-bindings", "derive"] [package.metadata.docs.rs] features = [ @@ -108,8 +108,8 @@ features = [ "with-dirs", "with-file-history", "with-fuzzy", - "split-highlight", - "anstyle", + # "split-highlight", + # "anstyle", ] all-features = false no-default-features = true From 567fdcd07e50f21d29b54be39590667f8f529f8b Mon Sep 17 00:00:00 2001 From: junzhuo Date: Mon, 30 Sep 2024 06:51:20 +0800 Subject: [PATCH 12/13] update deprecated information for `Editor::{new, with_config, set_helper}` --- src/lib.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7668cf580..626909df9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -643,12 +643,16 @@ pub type DefaultEditor = Editor<(), DefaultHistory>; #[allow(clippy::new_without_default)] impl Editor { - /// Create an editor with the default configuration + /// Create an editor with a helper and the default configuration. + /// + /// Use `new(())` for default Helper. pub fn new(helper: H) -> Result { Self::with_config(Config::default(), helper) } - /// Create an editor with a specific configuration. + /// Create an editor with a specific configuration and a helper. + /// + /// Use `with_config(config,())` for default Helper. pub fn with_config(config: Config, helper: H) -> Result { Self::with_history(config, DefaultHistory::with_config(config), helper) } @@ -883,9 +887,10 @@ impl Editor { &self.history } - /// Register a callback function to be called for tab-completion - /// or to show hints to the user at the right of the prompt. - #[deprecated = "reason"] + /// This will do nothing and will remove in future version. + /// + /// Now we should specify the helper when create the editor + #[deprecated = "specify the helper when crate the editor"] pub fn set_helper(&mut self) {} /// Return a mutable reference to the helper. From 4fd57c61abf44f84d220ffd3298905cd8f20d0b3 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Mon, 30 Sep 2024 06:55:16 +0800 Subject: [PATCH 13/13] update the `DefaultEditor` usage --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 47d4e1b79..79a60a300 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,8 @@ use rustyline::{DefaultEditor, Result}; fn main() -> Result<()> { // `()` can be used when no completer is required - let mut rl = DefaultEditor::new()?; + // Use `new(())` for default Helper. + let mut rl = DefaultEditor::new(())?; #[cfg(feature = "with-file-history")] if rl.load_history("history.txt").is_err() { println!("No previous history.");