grit_util/
ranges.rs

1use crate::{EffectKind, Position};
2use serde::{Deserialize, Serialize};
3use std::{ops::Add, path::PathBuf};
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
6#[serde(rename_all = "camelCase")]
7pub struct Range {
8    pub start: Position,
9    pub end: Position,
10    // TODO: automatically derive these from the start and end positions during deserialization
11    #[serde(skip_deserializing)]
12    pub start_byte: u32,
13    #[serde(skip_deserializing)]
14    pub end_byte: u32,
15}
16
17impl Range {
18    pub fn new(start: Position, end: Position, start_byte: u32, end_byte: u32) -> Self {
19        Self {
20            start,
21            end,
22            start_byte,
23            end_byte,
24        }
25    }
26
27    pub fn from_byte_range(source: &str, byte_range: &ByteRange) -> Self {
28        let start = Position::from_byte_index(source, byte_range.start);
29        let end =
30            Position::from_relative_byte_index(start, byte_range.start, source, byte_range.end);
31        Self {
32            start,
33            end,
34            start_byte: byte_range.start as u32,
35            end_byte: byte_range.end as u32,
36        }
37    }
38
39    pub fn add(&mut self, other: Position, other_byte: u32) {
40        self.start.add(other);
41        self.end.add(other);
42        self.start_byte += other_byte;
43        self.end_byte += other_byte;
44    }
45
46    pub fn range_index(&self) -> std::ops::Range<usize> {
47        self.start_byte as usize..self.end_byte as usize
48    }
49
50    pub fn from_byteless(range: RangeWithoutByte, str: &str) -> Self {
51        let mut start_byte = 0;
52        let mut byte_length = 0;
53
54        let start_line_zero_indexed = range.start.line as usize - 1;
55        let end_line_zero_indexed = range.end.line as usize - 1;
56
57        for (current_line, line) in str.lines().enumerate() {
58            if current_line < start_line_zero_indexed {
59                start_byte += line.len() as u32 + 1;
60            } else if current_line == start_line_zero_indexed {
61                start_byte += range.start.column - 1;
62                // If this is *also* the end, we must handle that here
63                if current_line == end_line_zero_indexed {
64                    byte_length += range.end.column - range.start.column;
65                    break;
66                } else {
67                    byte_length += (line.len() as u32 + 1) - range.start.column;
68                }
69            } else if current_line < end_line_zero_indexed {
70                byte_length += line.len() as u32 + 1;
71            } else if current_line == end_line_zero_indexed {
72                byte_length += range.end.column;
73                break;
74            }
75        }
76
77        Self {
78            start: range.start,
79            end: range.end,
80            start_byte,
81            end_byte: start_byte + byte_length,
82        }
83    }
84
85    #[cfg(test)]
86    fn from_md(
87        start_line: usize,
88        start_col: usize,
89        end_line: usize,
90        end_col: usize,
91        start_byte: usize,
92        end_byte: usize,
93    ) -> Self {
94        Self {
95            start: Position::new(start_line as u32, start_col as u32),
96            end: Position::new(end_line as u32, end_col as u32),
97            start_byte: start_byte as u32,
98            end_byte: end_byte as u32,
99        }
100    }
101
102    pub fn adjust_columns(&mut self, start: i32, end: i32) -> bool {
103        if let (Some(start), Some(end), Some(start_byte), Some(end_byte)) = (
104            self.start.column.checked_add_signed(start),
105            self.end.column.checked_add_signed(end),
106            self.start_byte.checked_add_signed(start),
107            self.end_byte.checked_add_signed(end),
108        ) {
109            self.start.column = start;
110            self.end.column = end;
111            self.start_byte = start_byte;
112            self.end_byte = end_byte;
113            true
114        } else {
115            false
116        }
117    }
118
119    // Return the 0-based indexes within the line where the match exists, if any
120    pub fn get_line_range(&self, line: u32, line_length: u32) -> Option<(usize, usize)> {
121        let max_length = if line_length == 0 { 1 } else { line_length + 1 };
122        let (start, end) = if line < self.start.line || line > self.end.line {
123            return None;
124        } else if self.start.line == line && self.end.line == line {
125            (self.start.column - 1, self.end.column - 1)
126        } else if self.start.line == line {
127            (self.start.column - 1, max_length - 1)
128        } else if self.end.line == line {
129            (0, self.end.column - 1)
130        } else {
131            (0, max_length - 1)
132        };
133        Some((start as usize, end as usize))
134    }
135}
136
137// A simple range, without byte information
138#[cfg_attr(feature = "napi", napi_derive::napi(object))]
139#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
140pub struct RangeWithoutByte {
141    pub start: Position,
142    pub end: Position,
143}
144
145impl RangeWithoutByte {
146    pub fn start_column(&self) -> u32 {
147        self.start.column
148    }
149
150    pub fn end_column(&self) -> u32 {
151        self.end.column
152    }
153
154    pub fn start_line(&self) -> u32 {
155        self.start.line
156    }
157
158    pub fn end_line(&self) -> u32 {
159        self.end.line
160    }
161
162    pub fn is_empty(&self) -> bool {
163        self.start == self.end
164    }
165}
166
167impl From<Range> for RangeWithoutByte {
168    fn from(range: Range) -> Self {
169        Self {
170            start: range.start,
171            end: range.end,
172        }
173    }
174}
175
176#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
177#[serde(untagged)]
178pub enum UtilRange {
179    Range(Range),
180    RangeWithoutByte(RangeWithoutByte),
181}
182
183impl From<Range> for UtilRange {
184    fn from(range: Range) -> Self {
185        Self::Range(range)
186    }
187}
188
189impl From<RangeWithoutByte> for UtilRange {
190    fn from(range: RangeWithoutByte) -> Self {
191        Self::RangeWithoutByte(range)
192    }
193}
194
195#[derive(Clone, Copy, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
196#[serde(rename_all = "camelCase")]
197pub struct ByteRange {
198    pub start: usize,
199    pub end: usize,
200}
201
202impl ByteRange {
203    pub fn new(start: usize, end: usize) -> Self {
204        Self { start, end }
205    }
206
207    pub fn abbreviated_debug(&self) -> String {
208        format!("[{}-{}]", self.start, self.end)
209    }
210
211    /// Converts a range expressed in byte indices to a range expressed in
212    /// character offsets.
213    pub fn to_char_range(self, context: &str) -> Self {
214        let start = byte_index_to_char_offset(self.start, context);
215        let end = byte_index_to_char_offset(self.end, context);
216        Self { start, end }
217    }
218}
219
220impl From<Range> for ByteRange {
221    fn from(value: Range) -> Self {
222        Self {
223            start: value.start_byte as usize,
224            end: value.end_byte as usize,
225        }
226    }
227}
228
229impl From<std::ops::Range<usize>> for ByteRange {
230    fn from(range: std::ops::Range<usize>) -> Self {
231        Self {
232            start: range.start,
233            end: range.end,
234        }
235    }
236}
237
238impl Add<usize> for ByteRange {
239    type Output = Self;
240
241    fn add(self, rhs: usize) -> Self::Output {
242        Self {
243            start: self.start + rhs,
244            end: self.end + rhs,
245        }
246    }
247}
248
249#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
250#[serde(rename_all = "camelCase")]
251pub struct FileRange {
252    pub file_path: PathBuf,
253    pub range: UtilRange,
254}
255
256fn byte_index_to_char_offset(index: usize, text: &str) -> usize {
257    text.char_indices().take_while(|(i, _)| *i < index).count()
258}
259
260#[derive(Debug, Clone)]
261pub struct InputRanges {
262    pub ranges: Vec<Range>,
263    pub variables: Vec<VariableMatch>,
264    pub suppressed: bool,
265}
266
267#[derive(Debug, Clone, Default)]
268pub struct MatchRanges {
269    pub input_matches: Option<InputRanges>,
270    pub byte_ranges: Option<Vec<ByteRange>>,
271}
272
273impl MatchRanges {
274    pub fn new(byte_ranges: Vec<ByteRange>) -> Self {
275        Self {
276            input_matches: None,
277            byte_ranges: Some(byte_ranges),
278        }
279    }
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
283#[serde(rename_all = "camelCase")]
284pub struct VariableBinding {
285    pub name: String,
286    pub scoped_name: String,
287    pub ranges: Vec<ByteRange>,
288}
289
290impl VariableBinding {
291    pub fn new(name: String, scoped_name: String, ranges: Vec<ByteRange>) -> Self {
292        Self {
293            name,
294            scoped_name,
295            ranges,
296        }
297    }
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
301#[serde(rename_all = "camelCase")]
302pub struct VariableMatch {
303    pub name: String,
304    pub scoped_name: String,
305    pub ranges: Vec<Range>,
306}
307
308impl VariableMatch {
309    pub fn new(name: String, scoped_name: String, ranges: Vec<Range>) -> Self {
310        Self {
311            name,
312            scoped_name,
313            ranges,
314        }
315    }
316}
317
318#[cfg(test)]
319mod tests {
320
321    use super::*;
322
323    #[test]
324    fn test_range_one_char_line() {
325        let content = "a";
326        let range = Range::from_md(1, 1, 1, 2, 0, 1);
327        let (start, end) = range.get_line_range(1, 1).unwrap();
328        assert_eq!(start, 0);
329        assert_eq!(end, 1);
330        let content_highlighted = content[start..end].to_string();
331        assert_eq!(content_highlighted, "a");
332    }
333
334    #[test]
335    fn test_long_one_char() {
336        let content = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n";
337        let range = Range::from_md(5, 1, 10, 1, 8, 13);
338        let line_number = 8;
339        let lines = content.lines().collect::<Vec<_>>();
340        let line = lines[line_number as usize - 1];
341        let line_length = line.len() as u32;
342        let (start, end) = range.get_line_range(line_number, line_length).unwrap();
343        assert_eq!(start, 0);
344        assert_eq!(end, 1);
345        let content_highlighted = line[start..end].to_string();
346        assert_eq!(content_highlighted, "8");
347    }
348
349    #[test]
350    fn byte_range_to_char_range() {
351        let range = ByteRange::new(7, 9);
352        let new_range = range.to_char_range("const [µb, fµa]");
353        assert_eq!(new_range, ByteRange::new(7, 8));
354        let range = ByteRange::new(15, 17);
355        let new_range = range.to_char_range("const [µb, fµa]");
356        assert_eq!(new_range, ByteRange::new(13, 15));
357    }
358}
359
360#[derive(Debug, Clone)]
361pub struct EffectRange {
362    pub kind: EffectKind,
363    pub range: std::ops::Range<usize>,
364}
365
366impl EffectRange {
367    pub fn new(kind: EffectKind, range: std::ops::Range<usize>) -> Self {
368        Self { kind, range }
369    }
370
371    pub fn start(&self) -> usize {
372        self.range.start
373    }
374
375    // The range which is actually edited by this effect
376    // This is used for most operations, but does not account for expansion from deleted commas
377    pub fn effective_range(&self) -> std::ops::Range<usize> {
378        match self.kind {
379            EffectKind::Rewrite => self.range.clone(),
380            EffectKind::Insert => self.range.end..self.range.end,
381        }
382    }
383}