use std::collections::HashMap;
use serde::{
Deserialize,
Serialize,
};
#[macro_export]
macro_rules! for_each_tui_theme_style {
($callback:ident) => {
$callback!(
inactive_border,
active_border,
popup_border,
app_title,
help_popup,
inline_timestamp,
cli_flag,
help_key,
help_desc,
fancy_help_desc,
pid_success,
pid_failure,
pid_enoent,
pid_in_msg,
comm,
tracer_info,
tracer_warning,
tracer_error,
new_child_pid,
tracer_event,
inline_tracer_error,
partial_ok,
filename,
modified_fd_in_cmdline,
removed_fd_in_cmdline,
cloexec_fd_in_cmdline,
added_fd_in_cmdline,
arg0,
cwd,
deleted_env_var,
modified_env_var,
added_env_var,
argv,
search_match,
query_no_match,
query_match_current_no,
query_match_total_cnt,
empty_field,
uid_gid_name,
uid_gid_value,
exec_result_success,
exec_result_failure,
value_unknown,
fd_closed,
plus_sign,
minus_sign,
equal_sign,
added_env_key,
added_env_val,
removed_env_key,
removed_env_val,
unchanged_env_key,
unchanged_env_val,
fd_label,
fd_number_label,
sublabel,
selected_label,
label,
selection_indicator,
open_flag_cloexec,
open_flag_access_mode,
open_flag_creation,
open_flag_status,
open_flag_other,
visual_separator,
error_popup,
info_popup,
active_tab,
status_process_running,
status_process_paused,
status_process_detached,
status_exec_error,
status_process_exited_normally,
status_process_exited_abnormally,
status_process_killed,
status_process_terminated,
status_process_interrupted,
status_process_segfault,
status_process_aborted,
status_process_sigill,
status_process_signaled,
status_internal_failure,
breakpoint_title_selected,
breakpoint_title,
breakpoint_pattern_type_label,
breakpoint_pattern,
breakpoint_info_label,
breakpoint_info_label_active,
breakpoint_info_value,
hit_entry_pid,
hit_entry_plain_text,
hit_entry_breakpoint_stop,
hit_entry_breakpoint_pattern,
hit_entry_no_breakpoint_pattern,
hit_manager_default_command,
hit_manager_no_default_command,
);
};
}
macro_rules! define_theme_spec {
($($style_field:ident),* $(,)?) => {
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct ThemeSpec {
$(pub $style_field: Option<StyleSpec>,)*
pub backtrace_parent_spawns: Option<SpanSpec>,
pub backtrace_parent_becomes: Option<SpanSpec>,
pub backtrace_parent_unknown: Option<SpanSpec>,
#[serde(flatten)]
pub extra: HashMap<String, toml::Value>,
}
};
}
crate::for_each_tui_theme_style!(define_theme_spec);
impl ThemeSpec {
pub fn warn_unknown_fields(&self, source: &str, section: Option<&str>) {
for key in self.extra.keys() {
if let Some(section) = section {
tracing::warn!("Unknown key '{key}' in [{section}] in {source} will be ignored");
} else {
tracing::warn!("Unknown key '{key}' in {source} will be ignored");
}
}
macro_rules! warn_style {
($($field:ident),* $(,)?) => {
$(
if let Some(ref spec) = self.$field {
let key = kebab_case(stringify!($field));
spec.warn_unknown_fields(source, &key, section);
}
)*
};
}
crate::for_each_tui_theme_style!(warn_style);
macro_rules! warn_span {
($($field:ident),* $(,)?) => {
$(
if let Some(ref spec) = self.$field {
let key = kebab_case(stringify!($field));
spec.style.warn_unknown_fields(source, &key, section);
}
)*
};
}
warn_span!(
backtrace_parent_spawns,
backtrace_parent_becomes,
backtrace_parent_unknown,
);
}
}
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct StyleSpec {
pub fg: Option<ThemeColor>,
pub bg: Option<ThemeColor>,
pub underline_color: Option<ThemeColor>,
#[serde(default)]
pub modifiers: Vec<ThemeModifier>,
#[serde(default)]
pub remove_modifiers: Vec<ThemeModifier>,
#[serde(flatten)]
pub extra: HashMap<String, toml::Value>,
}
impl StyleSpec {
pub fn warn_unknown_fields(&self, source: &str, parent_key: &str, section: Option<&str>) {
for unknown_key in self.extra.keys() {
if let Some(section) = section {
tracing::warn!(
"Unknown key '{unknown_key}' for '{parent_key}' in [{section}] in {source} will be ignored"
);
} else {
tracing::warn!(
"Unknown key '{unknown_key}' for '{parent_key}' in {source} will be ignored"
);
}
}
}
}
fn kebab_case(field: &str) -> String {
field.replace('_', "-")
}
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct SpanSpec {
pub content: Option<String>,
#[serde(flatten)]
pub style: StyleSpec,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ThemeColor {
Named(String),
Indexed(u8),
Rgb { r: u8, g: u8, b: u8 },
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ThemeModifier {
Bold,
Dim,
Italic,
Underlined,
SlowBlink,
RapidBlink,
Reversed,
Hidden,
CrossedOut,
}
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct ThemeFile {
pub tui: Option<ThemeSpec>,
#[serde(flatten)]
pub extra: HashMap<String, toml::Value>,
}
impl ThemeFile {
pub fn warn_unknown_sections(&self, source: &str) {
for key in self.extra.keys() {
tracing::warn!(
"Unknown key '{key}' at top level in theme file {source} will be ignored; theme keys must be under [tui]"
);
}
}
}