From 15788df8c452a77b5c53e596426ffa87a936db3c Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 31 Aug 2024 10:41:28 +0200 Subject: [PATCH 01/10] Fix clippy warnings --- src/completion.rs | 1 + src/highlight.rs | 2 -- src/validate.rs | 9 +++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index f34befb4d..114d1aa9e 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -415,6 +415,7 @@ fn normalize(s: &str) -> Cow { /// Given a `line` and a cursor `pos`ition, /// try to find backward the start of a word. +/// /// Return (0, `line[..pos]`) if no break char has been found. /// Return the word and its start position (idx, `line[idx..pos]`) otherwise. #[must_use] diff --git a/src/highlight.rs b/src/highlight.rs index d5c0e8967..a21459e0a 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -5,8 +5,6 @@ use std::borrow::Cow::{self, Borrowed, Owned}; use std::cell::Cell; /// Syntax highlighter with [ANSI color](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters). -/// Rustyline will try to handle escape sequence for ANSI color on windows -/// when not supported natively (windows <10). /// /// Currently, the highlighted version *must* have the same display width as /// the original input. diff --git a/src/validate.rs b/src/validate.rs index e3bf2e247..12b7f3314 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -48,10 +48,11 @@ impl<'i> ValidationContext<'i> { } /// This trait provides an extension interface for determining whether -/// the current input buffer is valid. Rustyline uses the method -/// provided by this trait to decide whether hitting the enter key -/// will end the current editing session and return the current line -/// buffer to the caller of `Editor::readline` or variants. +/// the current input buffer is valid. +/// +/// Rustyline uses the method provided by this trait to decide whether hitting +/// the enter key will end the current editing session and return the current +/// line buffer to the caller of `Editor::readline` or variants. pub trait Validator { /// Takes the currently edited `input` and returns a /// `ValidationResult` indicating whether it is valid or not along From 35f48a9fc4c0e4e11e4083b2cf130238adaa2651 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 5 Sep 2024 20:04:28 +0200 Subject: [PATCH 02/10] Use #[expect(lint)] where possible --- src/completion.rs | 1 - src/error.rs | 2 +- src/keymap.rs | 17 +++++++---------- src/keys.rs | 1 - src/lib.rs | 3 +-- src/line_buffer.rs | 4 ++-- src/tty/mod.rs | 1 - src/tty/unix.rs | 8 +++----- src/tty/windows.rs | 2 +- 9 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 114d1aa9e..2d1971f01 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -211,7 +211,6 @@ impl FilenameCompleter { /// partial path to be completed. pub fn complete_path(&self, line: &str, pos: usize) -> Result<(usize, Vec)> { let (start, mut matches) = self.complete_path_unsorted(line, pos)?; - #[allow(clippy::unnecessary_sort_by)] matches.sort_by(|a, b| a.display().cmp(b.display())); Ok((start, matches)) } diff --git a/src/error.rs b/src/error.rs index b75101199..038ae4b2e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,7 +8,7 @@ use std::io; /// The error type for Rustyline errors that can arise from /// I/O related errors or Errno when using the nix-rust library // #[non_exhaustive] -#[allow(clippy::module_name_repetitions)] +#[expect(clippy::module_name_repetitions)] #[derive(Debug)] #[non_exhaustive] pub enum ReadlineError { diff --git a/src/keymap.rs b/src/keymap.rs index 784c91016..8564c163a 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -131,7 +131,6 @@ impl Cmd { /// Tells if current command should reset kill ring. #[must_use] pub const fn should_reset_kill_ring(&self) -> bool { - #[allow(clippy::match_same_arms)] match *self { Self::Kill(Movement::BackwardChar(_) | Movement::ForwardChar(_)) => true, Self::ClearScreen @@ -347,7 +346,7 @@ pub enum InputMode { /// Transform key(s) to commands based on current input mode pub struct InputState<'b> { pub(crate) mode: EditMode, - #[cfg_attr(not(feature = "custom-bindings"), allow(dead_code))] + #[cfg_attr(not(feature = "custom-bindings"), expect(dead_code))] custom_bindings: &'b Bindings, pub(crate) input_mode: InputMode, // vi only ? // numeric arguments: http://web.mit.edu/gnu/doc/html/rlman_1.html#SEC7 @@ -389,12 +388,12 @@ pub trait Refresher { /// Returns `true` if there is a hint displayed. fn has_hint(&self) -> bool; /// Returns the hint text that is shown after the current cursor position. - #[cfg_attr(not(feature = "custom-bindings"), allow(dead_code))] + #[cfg_attr(not(feature = "custom-bindings"), expect(dead_code))] fn hint_text(&self) -> Option<&str>; /// currently edited line fn line(&self) -> &str; /// Current cursor position (byte position) - #[cfg_attr(not(feature = "custom-bindings"), allow(dead_code))] + #[cfg_attr(not(feature = "custom-bindings"), expect(dead_code))] fn pos(&self) -> usize; /// Display `msg` above currently edited line. fn external_print(&mut self, msg: String) -> Result<()>; @@ -474,7 +473,7 @@ impl<'b> InputState<'b> { wrt: &mut dyn Refresher, digit: char, ) -> Result { - #[allow(clippy::cast_possible_truncation)] + #[expect(clippy::cast_possible_truncation)] match digit { '0'..='9' => { self.num_args = digit.to_digit(10).unwrap() as i16; @@ -487,7 +486,7 @@ impl<'b> InputState<'b> { loop { wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args))?; let key = rdr.next_key(true)?; - #[allow(clippy::cast_possible_truncation)] + #[expect(clippy::cast_possible_truncation)] match key { E(K::Char(digit @ '0'..='9'), m) if m == M::NONE || m == M::ALT => { if self.num_args == -1 { @@ -657,7 +656,7 @@ impl<'b> InputState<'b> { Ok(cmd) } - #[allow(clippy::cast_possible_truncation)] + #[expect(clippy::cast_possible_truncation)] fn vi_arg_digit( &mut self, rdr: &mut R, @@ -911,7 +910,6 @@ impl<'b> InputState<'b> { }; debug!(target: "rustyline", "Vi insert: {:?}", cmd); if cmd.is_repeatable_change() { - #[allow(clippy::if_same_then_else)] if let (Cmd::Replace(..), Cmd::SelfInsert(..)) = (&self.last_cmd, &cmd) { // replacing... } else if let (Cmd::SelfInsert(..), Cmd::SelfInsert(..)) = (&self.last_cmd, &cmd) { @@ -1101,7 +1099,7 @@ impl<'b> InputState<'b> { num_args } - #[allow(clippy::cast_sign_loss)] + #[expect(clippy::cast_sign_loss)] fn emacs_num_args(&mut self) -> (RepeatCount, bool) { let num_args = self.num_args(); if num_args < 0 { @@ -1115,7 +1113,6 @@ impl<'b> InputState<'b> { } } - #[allow(clippy::cast_sign_loss)] fn vi_num_args(&mut self) -> RepeatCount { let num_args = self.num_args(); if num_args < 0 { diff --git a/src/keys.rs b/src/keys.rs index b8c378628..7135d385d 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -24,7 +24,6 @@ impl KeyEvent { } return E(K::Char(c), mods); } - #[allow(clippy::match_same_arms)] match c { '\x00' => E(K::Char('@'), mods | M::CTRL), // '\0' '\x01' => E(K::Char('A'), mods | M::CTRL), diff --git a/src/lib.rs b/src/lib.rs index 10fd4b68a..388696f1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -444,7 +444,7 @@ fn reverse_incremental_search( struct Guard<'m>(&'m tty::Mode); -#[allow(unused_must_use)] +#[expect(unused_must_use)] impl Drop for Guard<'_> { fn drop(&mut self) { let Guard(mode) = *self; @@ -596,7 +596,6 @@ pub struct Editor { /// Default editor with no helper and `DefaultHistory` pub type DefaultEditor = Editor<(), DefaultHistory>; -#[allow(clippy::new_without_default)] impl Editor { /// Create an editor with the default configuration pub fn new() -> Result { diff --git a/src/line_buffer.rs b/src/line_buffer.rs index ac9c68821..3f2dda9a5 100644 --- a/src/line_buffer.rs +++ b/src/line_buffer.rs @@ -1115,7 +1115,7 @@ impl LineBuffer { .map_or_else(|| self.buf.len(), |pos| end + pos); let mut index = start; if dedent { - #[allow(clippy::unnecessary_to_owned)] + #[expect(clippy::unnecessary_to_owned)] for line in self.buf[start..end].to_string().split('\n') { let max = line.len() - line.trim_start().len(); let deleting = min(max, amount); @@ -1131,7 +1131,7 @@ impl LineBuffer { index += line.len() + 1 - deleting; } } else { - #[allow(clippy::unnecessary_to_owned)] + #[expect(clippy::unnecessary_to_owned)] for line in self.buf[start..end].to_string().split('\n') { for off in (0..amount).step_by(INDENT.len()) { self.insert_str(index, &INDENT[..min(amount - off, INDENT.len())], cl); diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 62484f042..d5a0c7cf9 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -46,7 +46,6 @@ pub trait Renderer { fn move_cursor(&mut self, old: Position, new: Position) -> Result<()>; /// Display `prompt`, line and cursor in terminal output - #[allow(clippy::too_many_arguments)] fn refresh_line( &mut self, prompt: &str, diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 08d754810..2800af617 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -41,7 +41,6 @@ const BRACKETED_PASTE_OFF: &str = "\x1b[?2004l"; nix::ioctl_read_bad!(win_size, libc::TIOCGWINSZ, libc::winsize); -#[allow(clippy::useless_conversion)] fn get_win_size(fd: RawFd) -> (usize, usize) { use std::mem::zeroed; @@ -158,7 +157,7 @@ impl Read for TtyIn { return Err(error); } } else { - #[allow(clippy::cast_sign_loss)] + #[expect(clippy::cast_sign_loss)] return Ok(res as usize); } } @@ -377,7 +376,7 @@ impl PosixRawReader { } /// Handle \E[ escape sequences - #[allow(clippy::cognitive_complexity)] + #[expect(clippy::cognitive_complexity)] fn extended_escape(&mut self, seq2: char) -> Result { let seq3 = self.next_char()?; if seq3 == '~' { @@ -1328,7 +1327,6 @@ impl Term for PosixTerminal { ) }; let unsupported = is_unsupported_term(); - #[allow(unused_variables)] let sigwinch = if !unsupported && is_in_a_tty && is_out_a_tty { Some(SigWinCh::install_sigwinch_handler()?) } else { @@ -1471,7 +1469,7 @@ impl Term for PosixTerminal { } } -#[allow(unused_must_use)] +#[expect(unused_must_use)] impl Drop for PosixTerminal { fn drop(&mut self) { if self.close_on_drop { diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 7574403f0..02cf69a4b 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -1,5 +1,5 @@ //! Windows specific definitions -#![allow(clippy::try_err)] // suggested fix does not work (cannot infer...) +#![expect(clippy::try_err)] // suggested fix does not work (cannot infer...) use std::fs::OpenOptions; use std::io; From 93f963916cef8eee174d7886d32508dbdb683c9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 08:09:08 +0000 Subject: [PATCH 03/10] Update unicode-width requirement from 0.1.13 to 0.2.0 Updates the requirements on [unicode-width](https://github.com/unicode-rs/unicode-width) to permit the latest version. - [Commits](https://github.com/unicode-rs/unicode-width/commits) --- updated-dependencies: - dependency-name: unicode-width dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 92b4cd953..97734f3d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ fd-lock = { version = "4.0.0", optional = true } rusqlite = { version = "0.32.0", optional = true, default-features = false, features = ["bundled", "backup"] } libc = "0.2.155" log = "0.4.22" -unicode-width = "0.1.13" +unicode-width = "0.2.0" unicode-segmentation = "1.0" memchr = "2.7" # For custom bindings From ccf416ee19e69cb40157275ad73d1fdff7e6deb9 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 29 Sep 2024 11:26:13 +0200 Subject: [PATCH 04/10] Fix clippy warning --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 388696f1f..a40db34db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -300,7 +300,7 @@ fn page_completions( let num_cols = cols / max_width; let mut pause_row = s.out.get_rows() - 1; - let num_rows = (candidates.len() + num_cols - 1) / num_cols; + let num_rows = candidates.len().div_ceil(num_cols); let mut ab = String::new(); for row in 0..num_rows { if row == pause_row { From 525bdfd14f1042817519e499480d2bdf0e933009 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 6 Oct 2024 10:10:46 +0200 Subject: [PATCH 05/10] Impl Candidate for AsRef --- src/completion.rs | 53 +++++++++++++++++------------------------------ 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index 2d1971f01..b39e927d5 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -14,44 +14,13 @@ pub trait Candidate { fn replacement(&self) -> &str; } -impl Candidate for String { +impl> Candidate for T { fn display(&self) -> &str { - self.as_str() + self.as_ref() } fn replacement(&self) -> &str { - self.as_str() - } -} - -/// #[deprecated = "Unusable"] -impl Candidate for str { - fn display(&self) -> &str { - self - } - - fn replacement(&self) -> &str { - self - } -} - -impl Candidate for &'_ str { - fn display(&self) -> &str { - self - } - - fn replacement(&self) -> &str { - self - } -} - -impl Candidate for Rc { - fn display(&self) -> &str { - self - } - - fn replacement(&self) -> &str { - self + self.as_ref() } } @@ -647,4 +616,20 @@ mod tests { pub fn normalize() { assert_eq!(super::normalize("Windows"), "windows") } + + #[test] + pub fn candidate_impls() { + struct StrCmp; + impl super::Completer for StrCmp { + type Candidate = &'static str; + } + struct RcCmp; + impl super::Completer for RcCmp { + type Candidate = std::rc::Rc; + } + struct ArcCmp; + impl super::Completer for ArcCmp { + type Candidate = std::sync::Arc; + } + } } From ed97271c89505c0a7b3fcf7188cf06e3da60d1bd Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 7 Oct 2024 09:14:02 +0200 Subject: [PATCH 06/10] MSRV --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 47d4e1b79..c08a735f2 100644 --- a/README.md +++ b/README.md @@ -252,3 +252,8 @@ literal newline to be added to the input buffer. The way to achieve multi-line editing is to implement the `Validator` trait. + +## Minimum supported Rust version (MSRV) + +Latest stable Rust version at the time of release. It might compile with older versions. + From c34ef39bb962f7f826cebdcb1b12565868d56288 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 7 Oct 2024 14:28:29 +0200 Subject: [PATCH 07/10] Fix Highlighter::highlight_char Introduce `CmdKind` parameter to make the distinction between edits vs cursor moves vs final refresh --- examples/example.rs | 6 ++--- examples/read_password.rs | 10 ++++--- rustyline-derive/src/lib.rs | 4 +-- src/command.rs | 7 +++-- src/edit.rs | 52 +++++++++++++++++-------------------- src/highlight.rs | 26 +++++++++++++------ src/keymap.rs | 3 ++- src/lib.rs | 6 ++--- src/tty/unix.rs | 2 +- 9 files changed, 62 insertions(+), 54 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index cc3e2a0cc..d258064c6 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::{CmdKind, Highlighter, MatchingBracketHighlighter}; use rustyline::hint::HistoryHinter; use rustyline::validate::MatchingBracketValidator; use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, KeyEvent}; @@ -41,8 +41,8 @@ impl Highlighter for MyHelper { self.highlighter.highlight(line, pos) } - fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { - self.highlighter.highlight_char(line, pos, forced) + fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool { + self.highlighter.highlight_char(line, pos, kind) } } diff --git a/examples/read_password.rs b/examples/read_password.rs index d669d511b..d93418ae2 100644 --- a/examples/read_password.rs +++ b/examples/read_password.rs @@ -1,7 +1,7 @@ use std::borrow::Cow::{self, Borrowed, Owned}; use rustyline::config::Configurer; -use rustyline::highlight::Highlighter; +use rustyline::highlight::{CmdKind, Highlighter}; use rustyline::{ColorMode, Editor, Result}; use rustyline::{Completer, Helper, Hinter, Validator}; @@ -20,12 +20,16 @@ impl Highlighter for MaskingHighlighter { } } - fn highlight_char(&self, _line: &str, _pos: usize, _forced: bool) -> bool { - self.masking + fn highlight_char(&self, _line: &str, _pos: usize, kind: CmdKind) -> bool { + match kind { + CmdKind::MoveCursor => false, + _ => self.masking, + } } } fn main() -> Result<()> { + env_logger::init(); println!("This is just a hack. Reading passwords securely requires more than that."); let h = MaskingHighlighter { masking: false }; let mut rl = Editor::new()?; diff --git a/rustyline-derive/src/lib.rs b/rustyline-derive/src/lib.rs index c25e6165b..e23908c84 100644 --- a/rustyline-derive/src/lib.rs +++ b/rustyline-derive/src/lib.rs @@ -126,8 +126,8 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream { ::rustyline::highlight::Highlighter::highlight_candidate(&self.#field_name_or_index, candidate, completion) } - fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { - ::rustyline::highlight::Highlighter::highlight_char(&self.#field_name_or_index, line, pos, forced) + fn highlight_char(&self, line: &str, pos: usize, kind: ::rustyline::highlight::CmdKind) -> bool { + ::rustyline::highlight::Highlighter::highlight_char(&self.#field_name_or_index, line, pos, kind) } } } diff --git a/src/command.rs b/src/command.rs index f0185e2cb..4eb215134 100644 --- a/src/command.rs +++ b/src/command.rs @@ -2,6 +2,7 @@ use crate::complete_hint_line; use crate::config::Config; use crate::edit::State; use crate::error; +use crate::highlight::CmdKind; use crate::history::SearchDirection; use crate::keymap::{Anchor, At, Cmd, Movement, Word}; use crate::keymap::{InputState, Refresher}; @@ -28,9 +29,7 @@ pub fn execute( if s.has_hint() || !s.is_default_prompt() || s.highlight_char { // Force a refresh without hints to leave the previous // line as the user typed it after a newline. - s.forced_refresh = true; - s.refresh_line_with_msg(None)?; - s.forced_refresh = false; + s.refresh_line_with_msg(None, CmdKind::ForcedRefresh)?; } } _ => {} @@ -190,7 +189,7 @@ pub fn execute( } Cmd::Move(Movement::EndOfBuffer) => { // Move to the end of the buffer. - s.edit_move_buffer_end()?; + s.edit_move_buffer_end(CmdKind::MoveCursor)?; } Cmd::DowncaseWord => { // lowercase word after point diff --git a/src/edit.rs b/src/edit.rs index c231e4a42..af75622ec 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -7,7 +7,7 @@ use unicode_width::UnicodeWidthChar; use super::{Context, Helper, Result}; use crate::error::ReadlineError; -use crate::highlight::Highlighter; +use crate::highlight::{CmdKind, Highlighter}; use crate::hint::Hint; use crate::history::SearchDirection; use crate::keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word}; @@ -36,7 +36,6 @@ pub struct State<'out, 'prompt, H: Helper> { 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 - pub forced_refresh: bool, // `true` if line is redraw without hint or highlight_char } enum Info<'m> { @@ -66,7 +65,6 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { ctx, hint: None, highlight_char: false, - forced_refresh: false, } } @@ -122,7 +120,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { ); } - pub fn move_cursor(&mut self) -> Result<()> { + pub fn move_cursor(&mut self, kind: CmdKind) -> Result<()> { // calculate the desired position of the cursor let cursor = self .out @@ -130,7 +128,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { if self.layout.cursor == cursor { return Ok(()); } - if self.highlight_char() { + if self.highlight_char(kind) { let prompt_size = self.prompt_size; self.refresh(self.prompt, prompt_size, true, Info::NoHint)?; } else { @@ -205,10 +203,9 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { } } - fn highlight_char(&mut self) -> bool { + fn highlight_char(&mut self, kind: CmdKind) -> bool { if let Some(highlighter) = self.highlighter() { - let highlight_char = - highlighter.highlight_char(&self.line, self.line.pos(), self.forced_refresh); + let highlight_char = highlighter.highlight_char(&self.line, self.line.pos(), kind); if highlight_char { self.highlight_char = true; true @@ -240,12 +237,12 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { if corrected || self.has_hint() || msg.is_some() { // Force a refresh without hints to leave the previous // line as the user typed it after a newline. - self.refresh_line_with_msg(msg.as_deref())?; + self.refresh_line_with_msg(msg.as_deref(), CmdKind::ForcedRefresh)?; } } ValidationResult::Invalid(ref msg) => { if corrected || self.has_hint() || msg.is_some() { - self.refresh_line_with_msg(msg.as_deref())?; + self.refresh_line_with_msg(msg.as_deref(), CmdKind::Other)?; } } } @@ -266,21 +263,21 @@ impl<'out, 'prompt, H: Helper> Refresher for State<'out, 'prompt, H> { fn refresh_line(&mut self) -> Result<()> { let prompt_size = self.prompt_size; self.hint(); - self.highlight_char(); + self.highlight_char(CmdKind::Other); self.refresh(self.prompt, prompt_size, true, Info::Hint) } - fn refresh_line_with_msg(&mut self, msg: Option<&str>) -> Result<()> { + fn refresh_line_with_msg(&mut self, msg: Option<&str>, kind: CmdKind) -> Result<()> { let prompt_size = self.prompt_size; self.hint = None; - self.highlight_char(); + self.highlight_char(kind); self.refresh(self.prompt, prompt_size, true, Info::Msg(msg)) } fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> { let prompt_size = self.out.calculate_position(prompt, Position::default()); self.hint(); - self.highlight_char(); + self.highlight_char(CmdKind::Other); self.refresh(prompt, prompt_size, false, Info::Hint) } @@ -361,7 +358,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { && width != 0 // Ctrl-V + \t or \n ... && self.layout.cursor.col + width < self.out.get_columns() && (self.hint.is_none() && no_previous_hint) // TODO refresh only current line - && !self.highlight_char() + && !self.highlight_char(CmdKind::Other) { // Avoid a full update of the line in the trivial case. self.layout.cursor.col += width; @@ -454,7 +451,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { /// Move cursor on the left. pub fn edit_move_backward(&mut self, n: RepeatCount) -> Result<()> { if self.line.move_backward(n) { - self.move_cursor() + self.move_cursor(CmdKind::MoveCursor) } else { Ok(()) } @@ -463,7 +460,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { /// Move cursor on the right. pub fn edit_move_forward(&mut self, n: RepeatCount) -> Result<()> { if self.line.move_forward(n) { - self.move_cursor() + self.move_cursor(CmdKind::MoveCursor) } else { Ok(()) } @@ -472,7 +469,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { /// Move cursor to the start of the line. pub fn edit_move_home(&mut self) -> Result<()> { if self.line.move_home() { - self.move_cursor() + self.move_cursor(CmdKind::MoveCursor) } else { Ok(()) } @@ -481,7 +478,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { /// Move cursor to the end of the line. pub fn edit_move_end(&mut self) -> Result<()> { if self.line.move_end() { - self.move_cursor() + self.move_cursor(CmdKind::MoveCursor) } else { Ok(()) } @@ -490,16 +487,16 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { /// Move cursor to the start of the buffer. pub fn edit_move_buffer_start(&mut self) -> Result<()> { if self.line.move_buffer_start() { - self.move_cursor() + self.move_cursor(CmdKind::MoveCursor) } else { Ok(()) } } /// Move cursor to the end of the buffer. - pub fn edit_move_buffer_end(&mut self) -> Result<()> { + pub fn edit_move_buffer_end(&mut self, kind: CmdKind) -> Result<()> { if self.line.move_buffer_end() { - self.move_cursor() + self.move_cursor(kind) } else { Ok(()) } @@ -565,7 +562,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { pub fn edit_move_to_prev_word(&mut self, word_def: Word, n: RepeatCount) -> Result<()> { if self.line.move_to_prev_word(word_def, n) { - self.move_cursor() + self.move_cursor(CmdKind::MoveCursor) } else { Ok(()) } @@ -573,7 +570,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { pub fn edit_move_to_next_word(&mut self, at: At, word_def: Word, n: RepeatCount) -> Result<()> { if self.line.move_to_next_word(at, word_def, n) { - self.move_cursor() + self.move_cursor(CmdKind::MoveCursor) } else { Ok(()) } @@ -582,7 +579,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { /// Moves the cursor to the same column in the line above pub fn edit_move_line_up(&mut self, n: RepeatCount) -> Result { if self.line.move_to_line_up(n) { - self.move_cursor()?; + self.move_cursor(CmdKind::MoveCursor)?; Ok(true) } else { Ok(false) @@ -592,7 +589,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { /// Moves the cursor to the same column in the line above pub fn edit_move_line_down(&mut self, n: RepeatCount) -> Result { if self.line.move_to_line_down(n) { - self.move_cursor()?; + self.move_cursor(CmdKind::MoveCursor)?; Ok(true) } else { Ok(false) @@ -601,7 +598,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { pub fn edit_move_to(&mut self, cs: CharSearch, n: RepeatCount) -> Result<()> { if self.line.move_to(cs, n) { - self.move_cursor() + self.move_cursor(CmdKind::MoveCursor) } else { Ok(()) } @@ -765,7 +762,6 @@ pub fn init_state<'out, H: Helper>( ctx: Context::new(history), hint: Some(Box::new("hint".to_owned())), highlight_char: false, - forced_refresh: false, } } diff --git a/src/highlight.rs b/src/highlight.rs index a21459e0a..ffee71e76 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -4,6 +4,18 @@ use crate::config::CompletionType; use std::borrow::Cow::{self, Borrowed, Owned}; use std::cell::Cell; +/// Describe which kind of action has been triggering the call to `Highlighter`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum CmdKind { + /// Cursor moved + MoveCursor, + /// Other action + Other, + /// Forced / final refresh (no auto-suggestion / hint, no matching bracket + /// highlighted, ...) + ForcedRefresh, +} + /// Syntax highlighter with [ANSI color](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters). /// /// Currently, the highlighted version *must* have the same display width as @@ -47,13 +59,11 @@ pub trait Highlighter { } /// Tells if `line` needs to be highlighted when a specific char is typed or /// when cursor is moved under a specific char. - /// `forced` flag is `true` mainly when user presses Enter (i.e. transient - /// vs final highlight). /// /// Used to optimize refresh when a character is inserted or the cursor is /// moved. - fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { - let _ = (line, pos, forced); + fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool { + let _ = (line, pos, kind); false } } @@ -85,8 +95,8 @@ impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H { (**self).highlight_candidate(candidate, completion) } - fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { - (**self).highlight_char(line, pos, forced) + fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool { + (**self).highlight_char(line, pos, kind) } } @@ -124,8 +134,8 @@ impl Highlighter for MatchingBracketHighlighter { Borrowed(line) } - fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { - if forced { + fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool { + if kind == CmdKind::ForcedRefresh { self.bracket.set(None); return false; } diff --git a/src/keymap.rs b/src/keymap.rs index 8564c163a..9bef72798 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -2,6 +2,7 @@ use log::debug; use super::Result; +use crate::highlight::CmdKind; use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M}; use crate::tty::{self, RawReader, Term, Terminal}; use crate::{Config, EditMode}; @@ -374,7 +375,7 @@ pub trait Refresher { /// cursor position, and number of columns of the terminal. fn refresh_line(&mut self) -> Result<()>; /// Same as [`refresh_line`] with a specific message instead of hint - fn refresh_line_with_msg(&mut self, msg: Option<&str>) -> Result<()>; + fn refresh_line_with_msg(&mut self, msg: Option<&str>, kind: CmdKind) -> Result<()>; /// Same as `refresh_line` but with a dynamic prompt. fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()>; /// Vi only, switch to insert mode. diff --git a/src/lib.rs b/src/lib.rs index a40db34db..41675730a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ use crate::completion::{longest_common_prefix, Candidate, Completer}; pub use crate::config::{Behavior, ColorMode, CompletionType, Config, EditMode, HistoryDuplicates}; use crate::edit::State; use crate::error::ReadlineError; -use crate::highlight::Highlighter; +use crate::highlight::{CmdKind, Highlighter}; use crate::hint::Hinter; use crate::history::{DefaultHistory, History, SearchDirection}; pub use crate::keymap::{Anchor, At, CharSearch, Cmd, InputMode, Movement, RepeatCount, Word}; @@ -790,9 +790,7 @@ impl Editor { // Move to end, in case cursor was in the middle of the line, so that // next thing application prints goes after the input - s.forced_refresh = true; - s.edit_move_buffer_end()?; - s.forced_refresh = false; + s.edit_move_buffer_end(CmdKind::ForcedRefresh)?; if cfg!(windows) { let _ = original_mode; // silent warning diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 2800af617..054b4f118 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -882,7 +882,7 @@ impl Receiver for Utf8 { self.valid = true; } - /// Called when an invalid_sequence is detected + /// Called when an invalid sequence is detected fn invalid_sequence(&mut self) { self.c = None; self.valid = false; From b6473661960224561bc47a40aa328d624b57d560 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 7 Oct 2024 15:04:04 +0200 Subject: [PATCH 08/10] Remove impl for & Trying to fix #425 --- src/completion.rs | 16 ------------- src/highlight.rs | 30 ------------------------ src/hint.rs | 8 ------- src/lib.rs | 2 -- src/tty/mod.rs | 60 ----------------------------------------------- src/validate.rs | 10 -------- 6 files changed, 126 deletions(-) diff --git a/src/completion.rs b/src/completion.rs index b39e927d5..e466d977b 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -82,22 +82,6 @@ impl Completer for () { } } -impl<'c, C: ?Sized + Completer> Completer for &'c C { - 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)*) => { $( diff --git a/src/highlight.rs b/src/highlight.rs index ffee71e76..6cc718a07 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -70,36 +70,6 @@ pub trait Highlighter { impl Highlighter for () {} -impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H { - fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { - (**self).highlight(line, pos) - } - - fn highlight_prompt<'b, 's: 'b, 'p: 'b>( - &'s 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> { - (**self).highlight_hint(hint) - } - - fn highlight_candidate<'c>( - &self, - candidate: &'c str, - completion: CompletionType, - ) -> Cow<'c, str> { - (**self).highlight_candidate(candidate, completion) - } - - fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool { - (**self).highlight_char(line, pos, kind) - } -} - // TODO versus https://python-prompt-toolkit.readthedocs.io/en/master/pages/reference.html?highlight=HighlightMatchingBracketProcessor#prompt_toolkit.layout.processors.HighlightMatchingBracketProcessor /// Highlight matching bracket when typed or cursor moved on. diff --git a/src/hint.rs b/src/hint.rs index efff9357c..5543f2e8c 100644 --- a/src/hint.rs +++ b/src/hint.rs @@ -40,14 +40,6 @@ impl Hinter for () { type Hint = String; } -impl<'r, H: ?Sized + Hinter> Hinter for &'r H { - type Hint = H::Hint; - - fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { - (**self).hint(line, pos, ctx) - } -} - /// Add suggestion based on previous history entries matching current user /// input. #[derive(Default)] diff --git a/src/lib.rs b/src/lib.rs index 41675730a..c48757e42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -550,8 +550,6 @@ where impl Helper for () {} -impl<'h, H: ?Sized + Helper> Helper for &'h H {} - /// Completion/suggestion context pub struct Context<'h> { history: &'h dyn History, diff --git a/src/tty/mod.rs b/src/tty/mod.rs index d5a0c7cf9..22dcb8a2b 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -118,66 +118,6 @@ pub trait Renderer { fn move_cursor_at_leftmost(&mut self, rdr: &mut Self::Reader) -> Result<()>; } -impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R { - type Reader = R::Reader; - - fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> { - (**self).move_cursor(old, new) - } - - fn refresh_line( - &mut self, - prompt: &str, - line: &LineBuffer, - hint: Option<&str>, - old_layout: &Layout, - new_layout: &Layout, - highlighter: Option<&dyn Highlighter>, - ) -> Result<()> { - (**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 write_and_flush(&mut self, buf: &str) -> Result<()> { - (**self).write_and_flush(buf) - } - - fn beep(&mut self) -> Result<()> { - (**self).beep() - } - - fn clear_screen(&mut self) -> Result<()> { - (**self).clear_screen() - } - - fn clear_rows(&mut self, layout: &Layout) -> Result<()> { - (**self).clear_rows(layout) - } - - fn update_size(&mut self) { - (**self).update_size(); - } - - fn get_columns(&self) -> usize { - (**self).get_columns() - } - - fn get_rows(&self) -> usize { - (**self).get_rows() - } - - fn colors_enabled(&self) -> bool { - (**self).colors_enabled() - } - - fn move_cursor_at_leftmost(&mut self, rdr: &mut R::Reader) -> Result<()> { - (**self).move_cursor_at_leftmost(rdr) - } -} - // ignore ANSI escape sequence fn width(s: &str, esc_seq: &mut u8) -> usize { if *esc_seq == 1 { diff --git a/src/validate.rs b/src/validate.rs index 12b7f3314..f1475cd47 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -86,16 +86,6 @@ pub trait Validator { impl Validator for () {} -impl<'v, V: ?Sized + Validator> Validator for &'v V { - fn validate(&self, ctx: &mut ValidationContext) -> Result { - (**self).validate(ctx) - } - - fn validate_while_typing(&self) -> bool { - (**self).validate_while_typing() - } -} - /// Simple matching bracket validator. #[derive(Default)] pub struct MatchingBracketValidator { From ad775d6ab1fce5d27862b680e88843933e04b365 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 7 Oct 2024 15:15:40 +0200 Subject: [PATCH 09/10] Improve doc of History::add --- src/history.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/history.rs b/src/history.rs index 0dc289db7..f3237d7aa 100644 --- a/src/history.rs +++ b/src/history.rs @@ -69,8 +69,14 @@ pub trait History { // reedline: fn append(&mut self, entry: &str); /// Add a new entry in the history. + /// + /// Return false if the `line` has been ignored (blank line / duplicate / + /// ...). fn add(&mut self, line: &str) -> Result; /// Add a new entry in the history. + /// + /// Return false if the `line` has been ignored (blank line / duplicate / + /// ...). fn add_owned(&mut self, line: String) -> Result; // TODO check AsRef + Into vs object safe /// Return the number of entries in the history. From c6a08676f898be46b15ec7678b454279b5e5f70a Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 7 Oct 2024 15:42:35 +0200 Subject: [PATCH 10/10] Fix broken link in README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index c08a735f2..d6bf9c684 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ For all modes: [Readline vi Editing Mode Cheat Sheet](http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf) -[Terminal codes (ANSI/VT100)](http://wiki.bash-hackers.org/scripting/terminalcodes) +[ANSI escape code](https://en.wikipedia.org/wiki/ANSI_escape_code) ## Wine @@ -256,4 +256,3 @@ trait. ## Minimum supported Rust version (MSRV) Latest stable Rust version at the time of release. It might compile with older versions. -