1use std::ops::Range;
9
10pub use fresh_core::text_property::{TextProperty, TextPropertyEntry};
12
13#[derive(Debug, Clone, Default)]
18pub struct TextPropertyManager {
19 properties: Vec<TextProperty>,
21}
22
23impl TextPropertyManager {
24 pub fn new() -> Self {
26 Self {
27 properties: Vec::new(),
28 }
29 }
30
31 pub fn add(&mut self, property: TextProperty) {
33 let pos = self
35 .properties
36 .binary_search_by_key(&property.start, |p| p.start)
37 .unwrap_or_else(|e| e);
38 self.properties.insert(pos, property);
39 }
40
41 pub fn get_at(&self, pos: usize) -> Vec<&TextProperty> {
43 self.properties.iter().filter(|p| p.contains(pos)).collect()
44 }
45
46 pub fn get_overlapping(&self, range: &Range<usize>) -> Vec<&TextProperty> {
48 self.properties
49 .iter()
50 .filter(|p| p.overlaps(range))
51 .collect()
52 }
53
54 pub fn clear(&mut self) {
56 self.properties.clear();
57 }
58
59 pub fn remove_in_range(&mut self, range: &Range<usize>) {
61 self.properties
62 .retain(|p| !p.overlaps(range) && !range.contains(&p.start));
63 }
64
65 pub fn all(&self) -> &[TextProperty] {
67 &self.properties
68 }
69
70 pub fn is_empty(&self) -> bool {
72 self.properties.is_empty()
73 }
74
75 pub fn len(&self) -> usize {
77 self.properties.len()
78 }
79
80 pub fn set_all(&mut self, properties: Vec<TextProperty>) {
82 self.properties = properties;
83 self.properties.sort_by_key(|p| p.start);
85 }
86
87 pub fn from_entries(entries: Vec<TextPropertyEntry>) -> (String, Self) {
91 let mut text = String::new();
92 let mut manager = Self::new();
93 let mut offset = 0;
94
95 for entry in entries {
96 let start = offset;
97 text.push_str(&entry.text);
98 let end = offset + entry.text.len();
99
100 if !entry.properties.is_empty() {
101 let property = TextProperty {
102 start,
103 end,
104 properties: entry.properties,
105 };
106 manager.add(property);
107 }
108
109 offset = end;
110 }
111
112 (text, manager)
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use serde::Deserialize;
120 use serde_json::json;
121
122 #[test]
123 fn test_text_property_contains() {
124 let prop = TextProperty::new(10, 20);
125 assert!(prop.contains(10));
126 assert!(prop.contains(15));
127 assert!(prop.contains(19));
128 assert!(!prop.contains(9));
129 assert!(!prop.contains(20));
130 }
131
132 #[test]
133 fn test_text_property_overlaps() {
134 let prop = TextProperty::new(10, 20);
135 assert!(prop.overlaps(&(5..15)));
136 assert!(prop.overlaps(&(15..25)));
137 assert!(prop.overlaps(&(10..20)));
138 assert!(prop.overlaps(&(12..18)));
139 assert!(!prop.overlaps(&(0..10)));
140 assert!(!prop.overlaps(&(20..30)));
141 }
142
143 #[test]
144 fn test_text_property_with_properties() {
145 let prop = TextProperty::new(0, 10)
146 .with_property("severity", json!("error"))
147 .with_property(
148 "location",
149 json!({"file": "test.rs", "line": 42, "column": 5}),
150 );
151
152 assert_eq!(prop.get("severity"), Some(&json!("error")));
153 assert_eq!(
154 prop.get("location"),
155 Some(&json!({"file": "test.rs", "line": 42, "column": 5}))
156 );
157 assert_eq!(prop.get("nonexistent"), None);
158 }
159
160 #[test]
161 fn test_text_property_get_as() {
162 let prop = TextProperty::new(0, 10)
163 .with_property("count", json!(42))
164 .with_property(
165 "location",
166 json!({"file": "test.rs", "line": 42, "column": 5}),
167 );
168
169 let count: Option<i64> = prop.get_as("count");
170 assert_eq!(count, Some(42));
171
172 #[derive(Debug, Deserialize, PartialEq)]
173 struct Location {
174 file: String,
175 line: u32,
176 column: u32,
177 }
178
179 let loc: Option<Location> = prop.get_as("location");
180 assert_eq!(
181 loc,
182 Some(Location {
183 file: "test.rs".to_string(),
184 line: 42,
185 column: 5,
186 })
187 );
188 }
189
190 #[test]
191 fn test_manager_add_and_get_at() {
192 let mut manager = TextPropertyManager::new();
193
194 manager.add(TextProperty::new(0, 10).with_property("id", json!("first")));
195 manager.add(TextProperty::new(5, 15).with_property("id", json!("second")));
196 manager.add(TextProperty::new(20, 30).with_property("id", json!("third")));
197
198 let props = manager.get_at(7);
200 assert_eq!(props.len(), 2);
201 assert_eq!(props[0].get("id"), Some(&json!("first")));
202 assert_eq!(props[1].get("id"), Some(&json!("second")));
203
204 let props = manager.get_at(25);
206 assert_eq!(props.len(), 1);
207 assert_eq!(props[0].get("id"), Some(&json!("third")));
208
209 let props = manager.get_at(17);
211 assert_eq!(props.len(), 0);
212 }
213
214 #[test]
215 fn test_manager_get_overlapping() {
216 let mut manager = TextPropertyManager::new();
217
218 manager.add(TextProperty::new(0, 10).with_property("id", json!("first")));
219 manager.add(TextProperty::new(20, 30).with_property("id", json!("second")));
220
221 let props = manager.get_overlapping(&(5..15));
223 assert_eq!(props.len(), 1);
224 assert_eq!(props[0].get("id"), Some(&json!("first")));
225
226 let props = manager.get_overlapping(&(25..35));
228 assert_eq!(props.len(), 1);
229 assert_eq!(props[0].get("id"), Some(&json!("second")));
230
231 let props = manager.get_overlapping(&(12..18));
233 assert_eq!(props.len(), 0);
234
235 let props = manager.get_overlapping(&(0..30));
237 assert_eq!(props.len(), 2);
238 }
239
240 #[test]
241 fn test_manager_from_entries() {
242 let entries = vec![
243 TextPropertyEntry::text("Error at line 42\n")
244 .with_property("severity", json!("error"))
245 .with_property("line", json!(42)),
246 TextPropertyEntry::text("Warning at line 100\n")
247 .with_property("severity", json!("warning"))
248 .with_property("line", json!(100)),
249 ];
250
251 let (text, manager) = TextPropertyManager::from_entries(entries);
252
253 assert_eq!(text, "Error at line 42\nWarning at line 100\n");
254 assert_eq!(manager.len(), 2);
255
256 let first_props = manager.get_at(0);
258 assert_eq!(first_props.len(), 1);
259 assert_eq!(first_props[0].get("severity"), Some(&json!("error")));
260 assert_eq!(first_props[0].get("line"), Some(&json!(42)));
261 assert_eq!(first_props[0].start, 0);
262 assert_eq!(first_props[0].end, 17);
263
264 let second_props = manager.get_at(17);
266 assert_eq!(second_props.len(), 1);
267 assert_eq!(second_props[0].get("severity"), Some(&json!("warning")));
268 assert_eq!(second_props[0].get("line"), Some(&json!(100)));
269 assert_eq!(second_props[0].start, 17);
270 assert_eq!(second_props[0].end, 37);
271 }
272
273 #[test]
274 fn test_manager_clear() {
275 let mut manager = TextPropertyManager::new();
276 manager.add(TextProperty::new(0, 10));
277 manager.add(TextProperty::new(20, 30));
278
279 assert_eq!(manager.len(), 2);
280 manager.clear();
281 assert_eq!(manager.len(), 0);
282 assert!(manager.is_empty());
283 }
284
285 #[test]
286 fn test_manager_remove_in_range() {
287 let mut manager = TextPropertyManager::new();
288 manager.add(TextProperty::new(0, 10).with_property("id", json!("first")));
289 manager.add(TextProperty::new(20, 30).with_property("id", json!("second")));
290 manager.add(TextProperty::new(40, 50).with_property("id", json!("third")));
291
292 manager.remove_in_range(&(15..35));
294
295 assert_eq!(manager.len(), 2);
297 let all = manager.all();
298 assert_eq!(all[0].get("id"), Some(&json!("first")));
299 assert_eq!(all[1].get("id"), Some(&json!("third")));
300 }
301}