clankerdiff_ratatui/
syntax.rs1use crate::color::native_color;
4pub use clankerdiff_syntax::{
5 CacheConfig, CacheKey, CacheUsage, DocumentHighlights, Fingerprint, FontStyle, HighlightSpan,
6 HighlightStats, LanguageHint, Rgba, SyntaxError, SyntaxHighlighter, SyntaxStream,
7 SyntaxStreamUpdate, SyntaxStyle, SyntaxTheme, SyntaxWorkStats, ThemedHighlighter, empty_spans,
8 resolve_language,
9};
10use ratatui::{
11 style::{Color, Modifier, Style},
12 text::{Line, Span},
13};
14
15#[must_use]
20pub fn highlighted_line(source: &str, spans: &[HighlightSpan], base: Style) -> Line<'static> {
21 let mut output = Vec::with_capacity(spans.len().saturating_mul(2).saturating_add(1));
22 let mut cursor = 0;
23 for span in spans {
24 let start = span.range.start.max(cursor);
25 let end = span.range.end;
26 if start >= end
27 || end > source.len()
28 || !source.is_char_boundary(start)
29 || !source.is_char_boundary(end)
30 {
31 continue;
32 }
33 if cursor < start {
34 output.push(Span::styled(source[cursor..start].to_owned(), base));
35 }
36 let mut modifiers = Modifier::empty();
37 modifiers.set(Modifier::BOLD, span.font_style.bold);
38 modifiers.set(Modifier::ITALIC, span.font_style.italic);
39 modifiers.set(Modifier::UNDERLINED, span.font_style.underline);
40 let syntax = Style::new()
41 .fg(native_color(
42 span.foreground,
43 base.bg.unwrap_or(Color::Black),
44 ))
45 .add_modifier(modifiers);
46 output.push(Span::styled(
47 source[start..end].to_owned(),
48 base.patch(syntax),
49 ));
50 cursor = end;
51 }
52 if cursor < source.len() {
53 output.push(Span::styled(source[cursor..].to_owned(), base));
54 }
55 if output.is_empty() {
56 output.push(Span::styled(source.to_owned(), base));
57 }
58 Line::from(output)
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use clankerdiff_theme::{FontStyle, Rgba};
65
66 #[test]
67 fn preserves_utf8_gaps_and_base_background() {
68 let base = Style::new().bg(Color::Blue).add_modifier(Modifier::DIM);
69 let line = highlighted_line(
70 "aéz",
71 &[HighlightSpan {
72 range: 1..3,
73 foreground: Rgba::new(1, 2, 3, 255),
74 font_style: FontStyle {
75 bold: true,
76 italic: false,
77 underline: false,
78 },
79 }],
80 base,
81 );
82 assert_eq!(line.to_string(), "aéz");
83 assert_eq!(line.spans[1].style.bg, Some(Color::Blue));
84 assert!(line.spans[1].style.add_modifier.contains(Modifier::BOLD));
85 assert!(line.spans[1].style.add_modifier.contains(Modifier::DIM));
86 }
87}