mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 13:15:58 +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",
|
"fancy-regex",
|
||||||
"filesize",
|
"filesize",
|
||||||
"filetime",
|
"filetime",
|
||||||
|
"fuzzy-matcher",
|
||||||
"getrandom 0.2.15",
|
"getrandom 0.2.15",
|
||||||
"human-date-parser",
|
"human-date-parser",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
@ -83,6 +83,7 @@ csv = "1.3"
|
|||||||
ctrlc = "3.4"
|
ctrlc = "3.4"
|
||||||
devicons = "0.6.12"
|
devicons = "0.6.12"
|
||||||
dialoguer = { default-features = false, version = "0.11" }
|
dialoguer = { default-features = false, version = "0.11" }
|
||||||
|
fuzzy-matcher = { version = "^0.3.7" }
|
||||||
digest = { default-features = false, version = "0.10" }
|
digest = { default-features = false, version = "0.10" }
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
dirs-sys = "0.4"
|
dirs-sys = "0.4"
|
||||||
|
@ -54,6 +54,7 @@ devicons = { workspace = true }
|
|||||||
dialoguer = { workspace = true, default-features = false, features = [
|
dialoguer = { workspace = true, default-features = false, features = [
|
||||||
"fuzzy-select",
|
"fuzzy-select",
|
||||||
] }
|
] }
|
||||||
|
fuzzy-matcher = { workspace = true }
|
||||||
digest = { workspace = true, default-features = false }
|
digest = { workspace = true, default-features = false }
|
||||||
dtparse = { workspace = true }
|
dtparse = { workspace = true }
|
||||||
encoding_rs = { 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 answer: InteractMode = if multi {
|
||||||
let multi_select = MultiSelect::new(); //::with_theme(&theme);
|
let multi_select = MultiSelect::with_theme(&NuTheme);
|
||||||
|
|
||||||
InteractMode::Multi(
|
InteractMode::Multi(
|
||||||
if let Some(prompt) = prompt {
|
if let Some(prompt) = prompt {
|
||||||
@ -146,7 +140,7 @@ impl Command for InputList {
|
|||||||
})?,
|
})?,
|
||||||
)
|
)
|
||||||
} else if fuzzy {
|
} else if fuzzy {
|
||||||
let fuzzy_select = FuzzySelect::new(); //::with_theme(&theme);
|
let fuzzy_select = FuzzySelect::with_theme(&NuTheme);
|
||||||
|
|
||||||
InteractMode::Single(
|
InteractMode::Single(
|
||||||
if let Some(prompt) = prompt {
|
if let Some(prompt) = prompt {
|
||||||
@ -163,7 +157,7 @@ impl Command for InputList {
|
|||||||
})?,
|
})?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let select = Select::new(); //::with_theme(&theme);
|
let select = Select::with_theme(&NuTheme);
|
||||||
InteractMode::Single(
|
InteractMode::Single(
|
||||||
if let Some(prompt) = prompt {
|
if let Some(prompt) = prompt {
|
||||||
select.with_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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
Reference in New Issue
Block a user