xi_core_lib/
annotations.rs

1// Copyright 2018 The xi-editor Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Management of annotations.
16
17use serde::de::{Deserialize, Deserializer};
18use serde::ser::{Serialize, SerializeSeq, Serializer};
19use serde_json::{self, Value};
20
21use std::collections::HashMap;
22
23use crate::plugins::PluginId;
24use crate::view::View;
25use crate::xi_rope::spans::Spans;
26use crate::xi_rope::{Interval, Rope};
27
28#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
29pub enum AnnotationType {
30    Selection,
31    Find,
32    Other(String),
33}
34
35impl AnnotationType {
36    fn as_str(&self) -> &str {
37        match self {
38            AnnotationType::Find => "find",
39            AnnotationType::Selection => "selection",
40            AnnotationType::Other(ref s) => s,
41        }
42    }
43}
44
45/// Location and range of an annotation ([start_line, start_col, end_line, end_col]).
46/// Location and range of an annotation
47#[derive(Debug, Default, Clone, Copy, PartialEq)]
48pub struct AnnotationRange {
49    pub start_line: usize,
50    pub start_col: usize,
51    pub end_line: usize,
52    pub end_col: usize,
53}
54
55impl Serialize for AnnotationRange {
56    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
57    where
58        S: Serializer,
59    {
60        let mut seq = serializer.serialize_seq(Some(4))?;
61        seq.serialize_element(&self.start_line)?;
62        seq.serialize_element(&self.start_col)?;
63        seq.serialize_element(&self.end_line)?;
64        seq.serialize_element(&self.end_col)?;
65        seq.end()
66    }
67}
68
69impl<'de> Deserialize<'de> for AnnotationRange {
70    fn deserialize<D>(deserializer: D) -> Result<AnnotationRange, D::Error>
71    where
72        D: Deserializer<'de>,
73    {
74        let mut range = AnnotationRange { ..Default::default() };
75        let seq = <[usize; 4]>::deserialize(deserializer)?;
76
77        range.start_line = seq[0];
78        range.start_col = seq[1];
79        range.end_line = seq[2];
80        range.end_col = seq[3];
81
82        Ok(range)
83    }
84}
85
86/// A set of annotations of a given type.
87#[derive(Debug, Clone)]
88pub struct Annotations {
89    pub items: Spans<Value>,
90    pub annotation_type: AnnotationType,
91}
92
93impl Annotations {
94    /// Update the annotations in `interval` with the provided `items`.
95    pub fn update(&mut self, interval: Interval, items: Spans<Value>) {
96        self.items.edit(interval, items);
97    }
98
99    /// Remove annotations intersecting `interval`.
100    pub fn invalidate(&mut self, interval: Interval) {
101        self.items.delete_intersecting(interval);
102    }
103}
104
105/// A region of an `Annotation`.
106#[derive(Serialize, Deserialize, Debug, Clone)]
107pub struct AnnotationSlice {
108    annotation_type: AnnotationType,
109    /// Annotation occurrences, guaranteed non-descending start order.
110    ranges: Vec<AnnotationRange>,
111    /// If present, one payload per range.
112    payloads: Option<Vec<Value>>,
113}
114
115impl AnnotationSlice {
116    pub fn new(
117        annotation_type: AnnotationType,
118        ranges: Vec<AnnotationRange>,
119        payloads: Option<Vec<Value>>,
120    ) -> Self {
121        AnnotationSlice { annotation_type, ranges, payloads }
122    }
123
124    /// Returns json representation.
125    pub fn to_json(&self) -> Value {
126        json!({
127            "type": self.annotation_type.as_str(),
128            "ranges": self.ranges,
129            "payloads": self.payloads,
130            "n": self.ranges.len()
131        })
132    }
133}
134
135/// A trait for types (like `Selection`) that have a distinct representation
136/// in core but are presented to the frontend as annotations.
137pub trait ToAnnotation {
138    /// Returns annotations that overlap the provided interval.
139    fn get_annotations(&self, interval: Interval, view: &View, text: &Rope) -> AnnotationSlice;
140}
141
142/// All the annotations for a given view
143pub struct AnnotationStore {
144    store: HashMap<PluginId, Vec<Annotations>>,
145}
146
147impl AnnotationStore {
148    pub fn new() -> Self {
149        AnnotationStore { store: HashMap::new() }
150    }
151
152    /// Invalidates and removes all annotations in the range of the interval.
153    pub fn invalidate(&mut self, interval: Interval) {
154        self.store.values_mut().map(|v| v.iter_mut()).flatten().for_each(|a| a.invalidate(interval))
155    }
156
157    /// Applies an update from a plugin to a set of annotations
158    pub fn update(&mut self, source: PluginId, interval: Interval, item: Annotations) {
159        if !self.store.contains_key(&source) {
160            self.store.insert(source, vec![item]);
161            return;
162        }
163
164        let entry = self.store.get_mut(&source).unwrap();
165        if let Some(annotation) =
166            entry.iter_mut().find(|a| a.annotation_type == item.annotation_type)
167        {
168            annotation.update(interval, item.items);
169        } else {
170            entry.push(item);
171        }
172    }
173
174    /// Returns an iterator which produces, for each type of annotation,
175    /// those annotations which intersect the given interval.
176    pub fn iter_range<'c>(
177        &'c self,
178        view: &'c View,
179        text: &'c Rope,
180        interval: Interval,
181    ) -> impl Iterator<Item = AnnotationSlice> + 'c {
182        self.store.iter().flat_map(move |(_plugin, value)| {
183            value.iter().map(move |annotation| {
184                let payloads = annotation
185                    .items
186                    .subseq(interval)
187                    .iter()
188                    .map(|(_i, p)| p.clone())
189                    .collect::<Vec<Value>>();
190
191                let ranges = annotation
192                    .items
193                    .subseq(interval)
194                    .iter()
195                    .map(|(i, _p)| {
196                        let (start_line, start_col) = view.offset_to_line_col(text, i.start());
197                        let (end_line, end_col) = view.offset_to_line_col(text, i.end());
198
199                        AnnotationRange { start_line, start_col, end_line, end_col }
200                    })
201                    .collect::<Vec<AnnotationRange>>();
202
203                AnnotationSlice {
204                    annotation_type: annotation.annotation_type.clone(),
205                    ranges,
206                    payloads: Some(payloads),
207                }
208            })
209        })
210    }
211
212    /// Removes any annotations provided by this plugin
213    pub fn clear(&mut self, plugin: PluginId) {
214        self.store.remove(&plugin);
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use crate::plugins::PluginPid;
222    use crate::xi_rope::spans::SpansBuilder;
223
224    #[test]
225    fn test_annotation_range_serialization() {
226        let range = AnnotationRange { start_line: 1, start_col: 3, end_line: 4, end_col: 1 };
227
228        assert_eq!(json!(range).to_string(), "[1,3,4,1]")
229    }
230
231    #[test]
232    fn test_annotation_range_deserialization() {
233        let range: AnnotationRange = serde_json::from_str("[1,3,4,1]").unwrap();
234        assert_eq!(range, AnnotationRange { start_line: 1, start_col: 3, end_line: 4, end_col: 1 })
235    }
236
237    #[test]
238    fn test_annotation_slice_json() {
239        let range = AnnotationRange { start_line: 1, start_col: 3, end_line: 4, end_col: 1 };
240
241        let slice = AnnotationSlice {
242            annotation_type: AnnotationType::Find,
243            ranges: vec![range],
244            payloads: None,
245        };
246
247        assert_eq!(
248            slice.to_json().to_string(),
249            "{\"n\":1,\"payloads\":null,\"ranges\":[[1,3,4,1]],\"type\":\"find\"}"
250        )
251    }
252
253    #[test]
254    fn test_annotation_store_update() {
255        let mut store = AnnotationStore::new();
256
257        let mut sb = SpansBuilder::new(10);
258        sb.add_span(Interval::new(1, 5), json!(null));
259
260        assert_eq!(store.store.len(), 0);
261
262        store.update(
263            PluginPid(1),
264            Interval::new(1, 5),
265            Annotations { annotation_type: AnnotationType::Find, items: sb.build() },
266        );
267
268        assert_eq!(store.store.len(), 1);
269
270        sb = SpansBuilder::new(10);
271        sb.add_span(Interval::new(6, 8), json!(null));
272
273        store.update(
274            PluginPid(2),
275            Interval::new(6, 8),
276            Annotations { annotation_type: AnnotationType::Find, items: sb.build() },
277        );
278
279        assert_eq!(store.store.len(), 2);
280    }
281
282    #[test]
283    fn test_annotation_store_clear() {
284        let mut store = AnnotationStore::new();
285
286        let mut sb = SpansBuilder::new(10);
287        sb.add_span(Interval::new(1, 5), json!(null));
288
289        assert_eq!(store.store.len(), 0);
290
291        store.update(
292            PluginPid(1),
293            Interval::new(1, 5),
294            Annotations { annotation_type: AnnotationType::Find, items: sb.build() },
295        );
296
297        assert_eq!(store.store.len(), 1);
298
299        sb = SpansBuilder::new(10);
300        sb.add_span(Interval::new(6, 8), json!(null));
301
302        store.update(
303            PluginPid(2),
304            Interval::new(6, 8),
305            Annotations { annotation_type: AnnotationType::Find, items: sb.build() },
306        );
307
308        assert_eq!(store.store.len(), 2);
309
310        store.clear(PluginPid(1));
311
312        assert_eq!(store.store.len(), 1);
313
314        store.clear(PluginPid(1));
315
316        assert_eq!(store.store.len(), 1);
317    }
318}