1use std::collections::HashMap;
2
3use lsp_types::SemanticTokens;
4use ratatui::{
5 style::{Color, Style},
6 text::Span,
7};
8use syntect::{
9 easy::HighlightLines,
10 highlighting::{Theme, ThemeSet},
11 parsing::{SyntaxDefinition, SyntaxReference, SyntaxSet, SyntaxSetBuilder},
12};
13
14use super::Renderer;
15use crate::document::DocumentBuffer;
16
17#[derive(Debug, Clone)]
19pub struct SemanticToken {
20 pub start: usize, pub length: usize, pub token_type: u32, pub modifiers: u32, }
25
26pub fn decode_semantic_tokens(
28 tokens: &SemanticTokens,
29 _content: &str,
30) -> HashMap<usize, Vec<SemanticToken>> {
31 let mut result: HashMap<usize, Vec<SemanticToken>> = HashMap::new();
32
33 let data = &tokens.data;
34 let mut current_line = 0;
35 let mut current_start = 0;
36
37 for lsp_token in data {
38 let delta_line = lsp_token.delta_line;
39 let delta_start = lsp_token.delta_start;
40 let length = lsp_token.length;
41 let token_type = lsp_token.token_type;
42 let token_modifiers_bitset = lsp_token.token_modifiers_bitset;
43
44 if delta_line > 0 {
46 current_line += delta_line as usize;
47 current_start = delta_start as usize;
48 } else {
49 current_start += delta_start as usize;
50 }
51
52 let token = SemanticToken {
54 start: current_start,
55 length: length as usize,
56 token_type,
57 modifiers: token_modifiers_bitset,
58 };
59
60 result.entry(current_line).or_default().push(token);
61 }
62
63 result
64}
65
66pub struct CodeRenderer {
72 default_syntax_set: SyntaxSet,
74 mq_syntax_set: SyntaxSet,
76 theme: Theme,
78 theme_set: ThemeSet,
80 semantic_tokens: HashMap<usize, Vec<SemanticToken>>,
82 use_semantic_tokens: bool,
84}
85
86const MQ_SUBLIME_SYNTAX: &str = include_str!("../../mq.sublime-syntax");
88
89impl CodeRenderer {
90 pub fn new() -> Self {
91 Self::with_theme("base16-ocean.dark")
92 }
93
94 pub fn with_theme(theme_name: &str) -> Self {
96 let default_syntax_set = SyntaxSet::load_defaults_newlines();
97 let mq_syntax_set = Self::build_mq_syntax_set();
98 let theme_set = ThemeSet::load_defaults();
99 let theme = theme_set
100 .themes
101 .get(theme_name)
102 .or_else(|| theme_set.themes.get("base16-ocean.dark"))
103 .or_else(|| theme_set.themes.values().next())
104 .cloned()
105 .unwrap_or_default();
106
107 Self {
108 default_syntax_set,
109 mq_syntax_set,
110 theme,
111 theme_set,
112 semantic_tokens: HashMap::new(),
113 use_semantic_tokens: false,
114 }
115 }
116
117 pub fn set_theme(&mut self, theme_name: &str) {
119 if let Some(theme) = self.theme_set.themes.get(theme_name) {
120 self.theme = theme.clone();
121 }
122 }
123
124 pub fn available_themes() -> Vec<String> {
126 let theme_set = ThemeSet::load_defaults();
127 let mut themes: Vec<String> = theme_set.themes.keys().cloned().collect();
128 themes.sort();
129 themes
130 }
131
132 fn build_mq_syntax_set() -> SyntaxSet {
134 let mut builder = SyntaxSetBuilder::new();
135 builder.add_plain_text_syntax();
136
137 if let Ok(mq_syntax) = SyntaxDefinition::load_from_str(
138 MQ_SUBLIME_SYNTAX,
139 true, Some("mq"),
141 ) {
142 builder.add(mq_syntax);
143 }
144
145 builder.build()
146 }
147
148 pub fn set_semantic_tokens(&mut self, tokens: HashMap<usize, Vec<SemanticToken>>) {
150 self.semantic_tokens = tokens;
151 }
152
153 pub fn clear_semantic_tokens(&mut self) {
155 self.semantic_tokens.clear();
156 }
157
158 pub fn set_use_semantic_tokens(&mut self, use_semantic_tokens: bool) {
160 self.use_semantic_tokens = use_semantic_tokens;
161 }
162
163 fn get_syntax(&self, language: &str) -> Option<(&SyntaxReference, &SyntaxSet)> {
165 if let Some(syntax) = self.mq_syntax_set.find_syntax_by_token(language) {
166 return Some((syntax, &self.mq_syntax_set));
167 }
168
169 self.default_syntax_set
170 .find_syntax_by_token(language)
171 .map(|s| (s, &self.default_syntax_set))
172 }
173
174 fn render_with_semantic_tokens(
176 &self,
177 content: &str,
178 tokens: &[SemanticToken],
179 ) -> Vec<Span<'_>> {
180 if tokens.is_empty() {
181 return vec![Span::raw(content.to_string())];
182 }
183
184 let mut spans = Vec::new();
185 let mut last_end = 0;
186
187 for token in tokens {
188 if token.start > last_end {
190 let text = content
191 .chars()
192 .skip(last_end)
193 .take(token.start - last_end)
194 .collect::<String>();
195 spans.push(Span::raw(text));
196 }
197
198 let text = content
200 .chars()
201 .skip(token.start)
202 .take(token.length)
203 .collect::<String>();
204 let style = self.semantic_token_style(token.token_type, token.modifiers);
205 spans.push(Span::styled(text, style));
206
207 last_end = token.start + token.length;
208 }
209
210 if last_end < content.chars().count() {
212 let text = content.chars().skip(last_end).collect::<String>();
213 spans.push(Span::raw(text));
214 }
215
216 spans
217 }
218
219 fn semantic_token_style(&self, token_type: u32, _modifiers: u32) -> Style {
221 match token_type {
224 0 => Style::default().fg(Color::Cyan), 1 => Style::default().fg(Color::Yellow), 2 => Style::default().fg(Color::Yellow), 3 => Style::default().fg(Color::Yellow), 4 => Style::default().fg(Color::Cyan), 5 => Style::default().fg(Color::Yellow), 6 => Style::default().fg(Color::Magenta), 7 => Style::default().fg(Color::White), 8 => Style::default().fg(Color::White), 9 => Style::default().fg(Color::Cyan), 10 => Style::default().fg(Color::Green), 11 => Style::default().fg(Color::Blue), 12 => Style::default().fg(Color::Blue), 13 => Style::default().fg(Color::Magenta), 14 => Style::default().fg(Color::Magenta), 15 => Style::default().fg(Color::Gray), 16 => Style::default().fg(Color::Green), 17 => Style::default().fg(Color::Green), 18 => Style::default().fg(Color::Magenta), _ => Style::default(),
244 }
245 }
246
247 fn render_with_syntect(&self, content: &str, language: &str) -> Vec<Span<'_>> {
249 if let Some((syntax, syntax_set)) = self.get_syntax(language) {
250 let mut highlighter = HighlightLines::new(syntax, &self.theme);
251
252 match highlighter.highlight_line(content, syntax_set) {
253 Ok(regions) => regions
254 .iter()
255 .map(|(style, text)| {
256 let fg_color =
257 Color::Rgb(style.foreground.r, style.foreground.g, style.foreground.b);
258 Span::styled(text.to_string(), Style::default().fg(fg_color))
259 })
260 .collect(),
261 Err(_) => vec![Span::raw(content.to_string())],
262 }
263 } else {
264 vec![Span::raw(content.to_string())]
266 }
267 }
268}
269
270impl Default for CodeRenderer {
271 fn default() -> Self {
272 Self::new()
273 }
274}
275
276impl Renderer for CodeRenderer {
277 fn render_line(
278 &self,
279 buffer: &DocumentBuffer,
280 line_idx: usize,
281 is_current_line: bool,
282 ) -> Vec<Span<'_>> {
283 let content = buffer.line(line_idx).unwrap_or("");
284
285 if is_current_line {
286 return vec![Span::styled(content.to_string(), Style::default())];
287 }
288
289 let language = match buffer.document_type() {
290 crate::document::DocumentType::Code { language } => language.as_str(),
291 _ => return vec![Span::raw(content.to_string())],
292 };
293
294 if self.use_semantic_tokens
295 && let Some(tokens) = self.semantic_tokens.get(&line_idx)
296 {
297 return self.render_with_semantic_tokens(content, tokens);
298 }
299
300 self.render_with_syntect(content, language)
301 }
302
303 fn supports_wysiwyg(&self) -> bool {
304 true }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn test_code_renderer_creation() {
314 let renderer = CodeRenderer::new();
315 assert!(renderer.supports_wysiwyg());
316 assert!(renderer.semantic_tokens.is_empty());
317 }
318
319 #[test]
320 fn test_semantic_tokens_update() {
321 let mut renderer = CodeRenderer::new();
322 let mut tokens = HashMap::new();
323 tokens.insert(
324 0,
325 vec![SemanticToken {
326 start: 0,
327 length: 3,
328 token_type: 14, modifiers: 0,
330 }],
331 );
332
333 renderer.set_semantic_tokens(tokens);
334 assert_eq!(renderer.semantic_tokens.len(), 1);
335
336 renderer.clear_semantic_tokens();
337 assert!(renderer.semantic_tokens.is_empty());
338 }
339}