aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/line_index_utils.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src/line_index_utils.rs')
-rw-r--r--crates/ra_ide_api/src/line_index_utils.rs330
1 files changed, 330 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/line_index_utils.rs b/crates/ra_ide_api/src/line_index_utils.rs
new file mode 100644
index 000000000..799a920ad
--- /dev/null
+++ b/crates/ra_ide_api/src/line_index_utils.rs
@@ -0,0 +1,330 @@
1use ra_text_edit::{AtomTextEdit, TextEdit};
2use ra_syntax::{TextUnit, TextRange};
3use crate::{LineIndex, LineCol, line_index::Utf16Char};
4
5#[derive(Debug, Clone)]
6enum Step {
7 Newline(TextUnit),
8 Utf16Char(TextRange),
9}
10
11#[derive(Debug)]
12struct LineIndexStepIter<'a> {
13 line_index: &'a LineIndex,
14 next_newline_idx: usize,
15 utf16_chars: Option<(TextUnit, std::slice::Iter<'a, Utf16Char>)>,
16}
17
18impl<'a> LineIndexStepIter<'a> {
19 fn from(line_index: &LineIndex) -> LineIndexStepIter {
20 let mut x = LineIndexStepIter { line_index, next_newline_idx: 0, utf16_chars: None };
21 // skip first newline since it's not real
22 x.next();
23 x
24 }
25}
26
27impl<'a> Iterator for LineIndexStepIter<'a> {
28 type Item = Step;
29 fn next(&mut self) -> Option<Step> {
30 self.utf16_chars
31 .as_mut()
32 .and_then(|(newline, x)| {
33 let x = x.next()?;
34 Some(Step::Utf16Char(TextRange::from_to(*newline + x.start, *newline + x.end)))
35 })
36 .or_else(|| {
37 let next_newline = *self.line_index.newlines.get(self.next_newline_idx)?;
38 self.utf16_chars = self
39 .line_index
40 .utf16_lines
41 .get(&(self.next_newline_idx as u32))
42 .map(|x| (next_newline, x.iter()));
43 self.next_newline_idx += 1;
44 Some(Step::Newline(next_newline))
45 })
46 }
47}
48
49#[derive(Debug)]
50struct OffsetStepIter<'a> {
51 text: &'a str,
52 offset: TextUnit,
53}
54
55impl<'a> Iterator for OffsetStepIter<'a> {
56 type Item = Step;
57 fn next(&mut self) -> Option<Step> {
58 let (next, next_offset) = self
59 .text
60 .char_indices()
61 .filter_map(|(i, c)| {
62 if c == '\n' {
63 let next_offset = self.offset + TextUnit::from_usize(i + 1);
64 let next = Step::Newline(next_offset);
65 Some((next, next_offset))
66 } else {
67 let char_len = TextUnit::of_char(c);
68 if char_len.to_usize() > 1 {
69 let start = self.offset + TextUnit::from_usize(i);
70 let end = start + char_len;
71 let next = Step::Utf16Char(TextRange::from_to(start, end));
72 let next_offset = end;
73 Some((next, next_offset))
74 } else {
75 None
76 }
77 }
78 })
79 .next()?;
80 let next_idx = (next_offset - self.offset).to_usize();
81 self.text = &self.text[next_idx..];
82 self.offset = next_offset;
83 Some(next)
84 }
85}
86
87#[derive(Debug)]
88enum NextSteps<'a> {
89 Use,
90 ReplaceMany(OffsetStepIter<'a>),
91 AddMany(OffsetStepIter<'a>),
92}
93
94#[derive(Debug)]
95struct TranslatedEdit<'a> {
96 delete: TextRange,
97 insert: &'a str,
98 diff: i64,
99}
100
101struct Edits<'a> {
102 edits: &'a [AtomTextEdit],
103 current: Option<TranslatedEdit<'a>>,
104 acc_diff: i64,
105}
106
107impl<'a> Edits<'a> {
108 fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> {
109 let mut x = Edits { edits: text_edit.as_atoms(), current: None, acc_diff: 0 };
110 x.advance_edit();
111 x
112 }
113 fn advance_edit(&mut self) {
114 self.acc_diff += self.current.as_ref().map_or(0, |x| x.diff);
115 match self.edits.split_first() {
116 Some((next, rest)) => {
117 let delete = self.translate_range(next.delete);
118 let diff = next.insert.len() as i64 - next.delete.len().to_usize() as i64;
119 self.current = Some(TranslatedEdit { delete, insert: &next.insert, diff });
120 self.edits = rest;
121 }
122 None => {
123 self.current = None;
124 }
125 }
126 }
127
128 fn next_inserted_steps(&mut self) -> Option<OffsetStepIter<'a>> {
129 let cur = self.current.as_ref()?;
130 let res = Some(OffsetStepIter { offset: cur.delete.start(), text: &cur.insert });
131 self.advance_edit();
132 res
133 }
134
135 fn next_steps(&mut self, step: &Step) -> NextSteps {
136 let step_pos = match step {
137 &Step::Newline(n) => n,
138 &Step::Utf16Char(r) => r.end(),
139 };
140 let res = match &mut self.current {
141 Some(edit) => {
142 if step_pos <= edit.delete.start() {
143 NextSteps::Use
144 } else if step_pos <= edit.delete.end() {
145 let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert };
146 // empty slice to avoid returning steps again
147 edit.insert = &edit.insert[edit.insert.len()..];
148 NextSteps::ReplaceMany(iter)
149 } else {
150 let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert };
151 // empty slice to avoid returning steps again
152 edit.insert = &edit.insert[edit.insert.len()..];
153 self.advance_edit();
154 NextSteps::AddMany(iter)
155 }
156 }
157 None => NextSteps::Use,
158 };
159 res
160 }
161
162 fn translate_range(&self, range: TextRange) -> TextRange {
163 if self.acc_diff == 0 {
164 range
165 } else {
166 let start = self.translate(range.start());
167 let end = self.translate(range.end());
168 TextRange::from_to(start, end)
169 }
170 }
171
172 fn translate(&self, x: TextUnit) -> TextUnit {
173 if self.acc_diff == 0 {
174 x
175 } else {
176 TextUnit::from((x.to_usize() as i64 + self.acc_diff) as u32)
177 }
178 }
179
180 fn translate_step(&self, x: &Step) -> Step {
181 if self.acc_diff == 0 {
182 x.clone()
183 } else {
184 match x {
185 &Step::Newline(n) => Step::Newline(self.translate(n)),
186 &Step::Utf16Char(r) => Step::Utf16Char(self.translate_range(r)),
187 }
188 }
189 }
190}
191
192#[derive(Debug)]
193struct RunningLineCol {
194 line: u32,
195 last_newline: TextUnit,
196 col_adjust: TextUnit,
197}
198
199impl RunningLineCol {
200 fn new() -> RunningLineCol {
201 RunningLineCol { line: 0, last_newline: TextUnit::from(0), col_adjust: TextUnit::from(0) }
202 }
203
204 fn to_line_col(&self, offset: TextUnit) -> LineCol {
205 LineCol {
206 line: self.line,
207 col_utf16: ((offset - self.last_newline) - self.col_adjust).into(),
208 }
209 }
210
211 fn add_line(&mut self, newline: TextUnit) {
212 self.line += 1;
213 self.last_newline = newline;
214 self.col_adjust = TextUnit::from(0);
215 }
216
217 fn adjust_col(&mut self, range: &TextRange) {
218 self.col_adjust += range.len() - TextUnit::from(1);
219 }
220}
221
222pub fn translate_offset_with_edit(
223 line_index: &LineIndex,
224 offset: TextUnit,
225 text_edit: &TextEdit,
226) -> LineCol {
227 let mut state = Edits::from_text_edit(&text_edit);
228
229 let mut res = RunningLineCol::new();
230
231 macro_rules! test_step {
232 ($x:ident) => {
233 match &$x {
234 Step::Newline(n) => {
235 if offset < *n {
236 return res.to_line_col(offset);
237 } else {
238 res.add_line(*n);
239 }
240 }
241 Step::Utf16Char(x) => {
242 if offset < x.end() {
243 // if the offset is inside a multibyte char it's invalid
244 // clamp it to the start of the char
245 let clamp = offset.min(x.start());
246 return res.to_line_col(clamp);
247 } else {
248 res.adjust_col(x);
249 }
250 }
251 }
252 };
253 }
254
255 for orig_step in LineIndexStepIter::from(line_index) {
256 loop {
257 let translated_step = state.translate_step(&orig_step);
258 match state.next_steps(&translated_step) {
259 NextSteps::Use => {
260 test_step!(translated_step);
261 break;
262 }
263 NextSteps::ReplaceMany(ns) => {
264 for n in ns {
265 test_step!(n);
266 }
267 break;
268 }
269 NextSteps::AddMany(ns) => {
270 for n in ns {
271 test_step!(n);
272 }
273 }
274 }
275 }
276 }
277
278 loop {
279 match state.next_inserted_steps() {
280 None => break,
281 Some(ns) => {
282 for n in ns {
283 test_step!(n);
284 }
285 }
286 }
287 }
288
289 res.to_line_col(offset)
290}
291
292#[cfg(test)]
293mod test {
294 use super::*;
295 use proptest::{prelude::*, proptest};
296 use crate::line_index;
297 use ra_text_edit::test_utils::{arb_offset, arb_text_with_edit};
298 use ra_text_edit::TextEdit;
299
300 #[derive(Debug)]
301 struct ArbTextWithEditAndOffset {
302 text: String,
303 edit: TextEdit,
304 edited_text: String,
305 offset: TextUnit,
306 }
307
308 fn arb_text_with_edit_and_offset() -> BoxedStrategy<ArbTextWithEditAndOffset> {
309 arb_text_with_edit()
310 .prop_flat_map(|x| {
311 let edited_text = x.edit.apply(&x.text);
312 let arb_offset = arb_offset(&edited_text);
313 (Just(x), Just(edited_text), arb_offset).prop_map(|(x, edited_text, offset)| {
314 ArbTextWithEditAndOffset { text: x.text, edit: x.edit, edited_text, offset }
315 })
316 })
317 .boxed()
318 }
319
320 proptest! {
321 #[test]
322 fn test_translate_offset_with_edit(x in arb_text_with_edit_and_offset()) {
323 let expected = line_index::to_line_col(&x.edited_text, x.offset);
324 let line_index = LineIndex::new(&x.text);
325 let actual = translate_offset_with_edit(&line_index, x.offset, &x.edit);
326
327 assert_eq!(actual, expected);
328 }
329 }
330}