summaryrefslogtreecommitdiff
path: root/stag/src/text_range.rs
diff options
context:
space:
mode:
Diffstat (limited to 'stag/src/text_range.rs')
-rw-r--r--stag/src/text_range.rs121
1 files changed, 121 insertions, 0 deletions
diff --git a/stag/src/text_range.rs b/stag/src/text_range.rs
new file mode 100644
index 0000000..5b1ec67
--- /dev/null
+++ b/stag/src/text_range.rs
@@ -0,0 +1,121 @@
1use std::cmp::{Ord, Ordering};
2
3use serde::{Deserialize, Serialize};
4
5/// A singular position in a text document
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct Point {
8 /// The byte index
9 pub byte: usize,
10
11 /// 0-indexed line number
12 pub line: usize,
13
14 /// Position within the line
15 pub column: usize,
16}
17
18impl PartialOrd for Point {
19 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
20 Some(self.cmp(other))
21 }
22}
23
24impl Ord for Point {
25 fn cmp(&self, other: &Self) -> Ordering {
26 self.byte.cmp(&other.byte)
27 }
28}
29
30impl Point {
31 pub fn new(byte: usize, line: usize, column: usize) -> Self {
32 Self { byte, line, column }
33 }
34
35 pub fn from_byte(byte: usize, line_end_indices: &[u32]) -> Self {
36 let line = line_end_indices
37 .iter()
38 .position(|&line_end_byte| (line_end_byte as usize) > byte)
39 .unwrap_or(0);
40
41 let column = line
42 .checked_sub(1)
43 .and_then(|idx| line_end_indices.get(idx))
44 .map(|&prev_line_end| byte.saturating_sub(prev_line_end as usize))
45 .unwrap_or(byte);
46
47 Self::new(byte, line, column)
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
52pub struct TextRange {
53 pub start: Point,
54 pub end: Point,
55}
56
57impl PartialOrd for TextRange {
58 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
59 Some(self.cmp(other))
60 }
61}
62
63impl Ord for TextRange {
64 fn cmp(&self, other: &Self) -> Ordering {
65 let compare_start_byte = self.start.byte.cmp(&other.start.byte);
66 let compare_size = self.size().cmp(&other.size());
67
68 compare_start_byte.then(compare_size)
69 }
70}
71
72impl TextRange {
73 pub fn new(start: Point, end: Point) -> Self {
74 assert!(start <= end);
75 Self { start, end }
76 }
77
78 pub fn contains(&self, other: &TextRange) -> bool {
79 // (self.start ... [other.start ... other.end] ... self.end)
80 self.start <= other.start && other.end <= self.end
81 }
82
83 #[allow(unused)]
84 pub fn contains_strict(&self, other: TextRange) -> bool {
85 // (self.start ... (other.start ... other.end) ... self.end)
86 self.start < other.start && other.end <= self.end
87 }
88
89 pub fn size(&self) -> usize {
90 self.end.byte.saturating_sub(self.start.byte)
91 }
92
93 pub fn from_byte_range(range: std::ops::Range<usize>, line_end_indices: &[u32]) -> Self {
94 let start = Point::from_byte(range.start, line_end_indices);
95 let end = Point::from_byte(range.end, line_end_indices);
96 Self::new(start, end)
97 }
98}
99
100impl From<tree_sitter::Range> for TextRange {
101 fn from(r: tree_sitter::Range) -> Self {
102 Self {
103 start: Point {
104 byte: r.start_byte,
105 line: r.start_point.row,
106 column: r.start_point.column,
107 },
108 end: Point {
109 byte: r.end_byte,
110 line: r.end_point.row,
111 column: r.end_point.column,
112 },
113 }
114 }
115}
116
117impl From<TextRange> for std::ops::Range<usize> {
118 fn from(r: TextRange) -> std::ops::Range<usize> {
119 r.start.byte..r.end.byte
120 }
121}