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 #[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 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 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#[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 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 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}