1use std::collections::HashMap;
2
3use serde::{
4 Deserialize,
5 Serialize,
6};
7
8#[macro_export]
13macro_rules! for_each_tui_theme_style {
14 ($callback:ident) => {
15 $callback!(
16 inactive_border,
17 active_border,
18 popup_border,
19 app_title,
20 help_popup,
21 inline_timestamp,
22 cli_flag,
23 help_key,
24 help_desc,
25 fancy_help_desc,
26 pid_success,
27 pid_failure,
28 pid_enoent,
29 pid_in_msg,
30 comm,
31 tracer_info,
32 tracer_warning,
33 tracer_error,
34 new_child_pid,
35 tracer_event,
36 inline_tracer_error,
37 partial_ok,
38 filename,
39 modified_fd_in_cmdline,
40 removed_fd_in_cmdline,
41 cloexec_fd_in_cmdline,
42 added_fd_in_cmdline,
43 arg0,
44 cwd,
45 deleted_env_var,
46 modified_env_var,
47 added_env_var,
48 argv,
49 search_match,
50 query_no_match,
51 query_match_current_no,
52 query_match_total_cnt,
53 empty_field,
54 uid_gid_name,
55 uid_gid_value,
56 exec_result_success,
57 exec_result_failure,
58 value_unknown,
59 fd_closed,
60 plus_sign,
61 minus_sign,
62 equal_sign,
63 added_env_key,
64 added_env_val,
65 removed_env_key,
66 removed_env_val,
67 unchanged_env_key,
68 unchanged_env_val,
69 fd_label,
70 fd_number_label,
71 sublabel,
72 selected_label,
73 label,
74 selection_indicator,
75 open_flag_cloexec,
76 open_flag_access_mode,
77 open_flag_creation,
78 open_flag_status,
79 open_flag_other,
80 visual_separator,
81 error_popup,
82 info_popup,
83 active_tab,
84 status_process_running,
85 status_process_paused,
86 status_process_detached,
87 status_exec_error,
88 status_process_exited_normally,
89 status_process_exited_abnormally,
90 status_process_killed,
91 status_process_terminated,
92 status_process_interrupted,
93 status_process_segfault,
94 status_process_aborted,
95 status_process_sigill,
96 status_process_signaled,
97 status_internal_failure,
98 breakpoint_title_selected,
99 breakpoint_title,
100 breakpoint_pattern_type_label,
101 breakpoint_pattern,
102 breakpoint_info_label,
103 breakpoint_info_label_active,
104 breakpoint_info_value,
105 hit_entry_pid,
106 hit_entry_plain_text,
107 hit_entry_breakpoint_stop,
108 hit_entry_breakpoint_pattern,
109 hit_entry_no_breakpoint_pattern,
110 hit_manager_default_command,
111 hit_manager_no_default_command,
112 );
113 };
114}
115
116macro_rules! define_theme_spec {
117 ($($style_field:ident),* $(,)?) => {
118 #[derive(Debug, Default, Clone, Deserialize, Serialize)]
121 #[serde(rename_all = "kebab-case")]
122 pub struct ThemeSpec {
123 $(pub $style_field: Option<StyleSpec>,)*
124 pub backtrace_parent_spawns: Option<SpanSpec>,
125 pub backtrace_parent_becomes: Option<SpanSpec>,
126 pub backtrace_parent_unknown: Option<SpanSpec>,
127 #[serde(flatten)]
129 pub extra: HashMap<String, toml::Value>,
130 }
131 };
132}
133
134crate::for_each_tui_theme_style!(define_theme_spec);
135
136impl ThemeSpec {
137 pub fn warn_unknown_fields(&self, source: &str, section: Option<&str>) {
143 for key in self.extra.keys() {
144 if let Some(section) = section {
145 tracing::warn!("Unknown key '{key}' in [{section}] in {source} will be ignored");
146 } else {
147 tracing::warn!("Unknown key '{key}' in {source} will be ignored");
148 }
149 }
150
151 macro_rules! warn_style {
152 ($($field:ident),* $(,)?) => {
153 $(
154 if let Some(ref spec) = self.$field {
155 let key = kebab_case(stringify!($field));
156 spec.warn_unknown_fields(source, &key, section);
157 }
158 )*
159 };
160 }
161 crate::for_each_tui_theme_style!(warn_style);
162
163 macro_rules! warn_span {
164 ($($field:ident),* $(,)?) => {
165 $(
166 if let Some(ref spec) = self.$field {
167 let key = kebab_case(stringify!($field));
168 spec.style.warn_unknown_fields(source, &key, section);
169 }
170 )*
171 };
172 }
173 warn_span!(
174 backtrace_parent_spawns,
175 backtrace_parent_becomes,
176 backtrace_parent_unknown,
177 );
178 }
179}
180
181#[derive(Debug, Default, Clone, Deserialize, Serialize)]
184#[serde(rename_all = "kebab-case")]
185pub struct StyleSpec {
186 pub fg: Option<ThemeColor>,
187 pub bg: Option<ThemeColor>,
188 pub underline_color: Option<ThemeColor>,
189 #[serde(default)]
190 pub modifiers: Vec<ThemeModifier>,
191 #[serde(default)]
192 pub remove_modifiers: Vec<ThemeModifier>,
193 #[serde(flatten)]
195 pub extra: HashMap<String, toml::Value>,
196}
197
198impl StyleSpec {
199 pub fn warn_unknown_fields(&self, source: &str, parent_key: &str, section: Option<&str>) {
200 for unknown_key in self.extra.keys() {
201 if let Some(section) = section {
202 tracing::warn!(
203 "Unknown key '{unknown_key}' for '{parent_key}' in [{section}] in {source} will be ignored"
204 );
205 } else {
206 tracing::warn!(
207 "Unknown key '{unknown_key}' for '{parent_key}' in {source} will be ignored"
208 );
209 }
210 }
211 }
212}
213
214fn kebab_case(field: &str) -> String {
215 field.replace('_', "-")
216}
217
218#[derive(Debug, Default, Clone, Deserialize, Serialize)]
221#[serde(rename_all = "kebab-case")]
222pub struct SpanSpec {
223 pub content: Option<String>,
224 #[serde(flatten)]
225 pub style: StyleSpec,
226}
227
228#[derive(Debug, Clone, Deserialize, Serialize)]
230#[serde(untagged)]
231pub enum ThemeColor {
232 Named(String),
233 Indexed(u8),
234 Rgb { r: u8, g: u8, b: u8 },
235}
236
237#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
239#[serde(rename_all = "kebab-case")]
240pub enum ThemeModifier {
241 Bold,
242 Dim,
243 Italic,
244 Underlined,
245 SlowBlink,
246 RapidBlink,
247 Reversed,
248 Hidden,
249 CrossedOut,
250}
251
252#[derive(Debug, Default, Deserialize, Serialize)]
263pub struct ThemeFile {
264 pub tui: Option<ThemeSpec>,
266 #[serde(flatten)]
268 pub extra: HashMap<String, toml::Value>,
269}
270
271impl ThemeFile {
272 pub fn warn_unknown_sections(&self, source: &str) {
274 for key in self.extra.keys() {
275 tracing::warn!(
276 "Unknown key '{key}' at top level in theme file {source} will be ignored; theme keys must be under [tui]"
277 );
278 }
279 }
280}