mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 07:35:42 +02:00
fix(input list): don't leak ansi styling, fuzzy match indicator preserves styles (#16276)
- fixes #16200 # Description | | Select | Fuzzy | | -- | ---------------- | --------------- | | ❌ | ![select-before] | ![fuzzy-before] | | ✅ | ![select-fixed] | ![fuzzy-fixed] | [select-before]:8fe9136472/select-before.svg
[select-fixed]:8fe9136472/select-after.svg
[fuzzy-before]:8fe9136472/fuzzy-before.svg
[fuzzy-fixed]:8fe9136472/fuzzy-after.svg
Using a custom `dialoguer::theme::Theme` implementation, how `input list` renders items are overridden. Unfortunately, implementing one of the methods requires `fuzzy_matcher::skim::SkimMatcherV2` which `dialoguer` does not export by itself. Had to add an explicit dependency to `fuzzy_matcher`, which we already depend on through `dialoguer`. Version specification is copied from `dialoguer`. # Tests + Formatting No tests added. Couldn't find existing tests, not sure how to test this. --------- Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3778,6 +3778,7 @@ dependencies = [
|
||||
"fancy-regex",
|
||||
"filesize",
|
||||
"filetime",
|
||||
"fuzzy-matcher",
|
||||
"getrandom 0.2.15",
|
||||
"human-date-parser",
|
||||
"indexmap",
|
||||
|
@ -83,6 +83,7 @@ csv = "1.3"
|
||||
ctrlc = "3.4"
|
||||
devicons = "0.6.12"
|
||||
dialoguer = { default-features = false, version = "0.11" }
|
||||
fuzzy-matcher = { version = "^0.3.7" }
|
||||
digest = { default-features = false, version = "0.10" }
|
||||
dirs = "5.0"
|
||||
dirs-sys = "0.4"
|
||||
|
@ -54,6 +54,7 @@ devicons = { workspace = true }
|
||||
dialoguer = { workspace = true, default-features = false, features = [
|
||||
"fuzzy-select",
|
||||
] }
|
||||
fuzzy-matcher = { workspace = true }
|
||||
digest = { workspace = true, default-features = false }
|
||||
dtparse = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
|
@ -123,14 +123,8 @@ impl Command for InputList {
|
||||
});
|
||||
}
|
||||
|
||||
// could potentially be used to map the use theme colors at some point
|
||||
// let theme = dialoguer::theme::ColorfulTheme {
|
||||
// active_item_style: Style::new().fg(Color::Cyan).bold(),
|
||||
// ..Default::default()
|
||||
// };
|
||||
|
||||
let answer: InteractMode = if multi {
|
||||
let multi_select = MultiSelect::new(); //::with_theme(&theme);
|
||||
let multi_select = MultiSelect::with_theme(&NuTheme);
|
||||
|
||||
InteractMode::Multi(
|
||||
if let Some(prompt) = prompt {
|
||||
@ -146,7 +140,7 @@ impl Command for InputList {
|
||||
})?,
|
||||
)
|
||||
} else if fuzzy {
|
||||
let fuzzy_select = FuzzySelect::new(); //::with_theme(&theme);
|
||||
let fuzzy_select = FuzzySelect::with_theme(&NuTheme);
|
||||
|
||||
InteractMode::Single(
|
||||
if let Some(prompt) = prompt {
|
||||
@ -163,7 +157,7 @@ impl Command for InputList {
|
||||
})?,
|
||||
)
|
||||
} else {
|
||||
let select = Select::new(); //::with_theme(&theme);
|
||||
let select = Select::with_theme(&NuTheme);
|
||||
InteractMode::Single(
|
||||
if let Some(prompt) = prompt {
|
||||
select.with_prompt(&prompt)
|
||||
@ -255,6 +249,84 @@ impl Command for InputList {
|
||||
}
|
||||
}
|
||||
|
||||
use dialoguer::theme::{SimpleTheme, Theme};
|
||||
use nu_ansi_term::ansi::RESET;
|
||||
|
||||
// could potentially be used to map the use theme colors at some point
|
||||
|
||||
/// Theme for handling already colored items gracefully.
|
||||
struct NuTheme;
|
||||
|
||||
impl Theme for NuTheme {
|
||||
fn format_select_prompt_item(
|
||||
&self,
|
||||
f: &mut dyn std::fmt::Write,
|
||||
text: &str,
|
||||
active: bool,
|
||||
) -> std::fmt::Result {
|
||||
SimpleTheme.format_select_prompt_item(f, text, active)?;
|
||||
write!(f, "{RESET}")
|
||||
}
|
||||
|
||||
fn format_multi_select_prompt_item(
|
||||
&self,
|
||||
f: &mut dyn std::fmt::Write,
|
||||
text: &str,
|
||||
checked: bool,
|
||||
active: bool,
|
||||
) -> std::fmt::Result {
|
||||
SimpleTheme.format_multi_select_prompt_item(f, text, checked, active)?;
|
||||
write!(f, "{RESET}")
|
||||
}
|
||||
|
||||
fn format_sort_prompt_item(
|
||||
&self,
|
||||
f: &mut dyn std::fmt::Write,
|
||||
text: &str,
|
||||
picked: bool,
|
||||
active: bool,
|
||||
) -> std::fmt::Result {
|
||||
SimpleTheme.format_sort_prompt_item(f, text, picked, active)?;
|
||||
writeln!(f, "{RESET}")
|
||||
}
|
||||
|
||||
fn format_fuzzy_select_prompt_item(
|
||||
&self,
|
||||
f: &mut dyn std::fmt::Write,
|
||||
text: &str,
|
||||
active: bool,
|
||||
highlight_matches: bool,
|
||||
matcher: &fuzzy_matcher::skim::SkimMatcherV2,
|
||||
search_term: &str,
|
||||
) -> std::fmt::Result {
|
||||
use fuzzy_matcher::FuzzyMatcher;
|
||||
write!(f, "{} ", if active { ">" } else { " " })?;
|
||||
|
||||
if !highlight_matches {
|
||||
return write!(f, "{text}{RESET}");
|
||||
}
|
||||
let Some((_score, indices)) = matcher.fuzzy_indices(text, search_term) else {
|
||||
return write!(f, "{text}{RESET}");
|
||||
};
|
||||
let prefix = nu_ansi_term::Style::new()
|
||||
.italic()
|
||||
.underline()
|
||||
.prefix()
|
||||
.to_string();
|
||||
// HACK: Reset italic and underline, from the `ansi` command, should be moved to `nu_ansi_term`
|
||||
let suffix = "\x1b[23;24m";
|
||||
|
||||
for (idx, c) in text.chars().enumerate() {
|
||||
if indices.contains(&idx) {
|
||||
write!(f, "{prefix}{c}{suffix}")?;
|
||||
} else {
|
||||
write!(f, "{}", c)?;
|
||||
}
|
||||
}
|
||||
write!(f, "{RESET}")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
Reference in New Issue
Block a user