1use std::ops::Range;
9
10pub use fresh_core::text_property::{TextProperty, TextPropertyEntry};
12
13#[derive(Debug, Clone)]
16pub struct CollectedOverlay {
17 pub range: Range<usize>,
19 pub options: fresh_core::api::OverlayOptions,
21}
22
23#[derive(Debug, Clone, Default)]
28pub struct TextPropertyManager {
29 properties: Vec<TextProperty>,
31}
32
33impl TextPropertyManager {
34 pub fn new() -> Self {
36 Self {
37 properties: Vec::new(),
38 }
39 }
40
41 pub fn add(&mut self, property: TextProperty) {
43 let pos = self
45 .properties
46 .binary_search_by_key(&property.start, |p| p.start)
47 .unwrap_or_else(|e| e);
48 self.properties.insert(pos, property);
49 }
50
51 pub fn get_at(&self, pos: usize) -> Vec<&TextProperty> {
53 self.properties.iter().filter(|p| p.contains(pos)).collect()
54 }
55
56 pub fn get_overlapping(&self, range: &Range<usize>) -> Vec<&TextProperty> {
58 self.properties
59 .iter()
60 .filter(|p| p.overlaps(range))
61 .collect()
62 }
63
64 pub fn clear(&mut self) {
66 self.properties.clear();
67 }
68
69 pub fn remove_in_range(&mut self, range: &Range<usize>) {
71 self.properties
72 .retain(|p| !p.overlaps(range) && !range.contains(&p.start));
73 }
74
75 pub fn all(&self) -> &[TextProperty] {
77 &self.properties
78 }
79
80 pub fn is_empty(&self) -> bool {
82 self.properties.is_empty()
83 }
84
85 pub fn len(&self) -> usize {
87 self.properties.len()
88 }
89
90 pub fn set_all(&mut self, properties: Vec<TextProperty>) {
92 self.properties = properties;
93 self.properties.sort_by_key(|p| p.start);
95 }
96
97 pub fn from_entries(entries: Vec<TextPropertyEntry>) -> (String, Self, Vec<CollectedOverlay>) {
103 let mut text = String::new();
104 let mut manager = Self::new();
105 let mut collected_overlays = Vec::new();
106 let mut offset = 0;
107
108 for entry in entries {
109 let start = offset;
110 let entry_len = entry.text.len();
111 text.push_str(&entry.text);
112 let end = offset + entry_len;
113
114 if !entry.properties.is_empty() {
115 let property = TextProperty {
116 start,
117 end,
118 properties: entry.properties,
119 };
120 manager.add(property);
121 }
122
123 if let Some(style) = entry.style {
125 collected_overlays.push(CollectedOverlay {
126 range: start..end,
127 options: style,
128 });
129 }
130
131 for inline in entry.inline_overlays {
133 let abs_start = start + inline.start.min(entry_len);
134 let abs_end = start + inline.end.min(entry_len);
135 if abs_start < abs_end {
136 collected_overlays.push(CollectedOverlay {
137 range: abs_start..abs_end,
138 options: inline.style,
139 });
140 if !inline.properties.is_empty() {
142 let property = TextProperty {
143 start: abs_start,
144 end: abs_end,
145 properties: inline.properties,
146 };
147 manager.add(property);
148 }
149 }
150 }
151
152 offset = end;
153 }
154
155 (text, manager, collected_overlays)
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use serde::Deserialize;
163 use serde_json::json;
164
165 #[test]
166 fn test_text_property_contains() {
167 let prop = TextProperty::new(10, 20);
168 assert!(prop.contains(10));
169 assert!(prop.contains(15));
170 assert!(prop.contains(19));
171 assert!(!prop.contains(9));
172 assert!(!prop.contains(20));
173 }
174
175 #[test]
176 fn test_text_property_overlaps() {
177 let prop = TextProperty::new(10, 20);
178 assert!(prop.overlaps(&(5..15)));
179 assert!(prop.overlaps(&(15..25)));
180 assert!(prop.overlaps(&(10..20)));
181 assert!(prop.overlaps(&(12..18)));
182 assert!(!prop.overlaps(&(0..10)));
183 assert!(!prop.overlaps(&(20..30)));
184 }
185
186 #[test]
187 fn test_text_property_with_properties() {
188 let prop = TextProperty::new(0, 10)
189 .with_property("severity", json!("error"))
190 .with_property(
191 "location",
192 json!({"file": "test.rs", "line": 42, "column": 5}),
193 );
194
195 assert_eq!(prop.get("severity"), Some(&json!("error")));
196 assert_eq!(
197 prop.get("location"),
198 Some(&json!({"file": "test.rs", "line": 42, "column": 5}))
199 );
200 assert_eq!(prop.get("nonexistent"), None);
201 }
202
203 #[test]
204 fn test_text_property_get_as() {
205 let prop = TextProperty::new(0, 10)
206 .with_property("count", json!(42))
207 .with_property(
208 "location",
209 json!({"file": "test.rs", "line": 42, "column": 5}),
210 );
211
212 let count: Option<i64> = prop.get_as("count");
213 assert_eq!(count, Some(42));
214
215 #[derive(Debug, Deserialize, PartialEq)]
216 struct Location {
217 file: String,
218 line: u32,
219 column: u32,
220 }
221
222 let loc: Option<Location> = prop.get_as("location");
223 assert_eq!(
224 loc,
225 Some(Location {
226 file: "test.rs".to_string(),
227 line: 42,
228 column: 5,
229 })
230 );
231 }
232
233 #[test]
234 fn test_manager_add_and_get_at() {
235 let mut manager = TextPropertyManager::new();
236
237 manager.add(TextProperty::new(0, 10).with_property("id", json!("first")));
238 manager.add(TextProperty::new(5, 15).with_property("id", json!("second")));
239 manager.add(TextProperty::new(20, 30).with_property("id", json!("third")));
240
241 let props = manager.get_at(7);
243 assert_eq!(props.len(), 2);
244 assert_eq!(props[0].get("id"), Some(&json!("first")));
245 assert_eq!(props[1].get("id"), Some(&json!("second")));
246
247 let props = manager.get_at(25);
249 assert_eq!(props.len(), 1);
250 assert_eq!(props[0].get("id"), Some(&json!("third")));
251
252 let props = manager.get_at(17);
254 assert_eq!(props.len(), 0);
255 }
256
257 #[test]
258 fn test_manager_get_overlapping() {
259 let mut manager = TextPropertyManager::new();
260
261 manager.add(TextProperty::new(0, 10).with_property("id", json!("first")));
262 manager.add(TextProperty::new(20, 30).with_property("id", json!("second")));
263
264 let props = manager.get_overlapping(&(5..15));
266 assert_eq!(props.len(), 1);
267 assert_eq!(props[0].get("id"), Some(&json!("first")));
268
269 let props = manager.get_overlapping(&(25..35));
271 assert_eq!(props.len(), 1);
272 assert_eq!(props[0].get("id"), Some(&json!("second")));
273
274 let props = manager.get_overlapping(&(12..18));
276 assert_eq!(props.len(), 0);
277
278 let props = manager.get_overlapping(&(0..30));
280 assert_eq!(props.len(), 2);
281 }
282
283 #[test]
284 fn test_manager_from_entries() {
285 let entries = vec![
286 TextPropertyEntry::text("Error at line 42\n")
287 .with_property("severity", json!("error"))
288 .with_property("line", json!(42)),
289 TextPropertyEntry::text("Warning at line 100\n")
290 .with_property("severity", json!("warning"))
291 .with_property("line", json!(100)),
292 ];
293
294 let (text, manager, _overlays) = TextPropertyManager::from_entries(entries);
295
296 assert_eq!(text, "Error at line 42\nWarning at line 100\n");
297 assert_eq!(manager.len(), 2);
298
299 let first_props = manager.get_at(0);
301 assert_eq!(first_props.len(), 1);
302 assert_eq!(first_props[0].get("severity"), Some(&json!("error")));
303 assert_eq!(first_props[0].get("line"), Some(&json!(42)));
304 assert_eq!(first_props[0].start, 0);
305 assert_eq!(first_props[0].end, 17);
306
307 let second_props = manager.get_at(17);
309 assert_eq!(second_props.len(), 1);
310 assert_eq!(second_props[0].get("severity"), Some(&json!("warning")));
311 assert_eq!(second_props[0].get("line"), Some(&json!(100)));
312 assert_eq!(second_props[0].start, 17);
313 assert_eq!(second_props[0].end, 37);
314 }
315
316 #[test]
317 fn test_manager_clear() {
318 let mut manager = TextPropertyManager::new();
319 manager.add(TextProperty::new(0, 10));
320 manager.add(TextProperty::new(20, 30));
321
322 assert_eq!(manager.len(), 2);
323 manager.clear();
324 assert_eq!(manager.len(), 0);
325 assert!(manager.is_empty());
326 }
327
328 #[test]
329 fn test_manager_remove_in_range() {
330 let mut manager = TextPropertyManager::new();
331 manager.add(TextProperty::new(0, 10).with_property("id", json!("first")));
332 manager.add(TextProperty::new(20, 30).with_property("id", json!("second")));
333 manager.add(TextProperty::new(40, 50).with_property("id", json!("third")));
334
335 manager.remove_in_range(&(15..35));
337
338 assert_eq!(manager.len(), 2);
340 let all = manager.all();
341 assert_eq!(all[0].get("id"), Some(&json!("first")));
342 assert_eq!(all[1].get("id"), Some(&json!("third")));
343 }
344}