Skip to content

Commit 0fc2b57

Browse files
committed
feat(tui): add support for named themes (#1779)
1 parent d4f1aad commit 0fc2b57

25 files changed

Lines changed: 671 additions & 288 deletions

crates/trippy-tui/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub fn main() {
22
println!("cargo:rerun-if-changed=locales.toml");
3+
println!("cargo:rerun-if-changed=themes");
34
}

crates/trippy-tui/src/config.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ mod constants;
2121
mod file;
2222
mod theme;
2323

24-
use crate::config::file::{ConfigBindings, ConfigTui};
24+
use crate::config::file::{ConfigBindings, ConfigThemeColors, ConfigTui};
2525
pub use binding::{TuiBindings, TuiCommandItem, TuiKeyBinding};
2626
pub use cmd::Args;
2727
pub use columns::{TuiColumn, TuiColumns};
@@ -253,6 +253,8 @@ pub enum TrippyAction {
253253
Trippy(Box<TrippyConfig>),
254254
/// Print all TUI theme items and exit.
255255
PrintTuiThemeItems,
256+
/// Print all built-in TUI themes and exit.
257+
PrintTuiThemes,
256258
/// Print all TUI commands that can be bound and exit.
257259
PrintTuiBindingCommands,
258260
/// Print a template toml config file and exit.
@@ -269,6 +271,8 @@ impl TrippyAction {
269271
pub fn from(args: Args, privilege: &Privilege, pid: u16) -> anyhow::Result<Self> {
270272
Ok(if args.print_tui_theme_items {
271273
Self::PrintTuiThemeItems
274+
} else if args.print_tui_themes {
275+
Self::PrintTuiThemes
272276
} else if args.print_tui_binding_commands {
273277
Self::PrintTuiBindingCommands
274278
} else if args.print_config_template {
@@ -551,6 +555,11 @@ impl TrippyConfig {
551555
.as_deref()
552556
.map(chrono_tz::Tz::from_str)
553557
.transpose()?;
558+
let tui_theme = cfg_layer(
559+
args.tui_theme,
560+
cfg_file_tui.tui_theme,
561+
String::from(constants::DEFAULT_TUI_THEME),
562+
);
554563
let dns_lookup_as_info = cfg_layer_bool_flag(
555564
args.dns_lookup_as_info,
556565
cfg_file_dns.dns_lookup_as_info,
@@ -664,7 +673,10 @@ impl TrippyConfig {
664673
.tui_theme_colors
665674
.into_iter()
666675
.collect::<HashMap<TuiThemeItem, TuiColor>>();
667-
let tui_theme = TuiTheme::from((tui_theme_items, cfg_file_tui_theme_colors));
676+
let cli_tui_theme_colors = ConfigThemeColors::from(tui_theme_items);
677+
let tui_theme = TuiTheme::for_name(&tui_theme)?
678+
.overlay(&cfg_file_tui_theme_colors)
679+
.overlay(&cli_tui_theme_colors);
668680
let tui_binding_items = args
669681
.tui_key_bindings
670682
.into_iter()
@@ -1569,11 +1581,13 @@ mod tests {
15691581
compare(parse_config(cmd), expected);
15701582
}
15711583

1584+
// TODO add --tui-theme tests
15721585
#[test_case("trip example.com", Ok(cfg().tui_theme(TuiTheme::default()).build()); "default tui theme")]
15731586
#[test_case("trip example.com --tui-theme-colors bg-color=red", Ok(cfg().tui_theme(TuiTheme { bg: TuiColor::Red, ..Default::default() }).build()); "custom tui theme named color")]
15741587
#[test_case("trip example.com --tui-theme-colors bg-color=010203", Ok(cfg().tui_theme(TuiTheme { bg: TuiColor::Rgb(1, 2, 3), ..Default::default() }).build()); "custom tui theme hex color")]
15751588
#[test_case("trip example.com --tui-theme-colors bg-color=#010203", Ok(cfg().tui_theme(TuiTheme { bg: TuiColor::Rgb(1, 2, 3), ..Default::default() }).build()); "custom tui theme hex color with hash prefix")]
15761589
#[test_case("trip example.com --tui-theme-colors bg-color=red,text-color=blue", Ok(cfg().tui_theme(TuiTheme { bg: TuiColor::Red, text: TuiColor::Blue, ..Default::default() }).build()); "custom tui theme multiple")]
1590+
#[test_case("trip example.com --tui-theme foo", Err(anyhow!("error: invalid value 'foo' for '--tui-theme <TUI_THEME>': unknown theme: foo (available themes: light, trippy) For more information, try '--help'.")); "invalid tui theme name")]
15771591
#[test_case("trip example.com --tui-theme-colors bg-color=0", Err(anyhow!("error: invalid value 'bg-color=0' for '--tui-theme-colors <TUI_THEME_COLORS>': unknown color: 0 For more information, try '--help'.")); "invalid tui theme truncated hex value")]
15781592
#[test_case("trip example.com --tui-theme-colors bg-color=foo", Err(anyhow!("error: invalid value 'bg-color=foo' for '--tui-theme-colors <TUI_THEME_COLORS>': unknown color: foo For more information, try '--help'. ")); "invalid tui theme invalid named color")]
15791593
#[test_case("trip example.com --tui-theme-colors foo-color=red", Err(anyhow!("error: invalid value 'foo-color=red' for '--tui-theme-colors <TUI_THEME_COLORS>': Matching variant not found For more information, try '--help'.")); "invalid tui theme invalid item")]
@@ -1669,6 +1683,7 @@ mod tests {
16691683
#[test_case("trip --print-config-template", Ok(TrippyAction::PrintConfigTemplate); "print config template")]
16701684
#[test_case("trip --print-tui-binding-commands", Ok(TrippyAction::PrintTuiBindingCommands); "print the tui binding commands")]
16711685
#[test_case("trip --print-tui-theme-items", Ok(TrippyAction::PrintTuiThemeItems); "print the tui theme items")]
1686+
#[test_case("trip --print-tui-themes", Ok(TrippyAction::PrintTuiThemes); "print the tui themes")]
16721687
#[test_case("trip --generate elvish", Ok(TrippyAction::PrintShellCompletions(Shell::Elvish)); "generate elvish shell completions")]
16731688
#[test_case("trip --generate fish", Ok(TrippyAction::PrintShellCompletions(Shell::Fish)); "generate fish shell completions")]
16741689
#[test_case("trip --generate powershell", Ok(TrippyAction::PrintShellCompletions(Shell::PowerShell)); "generate powershell shell completions")]

crates/trippy-tui/src/config/cmd.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::config::theme::TuiThemeItem;
33
use crate::config::{
44
AddressFamilyConfig, AddressMode, AsMode, DnsResolveMethodConfig, GeoIpMode, IcmpExtensionMode,
55
LogFormat, LogSpanEvents, Mode, MultipathStrategyConfig, ProtocolConfig, TuiColor,
6-
TuiKeyBinding,
6+
TuiKeyBinding, TuiTheme,
77
};
88
use anyhow::anyhow;
99
use clap::Parser;
@@ -18,7 +18,7 @@ use std::time::Duration;
1818
#[command(name = "trip", author, version, about, long_about = None, arg_required_else_help(true), styles=clap_cargo::style::CLAP_STYLING)]
1919
pub struct Args {
2020
/// A space delimited list of hostnames and IPs to trace
21-
#[arg(required_unless_present_any(["print_tui_theme_items", "print_tui_binding_commands", "print_config_template", "generate", "generate_man", "print_locales"]), env = "TRIP_TARGETS")]
21+
#[arg(required_unless_present_any(["print_tui_theme_items", "print_tui_themes", "print_tui_binding_commands", "print_config_template", "generate", "generate_man", "print_locales"]), env = "TRIP_TARGETS")]
2222
pub targets: Vec<String>,
2323

2424
/// Config file
@@ -236,6 +236,10 @@ pub struct Args {
236236
#[arg(long, env = "TRIP_TUI_TIMEZONE")]
237237
pub tui_timezone: Option<String>,
238238

239+
/// The TUI theme [default: trippy]
240+
#[arg(long, value_parser = parse_tui_theme, env = "TRIP_TUI_THEME")]
241+
pub tui_theme: Option<String>,
242+
239243
/// The TUI theme colors [item=color,item=color,..]
240244
#[arg(long, value_delimiter(','), value_parser = parse_tui_theme_color_value, env = "TRIP_TUI_THEME_COLORS")]
241245
pub tui_theme_colors: Vec<(TuiThemeItem, TuiColor)>,
@@ -244,6 +248,10 @@ pub struct Args {
244248
#[arg(long, env = "TRIP_PRINT_TUI_THEME_ITEMS")]
245249
pub print_tui_theme_items: bool,
246250

251+
/// Print all built-in TUI themes and exit
252+
#[arg(long, env = "TRIP_PRINT_TUI_THEMES")]
253+
pub print_tui_themes: bool,
254+
247255
/// The TUI key bindings [command=key,command=key,..]
248256
#[arg(long, value_delimiter(','), value_parser = parse_tui_binding_value, env = "TRIP_TUI_KEY_BINDINGS")]
249257
pub tui_key_bindings: Vec<(TuiCommandItem, TuiKeyBinding)>,
@@ -302,6 +310,18 @@ fn parse_tui_theme_color_value(value: &str) -> anyhow::Result<(TuiThemeItem, Tui
302310
Ok((item, color))
303311
}
304312

313+
fn parse_tui_theme(value: &str) -> anyhow::Result<String> {
314+
let themes = TuiTheme::available();
315+
if themes.contains(&value) {
316+
Ok(value.to_string())
317+
} else {
318+
Err(anyhow!(
319+
"unknown theme: {value} (available themes: {})",
320+
themes.join(", ")
321+
))
322+
}
323+
}
324+
305325
fn parse_tui_binding_value(value: &str) -> anyhow::Result<(TuiCommandItem, TuiKeyBinding)> {
306326
let pos = value
307327
.find('=')

crates/trippy-tui/src/config/constants.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pub const DEFAULT_TUI_ADDRESS_MODE: AddressMode = AddressMode::Host;
4343
/// The default value for `tui-refresh-rate`.
4444
pub const DEFAULT_TUI_REFRESH_RATE: Duration = Duration::from_millis(100);
4545

46+
/// The default value for `tui-theme`.
47+
pub const DEFAULT_TUI_THEME: &str = "trippy";
48+
4649
/// The default value for `dns-resolve-method`.
4750
pub const DEFAULT_DNS_RESOLVE_METHOD: DnsResolveMethodConfig = DnsResolveMethodConfig::System;
4851

crates/trippy-tui/src/config/file.rs

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ pub struct ConfigTui {
252252
pub tui_custom_columns: Option<String>,
253253
pub tui_locale: Option<String>,
254254
pub tui_timezone: Option<String>,
255+
pub tui_theme: Option<String>,
255256
#[serde(rename = "tui-max-samples")]
256257
pub deprecated_tui_max_samples: Option<usize>,
257258
#[serde(rename = "tui-max-flows")]
@@ -272,14 +273,15 @@ impl Default for ConfigTui {
272273
tui_max_addrs: Some(super::constants::DEFAULT_TUI_MAX_ADDRS),
273274
tui_locale: None,
274275
tui_timezone: None,
276+
tui_theme: Some(String::from(super::constants::DEFAULT_TUI_THEME)),
275277
geoip_mmdb_file: None,
276278
deprecated_tui_max_samples: None,
277279
deprecated_tui_max_flows: None,
278280
}
279281
}
280282
}
281283

282-
#[derive(Debug, Eq, PartialEq, Deserialize)]
284+
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Default)]
283285
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
284286
#[expect(clippy::struct_field_names)]
285287
pub struct ConfigThemeColors {
@@ -323,58 +325,6 @@ pub struct ConfigThemeColors {
323325
pub info_bar_text_color: Option<TuiColor>,
324326
}
325327

326-
impl Default for ConfigThemeColors {
327-
fn default() -> Self {
328-
let theme = super::theme::TuiTheme::default();
329-
Self {
330-
bg_color: Some(theme.bg),
331-
border_color: Some(theme.border),
332-
text_color: Some(theme.text),
333-
tab_text_color: Some(theme.tab_text),
334-
hops_table_header_bg_color: Some(theme.hops_table_header_bg),
335-
hops_table_header_text_color: Some(theme.hops_table_header_text),
336-
hops_table_row_active_text_color: Some(theme.hops_table_row_active_text),
337-
hops_table_row_inactive_text_color: Some(theme.hops_table_row_inactive_text),
338-
hops_table_row_active_selected_bg_color: Some(theme.hops_table_row_active_selected_bg),
339-
hops_table_row_active_selected_text_color: Some(
340-
theme.hops_table_row_active_selected_text,
341-
),
342-
hops_table_row_inactive_selected_bg_color: Some(
343-
theme.hops_table_row_inactive_selected_bg,
344-
),
345-
hops_table_row_inactive_selected_text_color: Some(
346-
theme.hops_table_row_inactive_selected_text,
347-
),
348-
hops_chart_selected_color: Some(theme.hops_chart_selected),
349-
hops_chart_unselected_color: Some(theme.hops_chart_unselected),
350-
hops_chart_axis_color: Some(theme.hops_chart_axis),
351-
frequency_chart_bar_color: Some(theme.frequency_chart_bar),
352-
frequency_chart_text_color: Some(theme.frequency_chart_text),
353-
flows_chart_bar_selected_color: Some(theme.flows_chart_bar_selected),
354-
flows_chart_bar_unselected_color: Some(theme.flows_chart_bar_unselected),
355-
flows_chart_text_current_color: Some(theme.flows_chart_text_current),
356-
flows_chart_text_non_current_color: Some(theme.flows_chart_text_non_current),
357-
samples_chart_color: Some(theme.samples_chart),
358-
samples_chart_lost_color: Some(theme.samples_chart_lost),
359-
help_dialog_bg_color: Some(theme.help_dialog_bg),
360-
help_dialog_text_color: Some(theme.help_dialog_text),
361-
settings_dialog_bg_color: Some(theme.settings_dialog_bg),
362-
settings_tab_text_color: Some(theme.settings_tab_text),
363-
settings_table_header_text_color: Some(theme.settings_table_header_text),
364-
settings_table_header_bg_color: Some(theme.settings_table_header_bg),
365-
settings_table_row_text_color: Some(theme.settings_table_row_text),
366-
map_world_color: Some(theme.map_world),
367-
map_radius_color: Some(theme.map_radius),
368-
map_selected_color: Some(theme.map_selected),
369-
map_info_panel_border_color: Some(theme.map_info_panel_border),
370-
map_info_panel_bg_color: Some(theme.map_info_panel_bg),
371-
map_info_panel_text_color: Some(theme.map_info_panel_text),
372-
info_bar_bg_color: Some(theme.info_bar_bg),
373-
info_bar_text_color: Some(theme.info_bar_text),
374-
}
375-
}
376-
}
377-
378328
#[derive(Debug, Eq, PartialEq, Deserialize)]
379329
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
380330
pub struct ConfigBindings {

0 commit comments

Comments
 (0)