fix: correctly merge styles & do not reset them by default (#918)

This commit is contained in:
Loric ANDRE 2026-01-23 11:45:47 +01:00
parent 2cfd33ccc9
commit 96a4c69646
5 changed files with 52 additions and 172 deletions

View file

@ -230,18 +230,10 @@ impl SkimItem for DefaultSkimItem {
// Flush normal content if any
if !current_content.is_empty() {
// Combine ANSI style with context base_style
let mut combined_base_style = base_style;
if let Some(bg) = context.base_style.bg {
combined_base_style = combined_base_style.bg(bg);
}
if let Some(fg) = context.base_style.fg
&& base_style.fg.is_none()
{
combined_base_style = combined_base_style.fg(fg);
}
combined_base_style =
combined_base_style.add_modifier(context.base_style.add_modifier);
new_spans.push(Span::styled(current_content.clone(), combined_base_style));
new_spans.push(Span::styled(
current_content.clone(),
base_style.patch(context.base_style),
));
current_content.clear();
}
highlighted_content.push(ch);
@ -249,17 +241,10 @@ impl SkimItem for DefaultSkimItem {
// Flush highlighted content if any
if !highlighted_content.is_empty() {
// Combine styles: use highlight bg, preserve ANSI fg and modifiers
let mut combined_style = base_style;
if let Some(bg) = context.style.bg {
combined_style = combined_style.bg(bg);
}
if let Some(fg) = context.style.fg
&& base_style.fg.is_none()
{
combined_style = combined_style.fg(fg);
}
combined_style = combined_style.add_modifier(context.style.add_modifier);
new_spans.push(Span::styled(highlighted_content.clone(), combined_style));
new_spans.push(Span::styled(
highlighted_content.clone(),
base_style.patch(context.matched_syle),
));
highlighted_content.clear();
}
current_content.push(ch);
@ -270,31 +255,14 @@ impl SkimItem for DefaultSkimItem {
// Flush remaining content
if !current_content.is_empty() {
// Combine ANSI style with context base_style
let mut combined_base_style = base_style;
if let Some(bg) = context.base_style.bg {
combined_base_style = combined_base_style.bg(bg);
}
if let Some(fg) = context.base_style.fg
&& base_style.fg.is_none()
{
combined_base_style = combined_base_style.fg(fg);
}
combined_base_style = combined_base_style.add_modifier(context.base_style.add_modifier);
new_spans.push(Span::styled(current_content, combined_base_style));
new_spans.push(Span::styled(current_content, base_style.patch(context.base_style)));
}
if !highlighted_content.is_empty() {
// Combine styles: use highlight bg, preserve ANSI fg and modifiers
let mut combined_style = base_style;
if let Some(bg) = context.style.bg {
combined_style = combined_style.bg(bg);
}
if let Some(fg) = context.style.fg
&& base_style.fg.is_none()
{
combined_style = combined_style.fg(fg);
}
combined_style = combined_style.add_modifier(context.style.add_modifier);
new_spans.push(Span::styled(highlighted_content, combined_style));
new_spans.push(Span::styled(
highlighted_content,
base_style.patch(context.matched_syle),
));
}
}
@ -327,45 +295,15 @@ impl SkimItem for DefaultSkimItem {
if !before.is_empty() {
// Combine ANSI style with context base_style
let mut combined_base_style = base_style;
if let Some(bg) = context.base_style.bg {
combined_base_style = combined_base_style.bg(bg);
}
if let Some(fg) = context.base_style.fg
&& base_style.fg.is_none()
{
combined_base_style = combined_base_style.fg(fg);
}
combined_base_style = combined_base_style.add_modifier(context.base_style.add_modifier);
new_spans.push(Span::styled(before, combined_base_style));
new_spans.push(Span::styled(before, base_style.patch(context.base_style)));
}
if !highlighted.is_empty() {
// Combine styles: use highlight bg, preserve ANSI fg and modifiers
let mut combined_style = base_style;
if let Some(bg) = context.style.bg {
combined_style = combined_style.bg(bg);
}
if let Some(fg) = context.style.fg
&& base_style.fg.is_none()
{
combined_style = combined_style.fg(fg);
}
combined_style = combined_style.add_modifier(context.style.add_modifier);
new_spans.push(Span::styled(highlighted, combined_style));
// Combine ANSI style with context matched_syle
new_spans.push(Span::styled(highlighted, base_style.patch(context.matched_syle)));
}
if !after.is_empty() {
// Combine ANSI style with context base_style
let mut combined_base_style = base_style;
if let Some(bg) = context.base_style.bg {
combined_base_style = combined_base_style.bg(bg);
}
if let Some(fg) = context.base_style.fg
&& base_style.fg.is_none()
{
combined_base_style = combined_base_style.fg(fg);
}
combined_base_style = combined_base_style.add_modifier(context.base_style.add_modifier);
new_spans.push(Span::styled(after, combined_base_style));
new_spans.push(Span::styled(after, base_style.patch(context.base_style)));
}
}
@ -403,70 +341,21 @@ impl SkimItem for DefaultSkimItem {
if !before.is_empty() {
// Combine ANSI style with context base_style
let mut combined_base_style = base_style;
if let Some(bg) = context.base_style.bg {
combined_base_style = combined_base_style.bg(bg);
}
if let Some(fg) = context.base_style.fg
&& base_style.fg.is_none()
{
combined_base_style = combined_base_style.fg(fg);
}
combined_base_style = combined_base_style.add_modifier(context.base_style.add_modifier);
new_spans.push(Span::styled(before, combined_base_style));
new_spans.push(Span::styled(before, base_style.patch(context.base_style)));
}
if !highlighted.is_empty() {
// Combine styles: use highlight bg, preserve ANSI fg and modifiers
let mut combined_style = base_style;
if let Some(bg) = context.style.bg {
combined_style = combined_style.bg(bg);
}
if let Some(fg) = context.style.fg
&& base_style.fg.is_none()
{
combined_style = combined_style.fg(fg);
}
combined_style = combined_style.add_modifier(context.style.add_modifier);
new_spans.push(Span::styled(highlighted, combined_style));
// Combine ANSI style with context matched_syle
new_spans.push(Span::styled(highlighted, base_style.patch(context.matched_syle)));
}
if !after.is_empty() {
// Combine ANSI style with context base_style
let mut combined_base_style = base_style;
if let Some(bg) = context.base_style.bg {
combined_base_style = combined_base_style.bg(bg);
}
if let Some(fg) = context.base_style.fg
&& base_style.fg.is_none()
{
combined_base_style = combined_base_style.fg(fg);
}
combined_base_style = combined_base_style.add_modifier(context.base_style.add_modifier);
new_spans.push(Span::styled(after, combined_base_style));
new_spans.push(Span::styled(after, base_style.patch(context.base_style)));
}
}
Line::from(new_spans)
}
crate::Matches::None => {
// No highlighting needed, but apply base_style to all spans
let styled_spans: Vec<Span> = all_spans
.into_iter()
.map(|span| {
let mut combined_style = span.style;
if let Some(bg) = context.base_style.bg {
combined_style = combined_style.bg(bg);
}
if let Some(fg) = context.base_style.fg
&& span.style.fg.is_none()
{
combined_style = combined_style.fg(fg);
}
combined_style = combined_style.add_modifier(context.base_style.add_modifier);
Span::styled(span.content, combined_style)
})
.collect();
Line::from(styled_spans)
}
crate::Matches::None => Line::from(all_spans),
}
} else {
// No ANSI mapping needed, use text as-is
@ -706,7 +595,7 @@ mod test {
matches: Matches::CharRange(6, 10),
container_width: 80,
base_style: Style::default(),
style: Style::default().fg(Color::Yellow),
matched_syle: Style::default().fg(Color::Yellow),
};
// display() should map the match positions back to the original ANSI text
@ -745,7 +634,7 @@ mod test {
matches: Matches::CharIndices(vec![1, 2]),
container_width: 80,
base_style: Style::default(),
style: Style::default().fg(Color::Yellow),
matched_syle: Style::default().fg(Color::Yellow),
};
// display() should map these to positions 6,7 in original text
@ -815,7 +704,7 @@ mod test {
matches: Matches::CharIndices(vec![0]),
container_width: 80,
base_style: Style::default(),
style: Style::default().bg(Color::Yellow),
matched_syle: Style::default().bg(Color::Yellow),
};
let line = item.display(context);
@ -854,7 +743,7 @@ mod test {
matches: Matches::CharRange(1, 3),
container_width: 80,
base_style: Style::default(),
style: Style::default().bg(Color::Yellow),
matched_syle: Style::default().bg(Color::Yellow),
};
let line = item.display(context);
@ -895,7 +784,7 @@ mod test {
matches: Matches::ByteRange(1, 3),
container_width: 80,
base_style: Style::default(),
style: Style::default().bg(Color::Yellow),
matched_syle: Style::default().bg(Color::Yellow),
};
let line = item.display(context);

View file

@ -124,7 +124,7 @@ pub struct DisplayContext {
/// The base style to apply to non-matched portions
pub base_style: Style,
/// The style to apply to matched portions
pub style: Style,
pub matched_syle: Style,
}
impl DisplayContext {
@ -144,7 +144,7 @@ impl DisplayContext {
res.push_span(Span::styled(span_content.collect::<String>(), self.base_style));
let highlighted_char = chars.next().unwrap_or_default().to_string();
res.push_span(Span::styled(highlighted_char, self.style));
res.push_span(Span::styled(highlighted_char, self.base_style.patch(self.matched_syle)));
prev_index = index + 1;
}
res.push_span(Span::styled(chars.collect::<String>(), self.base_style));
@ -161,7 +161,7 @@ impl DisplayContext {
));
let highlighted_text = chars.by_ref().take(*end - *start).collect::<String>();
res.push_span(Span::styled(highlighted_text, self.style));
res.push_span(Span::styled(highlighted_text, self.base_style.patch(self.matched_syle)));
res.push_span(Span::styled(chars.collect::<String>(), self.base_style));
res
}
@ -175,7 +175,7 @@ impl DisplayContext {
let highlighted_bytes = bytes.by_ref().take(*end - *start).collect();
let highlighted_text = String::from_utf8(highlighted_bytes).unwrap();
res.push_span(Span::styled(highlighted_text, self.style));
res.push_span(Span::styled(highlighted_text, self.base_style.patch(self.matched_syle)));
res.push_span(Span::styled(
String::from_utf8(bytes.collect()).unwrap(),
self.base_style,

View file

@ -64,19 +64,9 @@ impl ColorTheme {
}
fn none() -> Self {
ColorTheme {
normal: Style::reset(),
matched: Style::reset(),
current: Style::reset(),
current_match: Style::reset(),
query: Style::reset(),
spinner: Style::reset().bold(),
info: Style::reset(),
prompt: Style::reset(),
cursor: Style::reset(),
selected: Style::reset(),
header: Style::reset(),
border: Style::reset(),
Self {
spinner: Style::default().bold(),
..ColorTheme::default()
}
}
@ -91,19 +81,18 @@ impl ColorTheme {
}
fn default16() -> Self {
let bg = Color::Black;
let base = ColorTheme::none();
ColorTheme {
matched: base.matched.fg(Color::Green).bg(bg),
current: base.current.fg(Color::Yellow).bg(bg),
current_match: base.current_match.fg(Color::Green).bg(bg),
spinner: base.spinner.fg(Color::Green).bg(bg),
info: base.info.fg(Color::White).bg(bg),
prompt: base.prompt.fg(Color::Blue).bg(bg),
cursor: base.cursor.fg(Color::Red).bg(bg),
selected: base.selected.fg(Color::Magenta).bg(bg),
header: base.header.fg(Color::Cyan).bg(bg),
border: base.border.fg(Color::Black).bg(bg),
matched: base.matched.fg(Color::Green),
current: base.current.fg(Color::Yellow),
current_match: base.current_match.fg(Color::Green),
spinner: base.spinner.fg(Color::Green),
info: base.info.fg(Color::White),
prompt: base.prompt.fg(Color::Blue),
cursor: base.cursor.fg(Color::Red),
selected: base.selected.fg(Color::Magenta),
header: base.header.fg(Color::Cyan),
border: base.border.fg(Color::Black),
..base
}
}
@ -112,7 +101,7 @@ impl ColorTheme {
let base = ColorTheme::none();
ColorTheme {
matched: base.matched.fg(Color::Indexed(108)).bg(Color::Indexed(0)),
current: base.current.fg(Color::Indexed(254)).bg(Color::Indexed(236)),
current: base.current.bg(Color::Indexed(236)),
current_match: base.current_match.fg(Color::Indexed(151)).bg(Color::Indexed(236)),
spinner: base.spinner.fg(Color::Indexed(148)),
info: base.info.fg(Color::Indexed(144)),
@ -129,7 +118,7 @@ impl ColorTheme {
let base = ColorTheme::none();
ColorTheme {
matched: base.matched.fg(Color::Indexed(234)).bg(Color::Indexed(186)),
current: base.current.fg(Color::Indexed(254)).bg(Color::Indexed(236)),
current: base.current.bg(Color::Indexed(236)),
current_match: base.current_match.fg(Color::Indexed(234)).bg(Color::Indexed(186)),
spinner: base.spinner.fg(Color::Indexed(148)),
info: base.info.fg(Color::Indexed(144)),
@ -146,7 +135,7 @@ impl ColorTheme {
let base = ColorTheme::none();
ColorTheme {
matched: base.matched.fg(Color::Indexed(0)).bg(Color::Indexed(220)),
current: base.current.fg(Color::Indexed(237)).bg(Color::Indexed(251)),
current: base.current.bg(Color::Indexed(251)),
current_match: base.current_match.fg(Color::Indexed(66)).bg(Color::Indexed(251)),
spinner: base.spinner.fg(Color::Indexed(65)),
info: base.info.fg(Color::Indexed(101)),
@ -206,6 +195,8 @@ impl ColorTheme {
(&name[..name.len() - 2], "u")
} else if name.ends_with("_underline") || name.ends_with("-underline") {
(&name[..name.len() - 10], "underline")
} else if name == "bg" {
("", "bg")
} else {
(name, "fg")
};
@ -307,7 +298,7 @@ mod tests {
let theme_16 = ColorTheme::default16();
assert_eq!(theme_16.matched.fg, Some(Color::Green));
assert_eq!(theme_16.matched.bg, Some(Color::Black));
assert_eq!(theme_16.matched.bg, None);
let dark = ColorTheme::dark256();
assert_eq!(dark.matched.fg, Some(Color::Indexed(108)));

View file

@ -676,7 +676,7 @@ impl SkimWidget for ItemList {
matches,
container_width,
base_style: if is_current { theme.current } else { theme.normal },
style: if is_current { theme.current_match } else { theme.matched },
matched_syle: if is_current { theme.current_match } else { theme.matched },
});
if !wrap {

View file

@ -26,7 +26,7 @@ sk_test!(issue_547_null_match, "\\0Test Test Test", &[], {
@capture[0] starts_with("> Test");
@capture[2] starts_with("> Test Test Test");
@capture_colored[2] trim().eq("\u{1b}[38;5;161m>\u{1b}[38;5;168m \u{1b}[38;5;151m\u{1b}[48;5;236mTest\u{1b}[38;5;254m Test Test");
@capture_colored[2] trim().eq("\u{1b}[38;5;161m>\u{1b}[38;5;168m \u{1b}[38;5;151m\u{1b}[48;5;236mTest\u{1b}[39m Test Test");
});
sk_test!(issue_xxx_null_delimiter_with_nth, "a\\0b\\0c", &["--delimiter", "'\\x00'", "--with-nth", "2"], {