From 53b74d9a216bbc03addef8dc215d8ab7f444db09 Mon Sep 17 00:00:00 2001 From: gwenn Date: Thu, 15 Aug 2024 12:54:30 +0200 Subject: [PATCH 01/15] Introduce `Style` and `StyledBlock` traits And their `ansi-str` implementations --- Cargo.toml | 3 +++ src/highlight.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 92b4cd953..ce8694399 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,9 @@ maintenance = { status = "actively-developed" } members = ["rustyline-derive"] [dependencies] +# For highlighting +# TODO make it optional +ansi-str = { version = "0.8.0", optional = false } bitflags = "2.6" cfg-if = "1.0" # For file completion diff --git a/src/highlight.rs b/src/highlight.rs index d5c0e8967..379a087df 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -3,6 +3,46 @@ use crate::config::CompletionType; use std::borrow::Cow::{self, Borrowed, Owned}; use std::cell::Cell; +use std::fmt::Display; + +/// ANSI style +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; +} + +/// Styled text +pub trait StyledBlock { + /// Style impl + type Style: Style; + /// Raw text to be styled + fn text(&self) -> &str; + /// `Style` to be applied on `text` + fn style(&self) -> &Self::Style; +} + +impl Style for ansi_str::Style { + fn start(&self) -> impl Display { + self.start() + } + + fn end(&self) -> impl Display { + self.end() + } +} +impl StyledBlock for ansi_str::AnsiBlock<'_> { + type Style = ansi_str::Style; + + fn text(&self) -> &str { + self.text() + } + + fn style(&self) -> &Self::Style { + self.style() + } +} /// 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 @@ -278,4 +318,15 @@ mod tests { assert!(is_open_bracket(b'(')); assert!(is_close_bracket(b')')); } + + #[test] + pub fn styled_text() { + use ansi_str::get_blocks; + + let mut blocks = get_blocks("\x1b[1;32mHello \x1b[3mworld\x1b[23m!\x1b[0m"); + assert_eq!(blocks.next(), get_blocks("\x1b[1;32mHello ").next()); + assert_eq!(blocks.next(), get_blocks("\x1b[1;32m\x1b[3mworld").next()); + assert_eq!(blocks.next(), get_blocks("\x1b[1;32m!").next()); + assert!(blocks.next().is_none()) + } } From b82d8612e96c69a6470984f13be783378aa21378 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 17 Aug 2024 10:35:54 +0200 Subject: [PATCH 02/15] Introduce StyledBlocks trait --- Cargo.toml | 17 ++++++++++---- src/highlight.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce8694399..1d4af754b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,8 @@ maintenance = { status = "actively-developed" } members = ["rustyline-derive"] [dependencies] -# For highlighting -# TODO make it optional -ansi-str = { version = "0.8.0", optional = false } +# For convenience, you can highlight the whole input buffer, ansi-str helps to split +ansi-str = { version = "0.8.0", optional = true } bitflags = "2.6" cfg-if = "1.0" # For file completion @@ -72,6 +71,9 @@ with-file-history = ["fd-lock"] with-sqlite-history = ["rusqlite"] with-fuzzy = ["skim"] case_insensitive_history_search = ["regex"] +# For continuation prompt, indentation, scrolling +# TODO make ansi-str optional +split-highlight = ["ansi-str"] [[example]] name = "custom_key_bindings" @@ -99,7 +101,14 @@ name = "sqlite_history" required-features = ["with-sqlite-history"] [package.metadata.docs.rs] -features = ["custom-bindings", "derive", "with-dirs", "with-file-history", "with-fuzzy"] +features = [ + "custom-bindings", + "derive", + "with-dirs", + "with-file-history", + "with-fuzzy", + "split-highlight", +] all-features = false no-default-features = true default-target = "x86_64-unknown-linux-gnu" diff --git a/src/highlight.rs b/src/highlight.rs index 379a087df..af701228b 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -3,9 +3,12 @@ use crate::config::CompletionType; use std::borrow::Cow::{self, Borrowed, Owned}; 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 { /// Produce a ansi sequences which sets the graphic mode fn start(&self) -> impl Display; @@ -14,15 +17,23 @@ pub trait Style { } /// Styled text +#[cfg(feature = "split-highlight")] +#[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] pub trait StyledBlock { /// Style impl - type Style: Style; + type Style: Style + where + Self: Sized; /// Raw text to be styled fn text(&self) -> &str; /// `Style` to be applied on `text` - fn style(&self) -> &Self::Style; + fn style(&self) -> &Self::Style + where + Self: Sized; } +#[cfg(feature = "ansi-str")] +#[cfg_attr(docsrs, doc(cfg(feature = "ansi-str")))] impl Style for ansi_str::Style { fn start(&self) -> impl Display { self.start() @@ -32,6 +43,8 @@ impl Style for ansi_str::Style { self.end() } } +#[cfg(feature = "ansi-str")] +#[cfg_attr(docsrs, doc(cfg(feature = "ansi-str")))] impl StyledBlock for ansi_str::AnsiBlock<'_> { type Style = ansi_str::Style; @@ -44,6 +57,31 @@ impl StyledBlock for ansi_str::AnsiBlock<'_> { } } +/// Ordered list of styled block +#[cfg(feature = "split-highlight")] +#[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] +pub trait StyledBlocks { + /// Styled block + type StyledBlock: StyledBlock + where + Self: Sized; + + /// FIXME maybe we can use Iterator trait directly ? + fn next(&mut self) -> Option + where + Self: Sized; +} + +#[cfg(feature = "ansi-str")] +#[cfg_attr(docsrs, doc(cfg(feature = "ansi-str")))] +impl<'l> StyledBlocks for ansi_str::AnsiBlockIter<'l> { + type StyledBlock = ansi_str::AnsiBlock<'l>; + + fn next(&mut self) -> Option { + Iterator::next(self) + } +} + /// 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). @@ -54,12 +92,26 @@ pub trait Highlighter { /// Takes the currently edited `line` with the cursor `pos`ition and /// returns the highlighted version (with ANSI color). /// + /// /// For example, you can implement /// [blink-matching-paren](https://www.gnu.org/software/bash/manual/html_node/Readline-Init-File-Syntax.html). + // TODO make it optional when split-highlight is activated fn highlight<'l>(&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>(&self, line: &'l str, pos: usize) -> &dyn StyledBlocks { + let _s = self.highlight(line, pos); + // it doesn't seem possible to return an AnsiBlockIter directly + //StyleBlocks::Whole(s) + todo!() + } + /// Takes the `prompt` and /// returns the highlighted version (with ANSI color). fn highlight_prompt<'b, 's: 'b, 'p: 'b>( @@ -103,6 +155,7 @@ pub trait Highlighter { impl Highlighter for () {} impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H { + #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { (**self).highlight(line, pos) } @@ -151,6 +204,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> { if line.len() <= 1 { return Borrowed(line); @@ -320,6 +374,7 @@ mod tests { } #[test] + #[cfg(feature = "ansi-str")] pub fn styled_text() { use ansi_str::get_blocks; From 35cbf3396aa640a54bcef48e639a255204b17be1 Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 19 Aug 2024 19:10:56 +0200 Subject: [PATCH 03/15] Add anstyle implementations Because ansi-str Style is immutable --- Cargo.toml | 2 ++ src/highlight.rs | 47 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1d4af754b..a9847c984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ members = ["rustyline-derive"] [dependencies] # For convenience, you can highlight the whole input buffer, ansi-str helps to split ansi-str = { version = "0.8.0", optional = true } +anstyle = { version = "1.0.8", optional = true } bitflags = "2.6" cfg-if = "1.0" # For file completion @@ -108,6 +109,7 @@ features = [ "with-file-history", "with-fuzzy", "split-highlight", + "anstyle", ] all-features = false no-default-features = true diff --git a/src/highlight.rs b/src/highlight.rs index af701228b..0da8b8177 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -15,6 +15,28 @@ pub trait Style { /// Produce a ansi sequences which ends the graphic mode fn end(&self) -> impl Display; } +#[cfg(feature = "ansi-str")] +#[cfg_attr(docsrs, doc(cfg(feature = "ansi-str")))] +impl Style for ansi_str::Style { + fn start(&self) -> impl Display { + self.start() + } + + fn end(&self) -> impl Display { + self.end() + } +} +#[cfg(feature = "anstyle")] +#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))] +impl Style for anstyle::Style { + fn start(&self) -> impl Display { + self.render() + } + + fn end(&self) -> impl Display { + self.render_reset() + } +} /// Styled text #[cfg(feature = "split-highlight")] @@ -31,18 +53,6 @@ pub trait StyledBlock { where Self: Sized; } - -#[cfg(feature = "ansi-str")] -#[cfg_attr(docsrs, doc(cfg(feature = "ansi-str")))] -impl Style for ansi_str::Style { - fn start(&self) -> impl Display { - self.start() - } - - fn end(&self) -> impl Display { - self.end() - } -} #[cfg(feature = "ansi-str")] #[cfg_attr(docsrs, doc(cfg(feature = "ansi-str")))] impl StyledBlock for ansi_str::AnsiBlock<'_> { @@ -56,6 +66,19 @@ impl StyledBlock for ansi_str::AnsiBlock<'_> { self.style() } } +#[cfg(feature = "anstyle")] +#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))] +impl StyledBlock for (anstyle::Style, &str) { + type Style = anstyle::Style; + + fn text(&self) -> &str { + self.1 + } + + fn style(&self) -> &Self::Style { + &self.0 + } +} /// Ordered list of styled block #[cfg(feature = "split-highlight")] From 6b34d32cb43561856d538bc729ef8cfd0d7de5aa Mon Sep 17 00:00:00 2001 From: gwenn Date: Mon, 19 Aug 2024 19:44:01 +0200 Subject: [PATCH 04/15] `IntoIterator<(Style, &str)>` seems natural As a return type for `highlight_line` * `Cow<'_, str>` -> `ansi_str::AnsiBlockIter<'_>` for compatibility with existing code * `Vec<(anstyle:Style, &str)>` for a default / test implementation But may be difficult to handle on our side or it's just me... --- src/highlight.rs | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/highlight.rs b/src/highlight.rs index 0da8b8177..7ba118f02 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -80,28 +80,20 @@ impl StyledBlock for (anstyle::Style, &str) { } } -/// Ordered list of styled block -#[cfg(feature = "split-highlight")] -#[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] -pub trait StyledBlocks { - /// Styled block - type StyledBlock: StyledBlock - where - Self: Sized; - - /// FIXME maybe we can use Iterator trait directly ? - fn next(&mut self) -> Option - where - Self: Sized; -} +struct Ansi<'s>(Cow<'s, str>); +/// Ordered list of styled block #[cfg(feature = "ansi-str")] #[cfg_attr(docsrs, doc(cfg(feature = "ansi-str")))] -impl<'l> StyledBlocks for ansi_str::AnsiBlockIter<'l> { - type StyledBlock = ansi_str::AnsiBlock<'l>; - - fn next(&mut self) -> Option { - Iterator::next(self) +impl<'s> IntoIterator for Ansi<'s> { + type IntoIter = ansi_str::AnsiBlockIter<'s>; + type Item = ansi_str::AnsiBlock<'s>; + + fn into_iter(self) -> Self::IntoIter { + match self { + Ansi(Cow::Borrowed(s)) => ansi_str::get_blocks(s), + Ansi(Cow::Owned(s)) => ansi_str::get_blocks(&s), // self_cell ? + } } } @@ -128,7 +120,7 @@ pub trait Highlighter { /// returns the styled blocks. #[cfg(feature = "split-highlight")] #[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] - fn highlight_line<'l>(&self, line: &'l str, pos: usize) -> &dyn StyledBlocks { + fn highlight_line<'l>(&self, line: &'l str, pos: usize) -> dyn IntoIterator { let _s = self.highlight(line, pos); // it doesn't seem possible to return an AnsiBlockIter directly //StyleBlocks::Whole(s) From 92bcae0ae39839f0045dbc4e9f3e0971f3857d8b Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 24 Aug 2024 09:56:16 +0200 Subject: [PATCH 05/15] Make Highlighter::highlight optional when "split-highlight" feature is activated but not "ansi-str" feature. --- src/highlight.rs | 57 +++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/highlight.rs b/src/highlight.rs index 7ba118f02..4c2f2ab7e 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -80,23 +80,6 @@ impl StyledBlock for (anstyle::Style, &str) { } } -struct Ansi<'s>(Cow<'s, str>); - -/// Ordered list of styled block -#[cfg(feature = "ansi-str")] -#[cfg_attr(docsrs, doc(cfg(feature = "ansi-str")))] -impl<'s> IntoIterator for Ansi<'s> { - type IntoIter = ansi_str::AnsiBlockIter<'s>; - type Item = ansi_str::AnsiBlock<'s>; - - fn into_iter(self) -> Self::IntoIter { - match self { - Ansi(Cow::Borrowed(s)) => ansi_str::get_blocks(s), - Ansi(Cow::Owned(s)) => ansi_str::get_blocks(&s), // self_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). @@ -104,13 +87,22 @@ impl<'s> IntoIterator for Ansi<'s> { /// Currently, the highlighted version *must* have the same display width as /// the original input. pub trait Highlighter { + /// ANSI Style + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + type Style: Style + Default + where + Self: Sized; /// Takes the currently edited `line` with the cursor `pos`ition and /// returns the highlighted version (with ANSI color). /// /// /// For example, you can implement /// [blink-matching-paren](https://www.gnu.org/software/bash/manual/html_node/Readline-Init-File-Syntax.html). - // TODO make it optional when split-highlight is activated + #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] + #[cfg_attr( + docsrs, + doc(cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))) + )] fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { let _ = pos; Borrowed(line) @@ -118,13 +110,19 @@ 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")))] - fn highlight_line<'l>(&self, line: &'l str, pos: usize) -> dyn IntoIterator { - let _s = self.highlight(line, pos); - // it doesn't seem possible to return an AnsiBlockIter directly - //StyleBlocks::Whole(s) - todo!() + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + #[cfg_attr( + docsrs, + doc(cfg(all(feature = "split-highlight", not(feature = "ansi-str")))) + )] + // TODO try to return an `IntoIterator` instead of a `Vec` + fn highlight_line<'l>(&self, line: &'l str, pos: usize) -> Vec<(Self::Style, &'l str)> + where + Self: Sized, + { + let _ = pos; + // TODO default style vs empty vec to indicated no highlighting + vec![(Self::Style::default(), line)] } /// Takes the `prompt` and @@ -167,6 +165,7 @@ pub trait Highlighter { } } +#[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] // FIXME impl Highlighter for () {} impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H { @@ -175,6 +174,14 @@ impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H { (**self).highlight(line, pos) } + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + fn highlight_line<'l>(&self, line: &'l str, pos: usize) -> Vec<(Self::Style, &'l str)> + where + Self: Sized, + { + (**self).highlight_line(line, pos) + } + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( &'s self, prompt: &'p str, From 271052d46165d529c59d1c64d58665a37173f308 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 24 Aug 2024 10:32:00 +0200 Subject: [PATCH 06/15] Make ansi-str optional --- Cargo.toml | 3 +-- src/highlight.rs | 29 +++++++++++++++++++++++------ src/lib.rs | 2 +- src/test/mod.rs | 5 ++++- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a9847c984..1d5851e5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,8 +73,7 @@ with-sqlite-history = ["rusqlite"] with-fuzzy = ["skim"] case_insensitive_history_search = ["regex"] # For continuation prompt, indentation, scrolling -# TODO make ansi-str optional -split-highlight = ["ansi-str"] +split-highlight = [] [[example]] name = "custom_key_bindings" diff --git a/src/highlight.rs b/src/highlight.rs index 4c2f2ab7e..228d72976 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -1,7 +1,7 @@ //! Syntax highlighting use crate::config::CompletionType; -use std::borrow::Cow::{self, Borrowed, Owned}; +use std::borrow::Cow::{self, Borrowed}; use std::cell::Cell; #[cfg(feature = "split-highlight")] use std::fmt::Display; @@ -15,6 +15,18 @@ pub trait Style { /// 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 { + "" + } + + fn end(&self) -> impl Display { + "" + } +} + #[cfg(feature = "ansi-str")] #[cfg_attr(docsrs, doc(cfg(feature = "ansi-str")))] impl Style for ansi_str::Style { @@ -121,7 +133,7 @@ pub trait Highlighter { Self: Sized, { let _ = pos; - // TODO default style vs empty vec to indicated no highlighting + // TODO default style vs empty vec to indicate no highlighting vec![(Self::Style::default(), line)] } @@ -165,10 +177,15 @@ pub trait Highlighter { } } -#[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] // FIXME -impl Highlighter for () {} +impl Highlighter for () { + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + type Style = (); +} + +impl<'r, H: Highlighter> Highlighter for &'r H { + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + type Style = H::Style; -impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H { #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { (**self).highlight(line, pos) @@ -225,8 +242,8 @@ impl MatchingBracketHighlighter { } } +#[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] 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> { if line.len() <= 1 { return Borrowed(line); diff --git a/src/lib.rs b/src/lib.rs index 10fd4b68a..b9e2f0fb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -550,7 +550,7 @@ where impl Helper for () {} -impl<'h, H: ?Sized + Helper> Helper for &'h H {} +impl<'h, H: Helper> Helper for &'h H {} /// Completion/suggestion context pub struct Context<'h> { diff --git a/src/test/mod.rs b/src/test/mod.rs index 14ff9052b..18d45244e 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -56,7 +56,10 @@ impl Hinter for SimpleCompleter { } impl Helper for SimpleCompleter {} -impl Highlighter for SimpleCompleter {} +impl Highlighter for SimpleCompleter { + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + type Style = (); +} impl Validator for SimpleCompleter {} #[test] From cb173ffbc083f08aed1bad58c3bcd979a96d3bb6 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 24 Aug 2024 12:07:06 +0200 Subject: [PATCH 07/15] Fix unix impl --- .github/workflows/rust.yml | 5 +++++ src/edit.rs | 7 +++---- src/highlight.rs | 38 +++++++++++++++++++++++++++++++++----- src/tty/mod.rs | 8 ++++---- src/tty/test.rs | 4 ++-- src/tty/unix.rs | 25 +++++++++++++++++++------ src/tty/windows.rs | 4 ++-- 7 files changed, 68 insertions(+), 23 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 169b31f9b..bf2e10d14 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -48,6 +48,11 @@ 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' direct-minimal-versions: name: Test min versions diff --git a/src/edit.rs b/src/edit.rs index c231e4a42..fa02a4249 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -7,7 +7,6 @@ use unicode_width::UnicodeWidthChar; use super::{Context, Helper, Result}; use crate::error::ReadlineError; -use crate::highlight::Highlighter; use crate::hint::Hint; use crate::history::SearchDirection; use crate::keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word}; @@ -70,9 +69,9 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { } } - pub fn highlighter(&self) -> Option<&dyn Highlighter> { + pub fn highlighter(&self) -> Option<&H> { if self.out.colors_enabled() { - self.helper.map(|h| h as &dyn Highlighter) + self.helper } else { None } @@ -169,7 +168,7 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> { Info::Msg(msg) => msg, }; let highlighter = if self.out.colors_enabled() { - self.helper.map(|h| h as &dyn Highlighter) + self.helper } else { None }; diff --git a/src/highlight.rs b/src/highlight.rs index 228d72976..ef8740893 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -101,7 +101,7 @@ impl StyledBlock for (anstyle::Style, &str) { pub trait Highlighter { /// ANSI Style #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - type Style: Style + Default + type Style: Style where Self: Sized; /// Takes the currently edited `line` with the cursor `pos`ition and @@ -122,6 +122,8 @@ pub trait Highlighter { /// Takes the currently edited `line` with the cursor `pos`ition and /// returns the styled blocks. + /// + /// Returns an empty vec when there is no highlighting. #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] #[cfg_attr( docsrs, @@ -132,9 +134,8 @@ pub trait Highlighter { where Self: Sized, { - let _ = pos; - // TODO default style vs empty vec to indicate no highlighting - vec![(Self::Style::default(), line)] + let _ = (line, pos); + vec![] } /// Takes the `prompt` and @@ -229,6 +230,8 @@ impl<'r, H: Highlighter> Highlighter for &'r 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... } @@ -237,13 +240,21 @@ impl MatchingBracketHighlighter { #[must_use] pub fn new() -> Self { Self { + #[cfg(feature = "anstyle")] + style: anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::AnsiColor::Blue.into())), bracket: Cell::new(None), } } } -#[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] +#[cfg(any(not(feature = "split-highlight"), feature = "anstyle"))] impl Highlighter for MatchingBracketHighlighter { + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + type Style = anstyle::Style; + + #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { if line.len() <= 1 { return Borrowed(line); @@ -259,6 +270,23 @@ impl Highlighter for MatchingBracketHighlighter { Borrowed(line) } + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + fn highlight_line<'l>(&self, line: &'l str, _pos: usize) -> Vec<(Self::Style, &'l str)> { + if line.len() <= 1 { + return vec![]; + } + if let Some((bracket, pos)) = self.bracket.get() { + if let Some((_, idx)) = find_matching_bracket(line, pos, bracket) { + return vec![ + (Self::Style::default(), &line[0..idx]), + (self.style, &line[idx..=idx]), + (Self::Style::default(), &line[idx + 1..]), + ]; + } + } + vec![] + } + fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { if forced { self.bracket.set(None); diff --git a/src/tty/mod.rs b/src/tty/mod.rs index 62484f042..f2abf0df5 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -47,14 +47,14 @@ pub trait Renderer { /// Display `prompt`, line and cursor in terminal output #[allow(clippy::too_many_arguments)] - fn refresh_line( + fn refresh_line( &mut self, prompt: &str, line: &LineBuffer, hint: Option<&str>, old_layout: &Layout, new_layout: &Layout, - highlighter: Option<&dyn Highlighter>, + highlighter: Option<&H>, ) -> Result<()>; /// Compute layout for rendering prompt + line + some info (either hint, @@ -126,14 +126,14 @@ impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R { (**self).move_cursor(old, new) } - fn refresh_line( + fn refresh_line( &mut self, prompt: &str, line: &LineBuffer, hint: Option<&str>, old_layout: &Layout, new_layout: &Layout, - highlighter: Option<&dyn Highlighter>, + highlighter: Option<&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 00ef42152..45828e61f 100644 --- a/src/tty/test.rs +++ b/src/tty/test.rs @@ -100,14 +100,14 @@ impl Renderer for Sink { Ok(()) } - fn refresh_line( + fn refresh_line( &mut self, _prompt: &str, _line: &LineBuffer, _hint: Option<&str>, _old_layout: &Layout, _new_layout: &Layout, - _highlighter: Option<&dyn Highlighter>, + _highlighter: Option<&H>, ) -> Result<()> { Ok(()) } diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 08d754810..cc8c8d074 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::{Highlighter, Style}; use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M}; use crate::layout::{Layout, Position}; use crate::line_buffer::LineBuffer; @@ -979,14 +979,14 @@ impl Renderer for PosixRenderer { Ok(()) } - fn refresh_line( + fn refresh_line( &mut self, prompt: &str, line: &LineBuffer, hint: Option<&str>, old_layout: &Layout, new_layout: &Layout, - highlighter: Option<&dyn Highlighter>, + highlighter: Option<&H>, ) -> Result<()> { use std::fmt::Write; self.buffer.clear(); @@ -1002,8 +1002,21 @@ impl Renderer for PosixRenderer { self.buffer .push_str(&highlighter.highlight_prompt(prompt, default_prompt)); // display the input line - self.buffer - .push_str(&highlighter.highlight(line, line.pos())); + 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 { + for (style, block) in highlighter.highlight_line(line, line.pos()) { + write!(self.buffer, "{}", style.start())?; + self.buffer.push_str(block); + write!(self.buffer, "{}", style.end())?; + } + } + } } else { // display the prompt self.buffer.push_str(prompt); @@ -1695,7 +1708,7 @@ mod test { let new_layout = out.compute_layout(prompt_size, default_prompt, &line, None); assert_eq!(Position { col: 1, row: 1 }, new_layout.cursor); assert_eq!(new_layout.cursor, new_layout.end); - out.refresh_line(prompt, &line, None, &old_layout, &new_layout, None) + out.refresh_line::<()>(prompt, &line, None, &old_layout, &new_layout, None) .unwrap(); #[rustfmt::skip] assert_eq!( diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 7574403f0..12cf187a2 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -429,14 +429,14 @@ impl Renderer for ConsoleRenderer { .map(|_| ()) } - fn refresh_line( + fn refresh_line( &mut self, prompt: &str, line: &LineBuffer, hint: Option<&str>, old_layout: &Layout, new_layout: &Layout, - highlighter: Option<&dyn Highlighter>, + highlighter: Option<&H>, ) -> Result<()> { let default_prompt = new_layout.default_prompt; let cursor = new_layout.cursor; From 606adc1daaf86e483e47dd0e2d14a3082a5ee382 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 24 Aug 2024 12:19:26 +0200 Subject: [PATCH 08/15] Fix build errors --- src/highlight.rs | 8 ++++++-- src/tty/unix.rs | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/highlight.rs b/src/highlight.rs index ef8740893..fa79d7080 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -249,7 +249,11 @@ impl MatchingBracketHighlighter { } } -#[cfg(any(not(feature = "split-highlight"), feature = "anstyle"))] +#[cfg(any( + not(feature = "split-highlight"), + feature = "anstyle", + feature = "ansi-str" +))] impl Highlighter for MatchingBracketHighlighter { #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] type Style = anstyle::Style; @@ -264,7 +268,7 @@ impl Highlighter for MatchingBracketHighlighter { 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 Owned(copy); + return Cow::Owned(copy); } } Borrowed(line) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index cc8c8d074..0c40ebdd6 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, Style}; +use crate::highlight::Highlighter; use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M}; use crate::layout::{Layout, Position}; use crate::line_buffer::LineBuffer; @@ -1010,6 +1010,7 @@ impl Renderer for PosixRenderer { self.buffer .push_str(&highlighter.highlight(line, line.pos())); } else { + use crate::highlight::Style; for (style, block) in highlighter.highlight_line(line, line.pos()) { write!(self.buffer, "{}", style.start())?; self.buffer.push_str(block); From 9e5c0e16dad4d28fd1cf46506b9a7fdb16b48eb2 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 24 Aug 2024 16:54:19 +0200 Subject: [PATCH 09/15] Fix highlight_line signature We need to return a `Cow` for `MaskingHighlighter` --- .github/workflows/rust.yml | 1 + Cargo.toml | 3 ++- examples/custom_key_bindings.rs | 3 +++ examples/example.rs | 13 ++++++++++++ examples/read_password.rs | 22 ++++++++++++++++++-- rustyline-derive/src/lib.rs | 20 ++++++++++++++++++ src/highlight.rs | 36 +++++++++++++++++++++++---------- src/tty/unix.rs | 2 +- 8 files changed, 85 insertions(+), 15 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index bf2e10d14..47ae56eaf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -53,6 +53,7 @@ jobs: 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 anstyle derive' direct-minimal-versions: name: Test min versions diff --git a/Cargo.toml b/Cargo.toml index 1d5851e5c..9b263e9b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,9 @@ maintenance = { status = "actively-developed" } members = ["rustyline-derive"] [dependencies] -# For convenience, you can highlight the whole input buffer, ansi-str helps to split +# 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 } bitflags = "2.6" cfg-if = "1.0" diff --git a/examples/custom_key_bindings.rs b/examples/custom_key_bindings.rs index 7d6624af7..abcdc5d6a 100644 --- a/examples/custom_key_bindings.rs +++ b/examples/custom_key_bindings.rs @@ -13,6 +13,9 @@ use rustyline::{Completer, Helper, Hinter, Validator}; struct MyHelper(#[rustyline(Hinter)] HistoryHinter); impl Highlighter for MyHelper { + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + type Style = (); + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( &'s self, prompt: &'p str, diff --git a/examples/example.rs b/examples/example.rs index cc3e2a0cc..c00e18512 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -21,6 +21,9 @@ struct MyHelper { } impl Highlighter for MyHelper { + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + type Style = anstyle::Style; + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( &'s self, prompt: &'p str, @@ -37,10 +40,20 @@ impl Highlighter for MyHelper { 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> { self.highlighter.highlight(line, pos) } + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + fn highlight_line<'l>( + &self, + line: &'l str, + pos: usize, + ) -> impl ExactSizeIterator)> { + self.highlighter.highlight_line(line, pos) + } + fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { self.highlighter.highlight_char(line, pos, forced) } diff --git a/examples/read_password.rs b/examples/read_password.rs index d669d511b..01c2e7561 100644 --- a/examples/read_password.rs +++ b/examples/read_password.rs @@ -1,4 +1,4 @@ -use std::borrow::Cow::{self, Borrowed, Owned}; +use std::borrow::Cow::{self, Owned}; use rustyline::config::Configurer; use rustyline::highlight::Highlighter; @@ -11,12 +11,30 @@ struct MaskingHighlighter { } impl Highlighter for MaskingHighlighter { + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + type Style = (); + + #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { use unicode_width::UnicodeWidthStr; if self.masking { Owned(" ".repeat(line.width())) } else { - Borrowed(line) + Cow::Borrowed(line) + } + } + + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + fn highlight_line<'l>( + &self, + line: &'l str, + _pos: usize, + ) -> impl ExactSizeIterator)> { + use unicode_width::UnicodeWidthStr; + if self.masking { + vec![((), Owned(" ".repeat(line.width())))].into_iter() + } else { + vec![].into_iter() } } diff --git a/rustyline-derive/src/lib.rs b/rustyline-derive/src/lib.rs index c25e6165b..c5325534d 100644 --- a/rustyline-derive/src/lib.rs +++ b/rustyline-derive/src/lib.rs @@ -102,10 +102,28 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream { quote! { #[automatically_derived] impl #impl_generics ::rustyline::highlight::Highlighter for #name #ty_generics #where_clause { + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str"), feature = "anstyle"))] + type Style = anstyle::Style; + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str"), not(feature = "anstyle")))] + type Style = (); + + #[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) } + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + fn highlight_line<'l>( + &self, + line: &'l str, + pos: usize, + ) -> impl ExactSizeIterator)> + where + Self: Sized, + { + ::rustyline::highlight::Highlighter::highlight_line(&self.#field_name_or_index, line, pos) + } + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( &'s self, prompt: &'p str, @@ -135,6 +153,8 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream { quote! { #[automatically_derived] impl #impl_generics ::rustyline::highlight::Highlighter for #name #ty_generics #where_clause { + #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] + type Style = (); } } }; diff --git a/src/highlight.rs b/src/highlight.rs index fa79d7080..7fe600db6 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -50,6 +50,7 @@ impl Style for anstyle::Style { } } +/* /// Styled text #[cfg(feature = "split-highlight")] #[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] @@ -91,6 +92,7 @@ impl StyledBlock for (anstyle::Style, &str) { &self.0 } } +*/ /// 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 @@ -129,13 +131,16 @@ pub trait Highlighter { docsrs, doc(cfg(all(feature = "split-highlight", not(feature = "ansi-str")))) )] - // TODO try to return an `IntoIterator` instead of a `Vec` - fn highlight_line<'l>(&self, line: &'l str, pos: usize) -> Vec<(Self::Style, &'l str)> + fn highlight_line<'l>( + &self, + line: &'l str, + pos: usize, + ) -> impl ExactSizeIterator)> where Self: Sized, { let _ = (line, pos); - vec![] + std::iter::empty() } /// Takes the `prompt` and @@ -193,7 +198,11 @@ impl<'r, H: Highlighter> Highlighter for &'r H { } #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - fn highlight_line<'l>(&self, line: &'l str, pos: usize) -> Vec<(Self::Style, &'l str)> + fn highlight_line<'l>( + &self, + line: &'l str, + pos: usize, + ) -> impl ExactSizeIterator)> where Self: Sized, { @@ -275,20 +284,25 @@ impl Highlighter for MatchingBracketHighlighter { } #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - fn highlight_line<'l>(&self, line: &'l str, _pos: usize) -> Vec<(Self::Style, &'l str)> { + fn highlight_line<'l>( + &self, + line: &'l str, + _pos: usize, + ) -> impl ExactSizeIterator)> { if line.len() <= 1 { - return vec![]; + return vec![].into_iter(); } if let Some((bracket, pos)) = self.bracket.get() { if let Some((_, idx)) = find_matching_bracket(line, pos, bracket) { return vec![ - (Self::Style::default(), &line[0..idx]), - (self.style, &line[idx..=idx]), - (Self::Style::default(), &line[idx + 1..]), - ]; + (Self::Style::default(), Borrowed(&line[0..idx])), + (self.style, Borrowed(&line[idx..=idx])), + (Self::Style::default(), Borrowed(&line[idx + 1..])), + ] + .into_iter(); } } - vec![] + vec![].into_iter() } fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 0c40ebdd6..ff93f8228 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1013,7 +1013,7 @@ impl Renderer for PosixRenderer { use crate::highlight::Style; for (style, block) in highlighter.highlight_line(line, line.pos()) { write!(self.buffer, "{}", style.start())?; - self.buffer.push_str(block); + self.buffer.push_str(&block); write!(self.buffer, "{}", style.end())?; } } From bb68c7259124df54be70b262bcda2dc3a8a09219 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 25 Aug 2024 08:35:00 +0200 Subject: [PATCH 10/15] Remove associated type Style --- .github/workflows/rust.yml | 1 + Cargo.toml | 5 +---- examples/custom_key_bindings.rs | 3 --- examples/example.rs | 5 +---- examples/read_password.rs | 11 ++++++----- rustyline-derive/src/lib.rs | 9 +-------- src/highlight.rs | 35 +++++++++++++-------------------- src/test/mod.rs | 5 +---- 8 files changed, 25 insertions(+), 49 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 47ae56eaf..41c690aa2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -53,6 +53,7 @@ jobs: 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: diff --git a/Cargo.toml b/Cargo.toml index 9b263e9b9..62f6d7524 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,7 @@ keywords = ["readline"] license = "MIT" categories = ["command-line-interface"] -exclude = [ - "/.github/*", - "/rustfmt.toml", -] +exclude = ["/.github/*", "/rustfmt.toml"] [badges] maintenance = { status = "actively-developed" } diff --git a/examples/custom_key_bindings.rs b/examples/custom_key_bindings.rs index abcdc5d6a..7d6624af7 100644 --- a/examples/custom_key_bindings.rs +++ b/examples/custom_key_bindings.rs @@ -13,9 +13,6 @@ use rustyline::{Completer, Helper, Hinter, Validator}; struct MyHelper(#[rustyline(Hinter)] HistoryHinter); impl Highlighter for MyHelper { - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - type Style = (); - fn highlight_prompt<'b, 's: 'b, 'p: 'b>( &'s self, prompt: &'p str, diff --git a/examples/example.rs b/examples/example.rs index c00e18512..5b2479b6b 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -21,9 +21,6 @@ struct MyHelper { } impl Highlighter for MyHelper { - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - type Style = anstyle::Style; - fn highlight_prompt<'b, 's: 'b, 'p: 'b>( &'s self, prompt: &'p str, @@ -50,7 +47,7 @@ impl Highlighter for MyHelper { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator)> { + ) -> impl ExactSizeIterator)> { self.highlighter.highlight_line(line, pos) } diff --git a/examples/read_password.rs b/examples/read_password.rs index 01c2e7561..c32f05256 100644 --- a/examples/read_password.rs +++ b/examples/read_password.rs @@ -11,9 +11,6 @@ struct MaskingHighlighter { } impl Highlighter for MaskingHighlighter { - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - type Style = (); - #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { use unicode_width::UnicodeWidthStr; @@ -29,10 +26,14 @@ impl Highlighter for MaskingHighlighter { &self, line: &'l str, _pos: usize, - ) -> impl ExactSizeIterator)> { + ) -> impl ExactSizeIterator)> { use unicode_width::UnicodeWidthStr; if self.masking { - vec![((), Owned(" ".repeat(line.width())))].into_iter() + vec![( + rustyline::highlight::AnsiStyle::default(), + Owned(" ".repeat(line.width())), + )] + .into_iter() } else { vec![].into_iter() } diff --git a/rustyline-derive/src/lib.rs b/rustyline-derive/src/lib.rs index c5325534d..6e7979436 100644 --- a/rustyline-derive/src/lib.rs +++ b/rustyline-derive/src/lib.rs @@ -102,11 +102,6 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream { quote! { #[automatically_derived] impl #impl_generics ::rustyline::highlight::Highlighter for #name #ty_generics #where_clause { - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str"), feature = "anstyle"))] - type Style = anstyle::Style; - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str"), not(feature = "anstyle")))] - type Style = (); - #[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) @@ -117,7 +112,7 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator)> + ) -> impl ExactSizeIterator)> where Self: Sized, { @@ -153,8 +148,6 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream { quote! { #[automatically_derived] impl #impl_generics ::rustyline::highlight::Highlighter for #name #ty_generics #where_clause { - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - type Style = (); } } }; diff --git a/src/highlight.rs b/src/highlight.rs index 7fe600db6..562923124 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -50,6 +50,14 @@ 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")] @@ -101,15 +109,9 @@ impl StyledBlock for (anstyle::Style, &str) { /// Currently, the highlighted version *must* have the same display width as /// the original input. pub trait Highlighter { - /// ANSI Style - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - type Style: Style - where - Self: Sized; /// Takes the currently edited `line` with the cursor `pos`ition and /// returns the highlighted version (with ANSI color). /// - /// /// 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"))] @@ -135,7 +137,7 @@ pub trait Highlighter { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator)> + ) -> impl ExactSizeIterator)> where Self: Sized, { @@ -183,15 +185,9 @@ pub trait Highlighter { } } -impl Highlighter for () { - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - type Style = (); -} +impl Highlighter for () {} impl<'r, H: Highlighter> Highlighter for &'r H { - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - type Style = H::Style; - #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { (**self).highlight(line, pos) @@ -202,7 +198,7 @@ impl<'r, H: Highlighter> Highlighter for &'r H { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator)> + ) -> impl ExactSizeIterator)> where Self: Sized, { @@ -264,9 +260,6 @@ impl MatchingBracketHighlighter { feature = "ansi-str" ))] impl Highlighter for MatchingBracketHighlighter { - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - type Style = anstyle::Style; - #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { if line.len() <= 1 { @@ -288,16 +281,16 @@ impl Highlighter for MatchingBracketHighlighter { &self, line: &'l str, _pos: usize, - ) -> impl ExactSizeIterator)> { + ) -> impl ExactSizeIterator)> { if line.len() <= 1 { return vec![].into_iter(); } if let Some((bracket, pos)) = self.bracket.get() { if let Some((_, idx)) = find_matching_bracket(line, pos, bracket) { return vec![ - (Self::Style::default(), Borrowed(&line[0..idx])), + (AnsiStyle::default(), Borrowed(&line[0..idx])), (self.style, Borrowed(&line[idx..=idx])), - (Self::Style::default(), Borrowed(&line[idx + 1..])), + (AnsiStyle::default(), Borrowed(&line[idx + 1..])), ] .into_iter(); } diff --git a/src/test/mod.rs b/src/test/mod.rs index 18d45244e..14ff9052b 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -56,10 +56,7 @@ impl Hinter for SimpleCompleter { } impl Helper for SimpleCompleter {} -impl Highlighter for SimpleCompleter { - #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] - type Style = (); -} +impl Highlighter for SimpleCompleter {} impl Validator for SimpleCompleter {} #[test] From 77e7b7079c8be3dbd52c6d79ccdd957ea840ea93 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 25 Aug 2024 08:46:54 +0200 Subject: [PATCH 11/15] Fix unix implementation --- src/tty/unix.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/tty/unix.rs b/src/tty/unix.rs index ff93f8228..413d9beba 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1011,10 +1011,15 @@ impl Renderer for PosixRenderer { .push_str(&highlighter.highlight(line, line.pos())); } else { use crate::highlight::Style; - for (style, block) in highlighter.highlight_line(line, line.pos()) { - write!(self.buffer, "{}", style.start())?; - self.buffer.push_str(&block); - write!(self.buffer, "{}", style.end())?; + let it = highlighter.highlight_line(line, line.pos()); + if it.len() == 0 { + self.buffer.push_str(line); + } else { + for (style, block) in it { + write!(self.buffer, "{}", style.start())?; + self.buffer.push_str(&block); + write!(self.buffer, "{}", style.end())?; + } } } } From 6422e06f20926cf6b19d1c80dab5e70509e4370b Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 25 Aug 2024 11:09:03 +0200 Subject: [PATCH 12/15] Simplify highlight_line signature `where Self: Sized` is not needed anymore And we don't really need `Cow<'l, str>` for MaskingHighlighter --- examples/example.rs | 2 +- examples/read_password.rs | 18 +++++++----------- rustyline-derive/src/lib.rs | 5 +---- src/highlight.rs | 18 ++++++------------ 4 files changed, 15 insertions(+), 28 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index 5b2479b6b..c48f7d7be 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -47,7 +47,7 @@ impl Highlighter for MyHelper { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator)> { + ) -> impl ExactSizeIterator { self.highlighter.highlight_line(line, pos) } diff --git a/examples/read_password.rs b/examples/read_password.rs index c32f05256..c62e1dc46 100644 --- a/examples/read_password.rs +++ b/examples/read_password.rs @@ -1,5 +1,3 @@ -use std::borrow::Cow::{self, Owned}; - use rustyline::config::Configurer; use rustyline::highlight::Highlighter; use rustyline::{ColorMode, Editor, Result}; @@ -12,12 +10,12 @@ struct MaskingHighlighter { impl Highlighter for MaskingHighlighter { #[cfg(any(not(feature = "split-highlight"), feature = "ansi-str"))] - fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { + fn highlight<'l>(&self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> { use unicode_width::UnicodeWidthStr; if self.masking { - Owned(" ".repeat(line.width())) + std::borrow::Cow::Owned(" ".repeat(line.width())) } else { - Cow::Borrowed(line) + std::borrow::Cow::Borrowed(line) } } @@ -26,14 +24,12 @@ impl Highlighter for MaskingHighlighter { &self, line: &'l str, _pos: usize, - ) -> impl ExactSizeIterator)> { + ) -> impl ExactSizeIterator { use unicode_width::UnicodeWidthStr; if self.masking { - vec![( - rustyline::highlight::AnsiStyle::default(), - Owned(" ".repeat(line.width())), - )] - .into_iter() + [(rustyline::highlight::AnsiStyle::default(), " ")] + .repeat(line.width()) + .into_iter() } else { vec![].into_iter() } diff --git a/rustyline-derive/src/lib.rs b/rustyline-derive/src/lib.rs index 6e7979436..6ed0f9389 100644 --- a/rustyline-derive/src/lib.rs +++ b/rustyline-derive/src/lib.rs @@ -112,10 +112,7 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator)> - where - Self: Sized, - { + ) -> impl ExactSizeIterator { ::rustyline::highlight::Highlighter::highlight_line(&self.#field_name_or_index, line, pos) } diff --git a/src/highlight.rs b/src/highlight.rs index 562923124..7256c3359 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -137,10 +137,7 @@ pub trait Highlighter { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator)> - where - Self: Sized, - { + ) -> impl ExactSizeIterator { let _ = (line, pos); std::iter::empty() } @@ -198,10 +195,7 @@ impl<'r, H: Highlighter> Highlighter for &'r H { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator)> - where - Self: Sized, - { + ) -> impl ExactSizeIterator { (**self).highlight_line(line, pos) } @@ -281,16 +275,16 @@ impl Highlighter for MatchingBracketHighlighter { &self, line: &'l str, _pos: usize, - ) -> impl ExactSizeIterator)> { + ) -> impl ExactSizeIterator { if line.len() <= 1 { return vec![].into_iter(); } if let Some((bracket, pos)) = self.bracket.get() { if let Some((_, idx)) = find_matching_bracket(line, pos, bracket) { return vec![ - (AnsiStyle::default(), Borrowed(&line[0..idx])), - (self.style, Borrowed(&line[idx..=idx])), - (AnsiStyle::default(), Borrowed(&line[idx + 1..])), + (AnsiStyle::default(), &line[0..idx]), + (self.style, &line[idx..=idx]), + (AnsiStyle::default(), &line[idx + 1..]), ] .into_iter(); } From c9db27158afbf51fd297f0da23c2597274dfd758 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 25 Aug 2024 11:40:57 +0200 Subject: [PATCH 13/15] Reintroduce StyledBlock --- examples/example.rs | 2 +- examples/read_password.rs | 4 ++-- rustyline-derive/src/lib.rs | 2 +- src/highlight.rs | 34 +++++++++++++++------------------- src/tty/unix.rs | 16 ++++++---------- 5 files changed, 25 insertions(+), 33 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index c48f7d7be..f2f5c79af 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -47,7 +47,7 @@ impl Highlighter for MyHelper { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator { + ) -> impl Iterator { self.highlighter.highlight_line(line, pos) } diff --git a/examples/read_password.rs b/examples/read_password.rs index c62e1dc46..2267afddd 100644 --- a/examples/read_password.rs +++ b/examples/read_password.rs @@ -24,14 +24,14 @@ impl Highlighter for MaskingHighlighter { &self, line: &'l str, _pos: usize, - ) -> impl ExactSizeIterator { + ) -> impl Iterator { use unicode_width::UnicodeWidthStr; if self.masking { [(rustyline::highlight::AnsiStyle::default(), " ")] .repeat(line.width()) .into_iter() } else { - vec![].into_iter() + vec![(rustyline::highlight::AnsiStyle::default(), line)].into_iter() } } diff --git a/rustyline-derive/src/lib.rs b/rustyline-derive/src/lib.rs index 6ed0f9389..2672136f7 100644 --- a/rustyline-derive/src/lib.rs +++ b/rustyline-derive/src/lib.rs @@ -112,7 +112,7 @@ pub fn highlighter_macro_derive(input: TokenStream) -> TokenStream { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator { + ) -> impl Iterator { ::rustyline::highlight::Highlighter::highlight_line(&self.#field_name_or_index, line, pos) } diff --git a/src/highlight.rs b/src/highlight.rs index 7256c3359..44caf8383 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -9,7 +9,7 @@ use std::fmt::Display; /// ANSI style #[cfg(feature = "split-highlight")] #[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] -pub trait Style { +pub trait Style: Default { /// Produce a ansi sequences which sets the graphic mode fn start(&self) -> impl Display; /// Produce a ansi sequences which ends the graphic mode @@ -27,7 +27,7 @@ impl Style for () { } } -#[cfg(feature = "ansi-str")] +/*#[cfg(feature = "ansi-str")] #[cfg_attr(docsrs, doc(cfg(feature = "ansi-str")))] impl Style for ansi_str::Style { fn start(&self) -> impl Display { @@ -37,7 +37,7 @@ impl Style for ansi_str::Style { fn end(&self) -> impl Display { self.end() } -} +}*/ #[cfg(feature = "anstyle")] #[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))] impl Style for anstyle::Style { @@ -58,7 +58,6 @@ pub type AnsiStyle = anstyle::Style; #[cfg(not(feature = "anstyle"))] pub type AnsiStyle = (); -/* /// Styled text #[cfg(feature = "split-highlight")] #[cfg_attr(docsrs, doc(cfg(feature = "split-highlight")))] @@ -74,7 +73,7 @@ pub trait StyledBlock { where Self: Sized; } -#[cfg(feature = "ansi-str")] +/*#[cfg(feature = "ansi-str")] #[cfg_attr(docsrs, doc(cfg(feature = "ansi-str")))] impl StyledBlock for ansi_str::AnsiBlock<'_> { type Style = ansi_str::Style; @@ -86,11 +85,11 @@ impl StyledBlock for ansi_str::AnsiBlock<'_> { fn style(&self) -> &Self::Style { self.style() } -} -#[cfg(feature = "anstyle")] -#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))] -impl StyledBlock for (anstyle::Style, &str) { - type Style = anstyle::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 @@ -100,7 +99,6 @@ impl StyledBlock for (anstyle::Style, &str) { &self.0 } } -*/ /// 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 @@ -126,8 +124,6 @@ pub trait Highlighter { /// Takes the currently edited `line` with the cursor `pos`ition and /// returns the styled blocks. - /// - /// Returns an empty vec when there is no highlighting. #[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] #[cfg_attr( docsrs, @@ -137,9 +133,9 @@ pub trait Highlighter { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator { + ) -> impl Iterator { let _ = (line, pos); - std::iter::empty() + vec![(AnsiStyle::default(), line)].into_iter() } /// Takes the `prompt` and @@ -195,7 +191,7 @@ impl<'r, H: Highlighter> Highlighter for &'r H { &self, line: &'l str, pos: usize, - ) -> impl ExactSizeIterator { + ) -> impl Iterator { (**self).highlight_line(line, pos) } @@ -275,9 +271,9 @@ impl Highlighter for MatchingBracketHighlighter { &self, line: &'l str, _pos: usize, - ) -> impl ExactSizeIterator { + ) -> impl Iterator { if line.len() <= 1 { - return vec![].into_iter(); + 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) { @@ -289,7 +285,7 @@ impl Highlighter for MatchingBracketHighlighter { .into_iter(); } } - vec![].into_iter() + vec![(AnsiStyle::default(), line)].into_iter() } fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool { diff --git a/src/tty/unix.rs b/src/tty/unix.rs index 413d9beba..edaea5470 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1010,16 +1010,12 @@ impl Renderer for PosixRenderer { self.buffer .push_str(&highlighter.highlight(line, line.pos())); } else { - use crate::highlight::Style; - let it = highlighter.highlight_line(line, line.pos()); - if it.len() == 0 { - self.buffer.push_str(line); - } else { - for (style, block) in it { - write!(self.buffer, "{}", style.start())?; - self.buffer.push_str(&block); - write!(self.buffer, "{}", style.end())?; - } + 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())?; } } } From 64051e13289fb6ce39f74755e316e4c2ccb19f43 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sun, 25 Aug 2024 11:57:12 +0200 Subject: [PATCH 14/15] Fix MaskingHighlighter --- examples/read_password.rs | 10 ++++++---- src/highlight.rs | 12 ++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/examples/read_password.rs b/examples/read_password.rs index 2267afddd..249b7d8ed 100644 --- a/examples/read_password.rs +++ b/examples/read_password.rs @@ -27,11 +27,13 @@ impl Highlighter for MaskingHighlighter { ) -> impl Iterator { use unicode_width::UnicodeWidthStr; if self.masking { - [(rustyline::highlight::AnsiStyle::default(), " ")] - .repeat(line.width()) - .into_iter() + vec![( + rustyline::highlight::AnsiStyle::default(), + " ".repeat(line.width()), + )] + .into_iter() } else { - vec![(rustyline::highlight::AnsiStyle::default(), line)].into_iter() + vec![(rustyline::highlight::AnsiStyle::default(), line.to_owned())].into_iter() } } diff --git a/src/highlight.rs b/src/highlight.rs index 44caf8383..a62e175ba 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -99,6 +99,18 @@ impl StyledBlock for (AnsiStyle, &str) { &self.0 } } +#[cfg(all(feature = "split-highlight", not(feature = "ansi-str")))] +impl StyledBlock for (AnsiStyle, String) { + type Style = AnsiStyle; + + fn text(&self) -> &str { + &self.1 + } + + fn style(&self) -> &Self::Style { + &self.0 + } +} /// 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 From 74656732759fd58032158ffc403b30f3e6849d88 Mon Sep 17 00:00:00 2001 From: gwenn Date: Sat, 31 Aug 2024 11:43:28 +0200 Subject: [PATCH 15/15] Fix windows impl --- src/error.rs | 2 +- src/tty/windows.rs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index b75101199..1ddfc5f64 100644 --- a/src/error.rs +++ b/src/error.rs @@ -125,7 +125,7 @@ impl From for ReadlineError { } } -#[cfg(unix)] +#[cfg(any(unix, all(feature = "split-highlight", not(feature = "ansi-str"))))] impl From for ReadlineError { fn from(err: fmt::Error) -> Self { Self::Io(io::Error::new(io::ErrorKind::Other, err)) diff --git a/src/tty/windows.rs b/src/tty/windows.rs index 12cf187a2..2d2c04796 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -449,7 +449,22 @@ impl Renderer for ConsoleRenderer { // append the prompt col = self.wrap_at_eol(&highlighter.highlight_prompt(prompt, default_prompt), col); // append the input line - col = self.wrap_at_eol(&highlighter.highlight(line, line.pos()), col); + 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);