tui_lipan/widgets/text_area/
color.rs1use std::any::Any;
2use std::hash::{Hash, Hasher};
3use std::rc::Rc;
4
5use crate::style::Span;
6
7#[derive(Clone, Copy, Debug)]
9pub struct TextAreaColorInput<'a> {
10 pub value: &'a str,
12 pub language: Option<&'a str>,
14 pub theme: Option<&'a str>,
16}
17
18pub type TextAreaColorLines = Vec<Vec<Span>>;
20
21pub trait TextAreaColorStrategy: Any {
23 fn highlight(&self, input: TextAreaColorInput<'_>) -> TextAreaColorLines;
25
26 fn cache_key(&self) -> u64 {
28 0
29 }
30
31 fn as_any(&self) -> &dyn Any;
33
34 fn as_any_mut(&mut self) -> &mut dyn Any;
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39pub(crate) struct TextAreaColorKey {
40 pub value_hash: u64,
41 pub strategy_hash: u64,
42 pub language_hash: u64,
43 pub theme_hash: u64,
44}
45
46#[derive(Clone, Debug, Default)]
47pub(crate) struct TextAreaColorCache {
48 pub key: Option<TextAreaColorKey>,
49 pub lines: TextAreaColorLines,
50 pub line_starts: Vec<usize>,
51 pub line_lengths: Vec<usize>,
52}
53
54impl TextAreaColorCache {
55 pub(crate) fn update(
56 &mut self,
57 strategy: Option<&Rc<dyn TextAreaColorStrategy>>,
58 value: &str,
59 value_hash: u64,
60 language: Option<&str>,
61 theme: Option<&str>,
62 ) {
63 let Some(strategy) = strategy else {
64 self.key = None;
65 self.lines.clear();
66 return;
67 };
68
69 let key = TextAreaColorKey {
70 value_hash,
71 strategy_hash: strategy.cache_key(),
72 language_hash: hash_optional_str(language),
73 theme_hash: hash_optional_str(theme),
74 };
75
76 if self.key == Some(key) {
77 return;
78 }
79
80 let input = TextAreaColorInput {
81 value,
82 language,
83 theme,
84 };
85
86 let lines = normalize_color_lines(strategy.highlight(input), value);
87 self.lines = lines;
88 let (line_starts, line_lengths) = line_meta(value);
89 self.line_starts = line_starts;
90 self.line_lengths = line_lengths;
91 self.key = Some(key);
92 }
93}
94
95fn logical_lines(value: &str) -> Vec<&str> {
96 if value.is_empty() {
97 vec![""]
98 } else {
99 value.split('\n').collect()
100 }
101}
102
103fn normalize_color_lines(mut lines: TextAreaColorLines, value: &str) -> TextAreaColorLines {
104 let expected = logical_lines(value).len();
105 if lines.len() > expected {
106 lines.truncate(expected);
107 }
108 while lines.len() < expected {
109 lines.push(vec![Span::new("")]);
110 }
111 lines
112}
113
114fn line_meta(value: &str) -> (Vec<usize>, Vec<usize>) {
115 if value.is_empty() {
116 return (vec![0], vec![0]);
117 }
118
119 let mut starts = Vec::new();
120 let mut lengths = Vec::new();
121 let mut current = 0usize;
122 for line in value.split('\n') {
123 starts.push(current);
124 lengths.push(line.len());
125 current = current.saturating_add(line.len()).saturating_add(1);
126 }
127 (starts, lengths)
128}
129
130fn hash_optional_str(value: Option<&str>) -> u64 {
131 let mut hasher = rustc_hash::FxHasher::default();
132 match value {
133 Some(v) => {
134 1u8.hash(&mut hasher);
135 v.hash(&mut hasher);
136 }
137 None => {
138 0u8.hash(&mut hasher);
139 }
140 }
141 hasher.finish()
142}