aboutsummaryrefslogtreecommitdiff
path: root/src/ui/sidebar
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-06 23:44:15 +0300
committerGitHub <noreply@github.com>2026-09-06 23:44:15 +0300
commitc7b79294e28fe7c835691a25597fabf226ecfc20 (patch)
tree0f53309032f46aa119b7c44386524b1f02436759 /src/ui/sidebar
parent2329bc181d1d21a365704f261e11ce0ee86524a5 (diff)
feat: add conditional styles for sidebar tokens (#3693)
Diffstat (limited to 'src/ui/sidebar')
-rw-r--r--src/ui/sidebar/tokens.rs131
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 {