anchor_cli/debugger/
highlight.rs1use {
17 ratatui::text::{Line, Span},
18 std::sync::OnceLock,
19 syntect::{
20 easy::HighlightLines,
21 highlighting::{Theme, ThemeSet},
22 parsing::{syntax_definition::SyntaxDefinition, SyntaxReference, SyntaxSet},
23 },
24 terminal_colorsaurus::{theme_mode, QueryOptions, ThemeMode},
25};
26
27const SBPF_SYNTAX_YAML: &str = include_str!("sbpf.sublime-syntax");
30
31static DETECTED_MODE: OnceLock<ThemeMode> = OnceLock::new();
36
37pub fn detect_theme_mode_once() {
45 let _ = DETECTED_MODE.set(theme_mode(QueryOptions::default()).unwrap_or(ThemeMode::Dark));
46}
47
48struct Ctx {
49 syntaxes: SyntaxSet,
50 theme: Theme,
51 rust_syntax: SyntaxReference,
52 asm_syntax: SyntaxReference,
53}
54
55fn ctx() -> &'static Ctx {
56 static C: OnceLock<Ctx> = OnceLock::new();
57 C.get_or_init(|| {
58 let mut builder = two_face::syntax::extra_newlines().into_builder();
65 let sbpf_def =
66 SyntaxDefinition::load_from_str(SBPF_SYNTAX_YAML, true, Some("SBPF Assembly"))
67 .expect("compile bundled sbpf.sublime-syntax");
68 builder.add(sbpf_def);
69 let syntaxes = builder.build();
70 let themes = ThemeSet::load_defaults();
71 let (dark_name, light_name) = ("base16-eighties.dark", "Solarized (light)");
76 let theme_name = match DETECTED_MODE.get() {
77 Some(ThemeMode::Light) => light_name,
78 _ => dark_name,
79 };
80 let theme = themes
81 .themes
82 .get(theme_name)
83 .cloned()
84 .or_else(|| themes.themes.get(dark_name).cloned())
85 .unwrap_or_else(|| {
86 themes
87 .themes
88 .values()
89 .next()
90 .cloned()
91 .expect("default theme")
92 });
93
94 let rust_syntax = syntaxes
95 .find_syntax_by_token("rs")
96 .or_else(|| syntaxes.find_syntax_by_name("Rust"))
97 .unwrap_or_else(|| syntaxes.find_syntax_plain_text())
98 .clone();
99
100 let asm_syntax = syntaxes
104 .find_syntax_by_name("SBPF Assembly")
105 .or_else(|| syntaxes.find_syntax_by_name("Assembly x86 (NASM)"))
106 .or_else(|| syntaxes.find_syntax_by_token("asm"))
107 .unwrap_or_else(|| syntaxes.find_syntax_plain_text())
108 .clone();
109
110 Ctx {
111 syntaxes,
112 theme,
113 rust_syntax,
114 asm_syntax,
115 }
116 })
117}
118
119pub fn highlight_rust(line: &str) -> Line<'static> {
127 highlight_with(&ctx().rust_syntax, line)
128}
129
130pub fn highlight_asm(line: &str) -> Line<'static> {
133 highlight_with(&ctx().asm_syntax, line)
134}
135
136fn syntect_style_to_ratatui(style: syntect::highlighting::Style) -> ratatui::style::Style {
138 fn translate_colour(
139 syntect_color: syntect::highlighting::Color,
140 ) -> Option<ratatui::style::Color> {
141 match syntect_color {
142 syntect::highlighting::Color { r, g, b, a } if a > 0 => {
143 Some(ratatui::style::Color::Rgb(r, g, b))
144 }
145 _ => None,
146 }
147 }
148
149 fn translate_font_style(
150 syntect_font_style: syntect::highlighting::FontStyle,
151 ) -> ratatui::style::Modifier {
152 use {ratatui::style::Modifier, syntect::highlighting::FontStyle};
153 match syntect_font_style {
154 x if x == FontStyle::empty() => Modifier::empty(),
155 x if x == FontStyle::BOLD => Modifier::BOLD,
156 x if x == FontStyle::ITALIC => Modifier::ITALIC,
157 x if x == FontStyle::UNDERLINE => Modifier::UNDERLINED,
158 x if x == FontStyle::BOLD | FontStyle::ITALIC => Modifier::BOLD | Modifier::ITALIC,
159 x if x == FontStyle::BOLD | FontStyle::UNDERLINE => {
160 Modifier::BOLD | Modifier::UNDERLINED
161 }
162 x if x == FontStyle::ITALIC | FontStyle::UNDERLINE => {
163 Modifier::ITALIC | Modifier::UNDERLINED
164 }
165 x if x == FontStyle::BOLD | FontStyle::ITALIC | FontStyle::UNDERLINE => {
166 Modifier::BOLD | Modifier::ITALIC | Modifier::UNDERLINED
167 }
168 _ => Modifier::empty(),
169 }
170 }
171
172 ratatui::style::Style {
173 fg: translate_colour(style.foreground),
174 bg: None,
179 underline_color: translate_colour(style.foreground),
180 add_modifier: translate_font_style(style.font_style),
181 sub_modifier: ratatui::style::Modifier::empty(),
182 }
183}
184
185fn highlight_with(syntax: &SyntaxReference, line: &str) -> Line<'static> {
186 let c = ctx();
187 let mut hl = HighlightLines::new(syntax, &c.theme);
188 let owned_line = if line.ends_with('\n') {
191 line.to_owned()
192 } else {
193 format!("{line}\n")
194 };
195 let regions = match hl.highlight_line(&owned_line, &c.syntaxes) {
196 Ok(r) => r,
197 Err(_) => return Line::from(Span::raw(line.to_owned())),
198 };
199 let spans: Vec<Span<'static>> = regions
200 .into_iter()
201 .filter_map(|(style, text)| {
202 let trimmed = text.trim_end_matches('\n');
205 if trimmed.is_empty() {
206 return None;
207 }
208 let style = syntect_style_to_ratatui(style);
209 Some(Span::styled(trimmed.to_owned(), style))
210 })
211 .collect();
212 if spans.is_empty() {
213 Line::from(Span::raw(line.to_owned()))
214 } else {
215 Line::from(spans)
216 }
217}