diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-06 23:44:15 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-06 23:44:15 +0300 |
| commit | c7b79294e28fe7c835691a25597fabf226ecfc20 (patch) | |
| tree | 0f53309032f46aa119b7c44386524b1f02436759 /src/ui/sidebar | |
| parent | 2329bc181d1d21a365704f261e11ce0ee86524a5 (diff) | |
feat: add conditional styles for sidebar tokens (#3693)
Diffstat (limited to 'src/ui/sidebar')
| -rw-r--r-- | src/ui/sidebar/tokens.rs | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/src/ui/sidebar/tokens.rs b/src/ui/sidebar/tokens.rs index 911c87f9..d9cefe99 100644 --- a/src/ui/sidebar/tokens.rs +++ b/src/ui/sidebar/tokens.rs @@ -24,6 +24,23 @@ pub(crate) enum ResolvedTokenKind { Custom(String), } +impl ResolvedTokenKind { + fn text_value(&self) -> Option<&str> { + match self { + Self::StateText(value) + | Self::Machine(value) + | Self::Workspace(value) + | Self::Tab(value) + | Self::Pane(value) + | Self::Agent(value) + | Self::TerminalTitle(value) + | Self::Branch(value) + | Self::Custom(value) => Some(value), + Self::StateIcon | Self::GitStatus { .. } => None, + } + } +} + impl ResolvedToken { fn new(kind: ResolvedTokenKind, style: SidebarTokenStyle) -> Self { Self { kind, style } @@ -93,6 +110,9 @@ pub(crate) fn agent_rows( .map(ResolvedTokenKind::Custom), AgentSidebarToken::Styled { .. } => None, }?; + let style = kind + .text_value() + .map_or(style, |value| configured.style_for_value(value)); Some(ResolvedToken::new(kind, style)) }) .collect::<Vec<_>>(); @@ -146,6 +166,9 @@ pub(crate) fn space_rows( .map(ResolvedTokenKind::Custom), SpaceSidebarToken::Styled { .. } => None, }?; + let style = kind + .text_value() + .map_or(style, |value| configured.style_for_value(value)); Some(ResolvedToken::new(kind, style)) }) .collect::<Vec<_>>(); @@ -208,6 +231,114 @@ mod tests { } #[test] + fn conditional_styles_merge_first_match_and_keep_missing_values_absent() { + let config: AgentsSidebarConfig = toml::from_str(r##" +rows = [["state_icon", { token = "machine", fg = "#fff", bold = true, dim = true, rules = [{ equals = "Local", fg = "#f00", bold = false }, { contains = "Loc", fg = "#0f0", dim = false }] }], [{ token = "$missing", rules = [{ equals = "", bold = true }] }]] +"##).unwrap(); + let entry = entry(); + for (machine, color, bold, dim) in [ + ("Local", (255, 0, 0), false, true), + ("Localhost", (0, 255, 0), true, false), + ("local", (255, 255, 255), true, true), + ] { + let mut context = context(&entry); + context.machine = Some(machine); + let rows = agent_rows(&config, context, "working"); + assert_eq!(rows.len(), 1); + assert_eq!( + rows[0][0], + ResolvedToken::unstyled(ResolvedTokenKind::StateIcon) + ); + let token = &rows[0][1]; + assert_eq!(token.kind, ResolvedTokenKind::Machine(machine.into())); + assert_eq!( + token.style.fg.unwrap().ratatui(), + ratatui::style::Color::Rgb(color.0, color.1, color.2) + ); + assert_eq!(token.style.bold, Some(bold)); + assert_eq!(token.style.dim, Some(dim)); + } + assert_eq!(agent_rows(&config, context(&entry), "working")[0].len(), 1); + } + + #[test] + fn conditional_style_survives_truncation_and_removes_theme_modifiers() { + use ratatui::style::{Color, Modifier, Style}; + let config: AgentsSidebarConfig = toml::from_str(r##" +rows = [[{ token = "workspace", rules = [{ equals = "long-workspace-name", fg = "#f00", bold = false, dim = false }] }]] +"##).unwrap(); + let mut entry = entry(); + entry.workspace = "long-workspace-name".into(); + let rows = agent_rows(&config, context(&entry), "working"); + let theme = Style::default() + .fg(Color::Blue) + .add_modifier(Modifier::BOLD | Modifier::DIM); + for width in [4, 40] { + let spans = super::super::resolved_token_spans( + &rows[0], + ("*", theme), + theme, + theme, + theme, + theme, + &super::super::Palette::catppuccin(), + width, + ); + assert_eq!(spans.len(), 1); + assert!(super::super::display_width(&spans[0].content) <= width); + assert_eq!(spans[0].style.fg, Some(Color::Rgb(255, 0, 0))); + assert!(!spans[0] + .style + .add_modifier + .intersects(Modifier::BOLD | Modifier::DIM)); + assert!(spans[0] + .style + .sub_modifier + .contains(Modifier::BOLD | Modifier::DIM)); + } + } + + #[test] + fn custom_numeric_rules_resolve_in_agent_overrides_and_space_rows() { + let config: crate::config::SidebarConfig = toml::from_str( + r#" +[agents] +rows = [["workspace"]] +[agents.rows_by_agent] +pi = [[{ token = "$load", rules = [{ gt = 80, bold = true }, { gt = 50, dim = true }] }]] +[spaces] +rows = [[{ token = "$load", rules = [{ lt = 50, dim = true }] }]] +"#, + ) + .unwrap(); + let mut entry = entry(); + for (value, bold, dim) in [ + ("90", Some(true), None), + ("60", None, Some(true)), + ("20", None, None), + ("90%", None, None), + ] { + entry.tokens.insert("load".into(), value.into()); + let rows = agent_rows(&config.agents, context(&entry), "working"); + assert_eq!(rows[0][0].kind, ResolvedTokenKind::Custom(value.into())); + assert_eq!(rows[0][0].style.bold, bold); + assert_eq!(rows[0][0].style.dim, dim); + let spaces = space_rows( + &config.spaces, + SpaceTokenContext { + workspace: "repo", + branch: None, + state_text: "working", + ahead_behind: None, + suppress_git_details: false, + tokens: &entry.tokens, + }, + ); + assert_eq!(spaces[0][0].style.dim, (value == "20").then_some(true)); + } + } + + #[test] fn missing_custom_tokens_elide_rows_and_separators() { let entry = entry(); let config = AgentsSidebarConfig { |
