diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_editor/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ra_editor/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_editor/src/line_index.rs | 4 | ||||
-rw-r--r-- | crates/ra_editor/src/line_index_utils.rs | 260 | ||||
-rw-r--r-- | crates/ra_lsp_server/Cargo.toml | 4 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/conv.rs | 305 |
6 files changed, 267 insertions, 310 deletions
diff --git a/crates/ra_editor/Cargo.toml b/crates/ra_editor/Cargo.toml index c29be1350..1ad99af28 100644 --- a/crates/ra_editor/Cargo.toml +++ b/crates/ra_editor/Cargo.toml | |||
@@ -14,5 +14,7 @@ rustc-hash = "1.0" | |||
14 | ra_syntax = { path = "../ra_syntax" } | 14 | ra_syntax = { path = "../ra_syntax" } |
15 | ra_text_edit = { path = "../ra_text_edit" } | 15 | ra_text_edit = { path = "../ra_text_edit" } |
16 | 16 | ||
17 | proptest = "0.8.7" | ||
18 | |||
17 | [dev-dependencies] | 19 | [dev-dependencies] |
18 | test_utils = { path = "../test_utils" } | 20 | test_utils = { path = "../test_utils" } |
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs index 7a689b0f2..619497f0b 100644 --- a/crates/ra_editor/src/lib.rs +++ b/crates/ra_editor/src/lib.rs | |||
@@ -2,6 +2,7 @@ mod code_actions; | |||
2 | mod extend_selection; | 2 | mod extend_selection; |
3 | mod folding_ranges; | 3 | mod folding_ranges; |
4 | mod line_index; | 4 | mod line_index; |
5 | mod line_index_utils; | ||
5 | mod symbols; | 6 | mod symbols; |
6 | #[cfg(test)] | 7 | #[cfg(test)] |
7 | mod test_utils; | 8 | mod test_utils; |
@@ -12,6 +13,7 @@ pub use self::{ | |||
12 | extend_selection::extend_selection, | 13 | extend_selection::extend_selection, |
13 | folding_ranges::{folding_ranges, Fold, FoldKind}, | 14 | folding_ranges::{folding_ranges, Fold, FoldKind}, |
14 | line_index::{LineCol, LineIndex}, | 15 | line_index::{LineCol, LineIndex}, |
16 | line_index_utils::translate_offset_with_edit, | ||
15 | symbols::{file_structure, file_symbols, FileSymbol, StructureNode}, | 17 | symbols::{file_structure, file_symbols, FileSymbol, StructureNode}, |
16 | typing::{join_lines, on_enter, on_eq_typed}, | 18 | typing::{join_lines, on_enter, on_eq_typed}, |
17 | }; | 19 | }; |
diff --git a/crates/ra_editor/src/line_index.rs b/crates/ra_editor/src/line_index.rs index 7eddfd502..7d9b8d79f 100644 --- a/crates/ra_editor/src/line_index.rs +++ b/crates/ra_editor/src/line_index.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use crate::{TextUnit, TextRange}; | 1 | use crate::TextUnit; |
2 | use rustc_hash::FxHashMap; | 2 | use rustc_hash::FxHashMap; |
3 | use superslice::Ext; | 3 | use superslice::Ext; |
4 | 4 | ||
@@ -121,7 +121,7 @@ impl LineIndex { | |||
121 | col | 121 | col |
122 | } | 122 | } |
123 | 123 | ||
124 | pub fn newlines(&self) -> &[TextUnit] { | 124 | pub(crate) fn newlines(&self) -> &[TextUnit] { |
125 | &self.newlines[1..] | 125 | &self.newlines[1..] |
126 | } | 126 | } |
127 | } | 127 | } |
diff --git a/crates/ra_editor/src/line_index_utils.rs b/crates/ra_editor/src/line_index_utils.rs new file mode 100644 index 000000000..e4fad04b2 --- /dev/null +++ b/crates/ra_editor/src/line_index_utils.rs | |||
@@ -0,0 +1,260 @@ | |||
1 | use ra_text_edit::{AtomTextEdit}; | ||
2 | use ra_syntax::{TextUnit, TextRange}; | ||
3 | use crate::{LineIndex, LineCol}; | ||
4 | |||
5 | #[derive(Debug)] | ||
6 | struct OffsetNewlineIter<'a> { | ||
7 | text: &'a str, | ||
8 | offset: TextUnit, | ||
9 | } | ||
10 | |||
11 | impl<'a> Iterator for OffsetNewlineIter<'a> { | ||
12 | type Item = TextUnit; | ||
13 | fn next(&mut self) -> Option<TextUnit> { | ||
14 | let next_idx = self | ||
15 | .text | ||
16 | .char_indices() | ||
17 | .filter_map(|(i, c)| if c == '\n' { Some(i + 1) } else { None }) | ||
18 | .next()?; | ||
19 | let next = self.offset + TextUnit::from_usize(next_idx); | ||
20 | self.text = &self.text[next_idx..]; | ||
21 | self.offset = next; | ||
22 | Some(next) | ||
23 | } | ||
24 | } | ||
25 | |||
26 | #[derive(Debug, Clone, Copy, PartialEq)] | ||
27 | enum TranslatedPos { | ||
28 | Before, | ||
29 | After, | ||
30 | } | ||
31 | |||
32 | /// None means it was deleted | ||
33 | type TranslatedOffset = Option<(TranslatedPos, TextUnit)>; | ||
34 | |||
35 | fn translate_offset(offset: TextUnit, edit: &TranslatedAtomEdit) -> TranslatedOffset { | ||
36 | if offset <= edit.delete.start() { | ||
37 | Some((TranslatedPos::Before, offset)) | ||
38 | } else if offset <= edit.delete.end() { | ||
39 | None | ||
40 | } else { | ||
41 | let diff = edit.insert.len() as i64 - edit.delete.len().to_usize() as i64; | ||
42 | let after = TextUnit::from((offset.to_usize() as i64 + diff) as u32); | ||
43 | Some((TranslatedPos::After, after)) | ||
44 | } | ||
45 | } | ||
46 | |||
47 | trait TranslatedNewlineIterator { | ||
48 | fn translate(&self, offset: TextUnit) -> TextUnit; | ||
49 | fn translate_range(&self, range: TextRange) -> TextRange { | ||
50 | TextRange::from_to(self.translate(range.start()), self.translate(range.end())) | ||
51 | } | ||
52 | fn next_translated(&mut self) -> Option<TextUnit>; | ||
53 | fn boxed<'a>(self) -> Box<TranslatedNewlineIterator + 'a> | ||
54 | where | ||
55 | Self: 'a + Sized, | ||
56 | { | ||
57 | Box::new(self) | ||
58 | } | ||
59 | } | ||
60 | |||
61 | struct TranslatedAtomEdit<'a> { | ||
62 | delete: TextRange, | ||
63 | insert: &'a str, | ||
64 | } | ||
65 | |||
66 | struct TranslatedNewlines<'a, T: TranslatedNewlineIterator> { | ||
67 | inner: T, | ||
68 | next_inner: Option<TranslatedOffset>, | ||
69 | edit: TranslatedAtomEdit<'a>, | ||
70 | insert: OffsetNewlineIter<'a>, | ||
71 | } | ||
72 | |||
73 | impl<'a, T: TranslatedNewlineIterator> TranslatedNewlines<'a, T> { | ||
74 | fn from(inner: T, edit: &'a AtomTextEdit) -> Self { | ||
75 | let delete = inner.translate_range(edit.delete); | ||
76 | let mut res = TranslatedNewlines { | ||
77 | inner, | ||
78 | next_inner: None, | ||
79 | edit: TranslatedAtomEdit { | ||
80 | delete, | ||
81 | insert: &edit.insert, | ||
82 | }, | ||
83 | insert: OffsetNewlineIter { | ||
84 | offset: delete.start(), | ||
85 | text: &edit.insert, | ||
86 | }, | ||
87 | }; | ||
88 | // prepare next_inner | ||
89 | res.advance_inner(); | ||
90 | res | ||
91 | } | ||
92 | |||
93 | fn advance_inner(&mut self) { | ||
94 | self.next_inner = self | ||
95 | .inner | ||
96 | .next_translated() | ||
97 | .map(|x| translate_offset(x, &self.edit)); | ||
98 | } | ||
99 | } | ||
100 | |||
101 | impl<'a, T: TranslatedNewlineIterator> TranslatedNewlineIterator for TranslatedNewlines<'a, T> { | ||
102 | fn translate(&self, offset: TextUnit) -> TextUnit { | ||
103 | let offset = self.inner.translate(offset); | ||
104 | let (_, offset) = | ||
105 | translate_offset(offset, &self.edit).expect("translate_unit returned None"); | ||
106 | offset | ||
107 | } | ||
108 | |||
109 | fn next_translated(&mut self) -> Option<TextUnit> { | ||
110 | match self.next_inner { | ||
111 | None => self.insert.next(), | ||
112 | Some(next) => match next { | ||
113 | None => self.insert.next().or_else(|| { | ||
114 | self.advance_inner(); | ||
115 | self.next_translated() | ||
116 | }), | ||
117 | Some((TranslatedPos::Before, next)) => { | ||
118 | self.advance_inner(); | ||
119 | Some(next) | ||
120 | } | ||
121 | Some((TranslatedPos::After, next)) => self.insert.next().or_else(|| { | ||
122 | self.advance_inner(); | ||
123 | Some(next) | ||
124 | }), | ||
125 | }, | ||
126 | } | ||
127 | } | ||
128 | } | ||
129 | |||
130 | impl<'a> Iterator for Box<dyn TranslatedNewlineIterator + 'a> { | ||
131 | type Item = TextUnit; | ||
132 | fn next(&mut self) -> Option<TextUnit> { | ||
133 | self.next_translated() | ||
134 | } | ||
135 | } | ||
136 | |||
137 | impl<T: TranslatedNewlineIterator + ?Sized> TranslatedNewlineIterator for Box<T> { | ||
138 | fn translate(&self, offset: TextUnit) -> TextUnit { | ||
139 | self.as_ref().translate(offset) | ||
140 | } | ||
141 | fn next_translated(&mut self) -> Option<TextUnit> { | ||
142 | self.as_mut().next_translated() | ||
143 | } | ||
144 | } | ||
145 | |||
146 | struct IteratorWrapper<T: Iterator<Item = TextUnit>>(T); | ||
147 | |||
148 | impl<T: Iterator<Item = TextUnit>> TranslatedNewlineIterator for IteratorWrapper<T> { | ||
149 | fn translate(&self, offset: TextUnit) -> TextUnit { | ||
150 | offset | ||
151 | } | ||
152 | fn next_translated(&mut self) -> Option<TextUnit> { | ||
153 | self.0.next() | ||
154 | } | ||
155 | } | ||
156 | |||
157 | impl<T: Iterator<Item = TextUnit>> Iterator for IteratorWrapper<T> { | ||
158 | type Item = TextUnit; | ||
159 | fn next(&mut self) -> Option<TextUnit> { | ||
160 | self.0.next() | ||
161 | } | ||
162 | } | ||
163 | |||
164 | fn translate_newlines<'a>( | ||
165 | mut newlines: Box<TranslatedNewlineIterator + 'a>, | ||
166 | edits: &'a [AtomTextEdit], | ||
167 | ) -> Box<TranslatedNewlineIterator + 'a> { | ||
168 | for edit in edits { | ||
169 | newlines = TranslatedNewlines::from(newlines, edit).boxed(); | ||
170 | } | ||
171 | newlines | ||
172 | } | ||
173 | |||
174 | pub fn translate_offset_with_edit( | ||
175 | pre_edit_index: &LineIndex, | ||
176 | offset: TextUnit, | ||
177 | edits: &[AtomTextEdit], | ||
178 | ) -> LineCol { | ||
179 | let mut newlines: Box<TranslatedNewlineIterator> = Box::new(IteratorWrapper( | ||
180 | pre_edit_index.newlines().iter().map(|x| *x), | ||
181 | )); | ||
182 | |||
183 | newlines = translate_newlines(newlines, edits); | ||
184 | |||
185 | let mut line = 0; | ||
186 | for n in newlines { | ||
187 | if n > offset { | ||
188 | break; | ||
189 | } | ||
190 | line += 1; | ||
191 | } | ||
192 | |||
193 | LineCol { | ||
194 | line: line, | ||
195 | col_utf16: 0, // TODO not implemented yet | ||
196 | } | ||
197 | } | ||
198 | |||
199 | #[cfg(test)] | ||
200 | mod test { | ||
201 | use proptest::{prelude::*, proptest, proptest_helper}; | ||
202 | use super::*; | ||
203 | use ra_text_edit::test_utils::{arb_text, arb_offset, arb_edits}; | ||
204 | |||
205 | #[derive(Debug)] | ||
206 | struct ArbTextWithOffsetAndEdits { | ||
207 | text: String, | ||
208 | offset: TextUnit, | ||
209 | edits: Vec<AtomTextEdit>, | ||
210 | } | ||
211 | |||
212 | fn arb_text_with_offset_and_edits() -> BoxedStrategy<ArbTextWithOffsetAndEdits> { | ||
213 | arb_text() | ||
214 | .prop_flat_map(|text| { | ||
215 | (arb_offset(&text), arb_edits(&text), Just(text)).prop_map( | ||
216 | |(offset, edits, text)| ArbTextWithOffsetAndEdits { | ||
217 | text, | ||
218 | offset, | ||
219 | edits, | ||
220 | }, | ||
221 | ) | ||
222 | }) | ||
223 | .boxed() | ||
224 | } | ||
225 | |||
226 | fn edit_text(pre_edit_text: &str, mut edits: Vec<AtomTextEdit>) -> String { | ||
227 | // apply edits ordered from last to first | ||
228 | // since they should not overlap we can just use start() | ||
229 | edits.sort_by_key(|x| -(x.delete.start().to_usize() as isize)); | ||
230 | |||
231 | let mut text = pre_edit_text.to_owned(); | ||
232 | |||
233 | for edit in &edits { | ||
234 | let range = edit.delete.start().to_usize()..edit.delete.end().to_usize(); | ||
235 | text.replace_range(range, &edit.insert); | ||
236 | } | ||
237 | |||
238 | text | ||
239 | } | ||
240 | |||
241 | fn translate_after_edit( | ||
242 | pre_edit_text: &str, | ||
243 | offset: TextUnit, | ||
244 | edits: Vec<AtomTextEdit>, | ||
245 | ) -> LineCol { | ||
246 | let text = edit_text(pre_edit_text, edits); | ||
247 | let line_index = LineIndex::new(&text); | ||
248 | line_index.line_col(offset) | ||
249 | } | ||
250 | |||
251 | proptest! { | ||
252 | #[test] | ||
253 | fn test_translate_offset_with_edit(x in arb_text_with_offset_and_edits()) { | ||
254 | let line_index = LineIndex::new(&x.text); | ||
255 | let expected = translate_after_edit(&x.text, x.offset, x.edits.clone()); | ||
256 | let actual = translate_offset_with_edit(&line_index, x.offset, &x.edits); | ||
257 | assert_eq!(actual.line, expected.line); | ||
258 | } | ||
259 | } | ||
260 | } | ||
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index d73ff158f..3c8c240cd 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml | |||
@@ -34,8 +34,6 @@ ra_analysis = { path = "../ra_analysis" } | |||
34 | gen_lsp_server = { path = "../gen_lsp_server" } | 34 | gen_lsp_server = { path = "../gen_lsp_server" } |
35 | ra_vfs = { path = "../ra_vfs" } | 35 | ra_vfs = { path = "../ra_vfs" } |
36 | 36 | ||
37 | proptest = "0.8.7" | ||
38 | |||
39 | [dev-dependencies] | 37 | [dev-dependencies] |
40 | tempdir = "0.3.7" | 38 | tempdir = "0.3.7" |
41 | test_utils = { path = "../test_utils" } \ No newline at end of file | 39 | test_utils = { path = "../test_utils" } |
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs index 6d0ebbcd9..051f1f995 100644 --- a/crates/ra_lsp_server/src/conv.rs +++ b/crates/ra_lsp_server/src/conv.rs | |||
@@ -296,202 +296,6 @@ fn translate_offset_with_edit( | |||
296 | } | 296 | } |
297 | } | 297 | } |
298 | 298 | ||
299 | #[derive(Debug)] | ||
300 | struct OffsetNewlineIter<'a> { | ||
301 | text: &'a str, | ||
302 | offset: TextUnit, | ||
303 | } | ||
304 | |||
305 | impl<'a> Iterator for OffsetNewlineIter<'a> { | ||
306 | type Item = TextUnit; | ||
307 | fn next(&mut self) -> Option<TextUnit> { | ||
308 | let next_idx = self | ||
309 | .text | ||
310 | .char_indices() | ||
311 | .filter_map(|(i, c)| if c == '\n' { Some(i + 1) } else { None }) | ||
312 | .next()?; | ||
313 | let next = self.offset + TextUnit::from_usize(next_idx); | ||
314 | self.text = &self.text[next_idx..]; | ||
315 | self.offset = next; | ||
316 | Some(next) | ||
317 | } | ||
318 | } | ||
319 | |||
320 | #[derive(Debug, Clone, Copy, PartialEq)] | ||
321 | enum TranslatedPos { | ||
322 | Before, | ||
323 | After, | ||
324 | } | ||
325 | |||
326 | /// None means it was deleted | ||
327 | type TranslatedOffset = Option<(TranslatedPos, TextUnit)>; | ||
328 | |||
329 | fn translate_offset(offset: TextUnit, edit: &TranslatedAtomEdit) -> TranslatedOffset { | ||
330 | if offset <= edit.delete.start() { | ||
331 | Some((TranslatedPos::Before, offset)) | ||
332 | } else if offset <= edit.delete.end() { | ||
333 | None | ||
334 | } else { | ||
335 | let diff = edit.insert.len() as i64 - edit.delete.len().to_usize() as i64; | ||
336 | let after = TextUnit::from((offset.to_usize() as i64 + diff) as u32); | ||
337 | Some((TranslatedPos::After, after)) | ||
338 | } | ||
339 | } | ||
340 | |||
341 | trait TranslatedNewlineIterator { | ||
342 | fn translate(&self, offset: TextUnit) -> TextUnit; | ||
343 | fn translate_range(&self, range: TextRange) -> TextRange { | ||
344 | TextRange::from_to(self.translate(range.start()), self.translate(range.end())) | ||
345 | } | ||
346 | fn next_translated(&mut self) -> Option<TextUnit>; | ||
347 | fn boxed<'a>(self) -> Box<TranslatedNewlineIterator + 'a> | ||
348 | where | ||
349 | Self: 'a + Sized, | ||
350 | { | ||
351 | Box::new(self) | ||
352 | } | ||
353 | } | ||
354 | |||
355 | struct TranslatedAtomEdit<'a> { | ||
356 | delete: TextRange, | ||
357 | insert: &'a str, | ||
358 | } | ||
359 | |||
360 | struct TranslatedNewlines<'a, T: TranslatedNewlineIterator> { | ||
361 | inner: T, | ||
362 | next_inner: Option<TranslatedOffset>, | ||
363 | edit: TranslatedAtomEdit<'a>, | ||
364 | insert: OffsetNewlineIter<'a>, | ||
365 | } | ||
366 | |||
367 | impl<'a, T: TranslatedNewlineIterator> TranslatedNewlines<'a, T> { | ||
368 | fn from(inner: T, edit: &'a AtomTextEdit) -> Self { | ||
369 | let delete = inner.translate_range(edit.delete); | ||
370 | let mut res = TranslatedNewlines { | ||
371 | inner, | ||
372 | next_inner: None, | ||
373 | edit: TranslatedAtomEdit { | ||
374 | delete, | ||
375 | insert: &edit.insert, | ||
376 | }, | ||
377 | insert: OffsetNewlineIter { | ||
378 | offset: delete.start(), | ||
379 | text: &edit.insert, | ||
380 | }, | ||
381 | }; | ||
382 | // prepare next_inner | ||
383 | res.advance_inner(); | ||
384 | res | ||
385 | } | ||
386 | |||
387 | fn advance_inner(&mut self) { | ||
388 | self.next_inner = self | ||
389 | .inner | ||
390 | .next_translated() | ||
391 | .map(|x| translate_offset(x, &self.edit)); | ||
392 | } | ||
393 | } | ||
394 | |||
395 | impl<'a, T: TranslatedNewlineIterator> TranslatedNewlineIterator for TranslatedNewlines<'a, T> { | ||
396 | fn translate(&self, offset: TextUnit) -> TextUnit { | ||
397 | let offset = self.inner.translate(offset); | ||
398 | let (_, offset) = | ||
399 | translate_offset(offset, &self.edit).expect("translate_unit returned None"); | ||
400 | offset | ||
401 | } | ||
402 | |||
403 | fn next_translated(&mut self) -> Option<TextUnit> { | ||
404 | match self.next_inner { | ||
405 | None => self.insert.next(), | ||
406 | Some(next) => match next { | ||
407 | None => self.insert.next().or_else(|| { | ||
408 | self.advance_inner(); | ||
409 | self.next_translated() | ||
410 | }), | ||
411 | Some((TranslatedPos::Before, next)) => { | ||
412 | self.advance_inner(); | ||
413 | Some(next) | ||
414 | } | ||
415 | Some((TranslatedPos::After, next)) => self.insert.next().or_else(|| { | ||
416 | self.advance_inner(); | ||
417 | Some(next) | ||
418 | }), | ||
419 | }, | ||
420 | } | ||
421 | } | ||
422 | } | ||
423 | |||
424 | impl<'a> Iterator for Box<dyn TranslatedNewlineIterator + 'a> { | ||
425 | type Item = TextUnit; | ||
426 | fn next(&mut self) -> Option<TextUnit> { | ||
427 | self.next_translated() | ||
428 | } | ||
429 | } | ||
430 | |||
431 | impl<T: TranslatedNewlineIterator + ?Sized> TranslatedNewlineIterator for Box<T> { | ||
432 | fn translate(&self, offset: TextUnit) -> TextUnit { | ||
433 | self.as_ref().translate(offset) | ||
434 | } | ||
435 | fn next_translated(&mut self) -> Option<TextUnit> { | ||
436 | self.as_mut().next_translated() | ||
437 | } | ||
438 | } | ||
439 | |||
440 | struct IteratorWrapper<T: Iterator<Item = TextUnit>>(T); | ||
441 | |||
442 | impl<T: Iterator<Item = TextUnit>> TranslatedNewlineIterator for IteratorWrapper<T> { | ||
443 | fn translate(&self, offset: TextUnit) -> TextUnit { | ||
444 | offset | ||
445 | } | ||
446 | fn next_translated(&mut self) -> Option<TextUnit> { | ||
447 | self.0.next() | ||
448 | } | ||
449 | } | ||
450 | |||
451 | impl<T: Iterator<Item = TextUnit>> Iterator for IteratorWrapper<T> { | ||
452 | type Item = TextUnit; | ||
453 | fn next(&mut self) -> Option<TextUnit> { | ||
454 | self.0.next() | ||
455 | } | ||
456 | } | ||
457 | |||
458 | fn translate_newlines<'a>( | ||
459 | mut newlines: Box<TranslatedNewlineIterator + 'a>, | ||
460 | edits: &'a [AtomTextEdit], | ||
461 | ) -> Box<TranslatedNewlineIterator + 'a> { | ||
462 | for edit in edits { | ||
463 | newlines = TranslatedNewlines::from(newlines, edit).boxed(); | ||
464 | } | ||
465 | newlines | ||
466 | } | ||
467 | |||
468 | #[allow(dead_code)] | ||
469 | fn translate_offset_with_edit_fast( | ||
470 | pre_edit_index: &LineIndex, | ||
471 | offset: TextUnit, | ||
472 | edits: &[AtomTextEdit], | ||
473 | ) -> LineCol { | ||
474 | // println!("{:?}", pre_edit_index.newlines()); | ||
475 | let mut newlines: Box<TranslatedNewlineIterator> = Box::new(IteratorWrapper( | ||
476 | pre_edit_index.newlines().iter().map(|x| *x), | ||
477 | )); | ||
478 | |||
479 | newlines = translate_newlines(newlines, edits); | ||
480 | |||
481 | let mut line = 0; | ||
482 | for n in newlines { | ||
483 | if n > offset { | ||
484 | break; | ||
485 | } | ||
486 | line += 1; | ||
487 | } | ||
488 | |||
489 | LineCol { | ||
490 | line: line, | ||
491 | col_utf16: 0, | ||
492 | } | ||
493 | } | ||
494 | |||
495 | impl TryConvWith for SourceFileEdit { | 299 | impl TryConvWith for SourceFileEdit { |
496 | type Ctx = ServerWorld; | 300 | type Ctx = ServerWorld; |
497 | type Output = TextDocumentEdit; | 301 | type Output = TextDocumentEdit; |
@@ -582,112 +386,3 @@ where | |||
582 | self.iter.next().map(|item| item.conv_with(self.ctx)) | 386 | self.iter.next().map(|item| item.conv_with(self.ctx)) |
583 | } | 387 | } |
584 | } | 388 | } |
585 | |||
586 | #[cfg(test)] | ||
587 | mod test { | ||
588 | use proptest::{prelude::*, proptest, proptest_helper}; | ||
589 | use super::*; | ||
590 | use ra_text_edit::test_utils::{arb_text, arb_offset, arb_edits}; | ||
591 | |||
592 | #[derive(Debug)] | ||
593 | struct ArbTextWithOffsetAndEdits { | ||
594 | text: String, | ||
595 | offset: TextUnit, | ||
596 | edits: Vec<AtomTextEdit>, | ||
597 | } | ||
598 | |||
599 | fn arb_text_with_offset_and_edits() -> BoxedStrategy<ArbTextWithOffsetAndEdits> { | ||
600 | arb_text() | ||
601 | .prop_flat_map(|text| { | ||
602 | (arb_offset(&text), arb_edits(&text), Just(text)).prop_map( | ||
603 | |(offset, edits, text)| ArbTextWithOffsetAndEdits { | ||
604 | text, | ||
605 | offset, | ||
606 | edits, | ||
607 | }, | ||
608 | ) | ||
609 | }) | ||
610 | .boxed() | ||
611 | } | ||
612 | |||
613 | fn edit_text(pre_edit_text: &str, mut edits: Vec<AtomTextEdit>) -> String { | ||
614 | // apply edits ordered from last to first | ||
615 | // since they should not overlap we can just use start() | ||
616 | edits.sort_by_key(|x| -(x.delete.start().to_usize() as isize)); | ||
617 | |||
618 | let mut text = pre_edit_text.to_owned(); | ||
619 | |||
620 | for edit in &edits { | ||
621 | let range = edit.delete.start().to_usize()..edit.delete.end().to_usize(); | ||
622 | text.replace_range(range, &edit.insert); | ||
623 | } | ||
624 | |||
625 | text | ||
626 | } | ||
627 | |||
628 | fn translate_after_edit( | ||
629 | pre_edit_text: &str, | ||
630 | offset: TextUnit, | ||
631 | edits: Vec<AtomTextEdit>, | ||
632 | ) -> LineCol { | ||
633 | let text = edit_text(pre_edit_text, edits); | ||
634 | let line_index = LineIndex::new(&text); | ||
635 | line_index.line_col(offset) | ||
636 | } | ||
637 | |||
638 | proptest! { | ||
639 | #[test] | ||
640 | fn test_translate_offset_with_edit(x in arb_text_with_offset_and_edits()) { | ||
641 | let line_index = LineIndex::new(&x.text); | ||
642 | let expected = translate_after_edit(&x.text, x.offset, x.edits.clone()); | ||
643 | let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits); | ||
644 | assert_eq!(actual.line, expected.line); | ||
645 | } | ||
646 | } | ||
647 | |||
648 | #[test] | ||
649 | fn test_translate_offset_with_edit_1() { | ||
650 | let x = ArbTextWithOffsetAndEdits { | ||
651 | text: "jbnan".to_owned(), | ||
652 | offset: 3.into(), | ||
653 | edits: vec![ | ||
654 | AtomTextEdit::delete(TextRange::from_to(1.into(), 3.into())), | ||
655 | AtomTextEdit::insert(4.into(), "\n".into()), | ||
656 | ], | ||
657 | }; | ||
658 | let line_index = LineIndex::new(&x.text); | ||
659 | let expected = translate_after_edit(&x.text, x.offset, x.edits.clone()); | ||
660 | let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits); | ||
661 | // assert_eq!(actual, expected); | ||
662 | assert_eq!(actual.line, expected.line); | ||
663 | } | ||
664 | |||
665 | #[test] | ||
666 | fn test_translate_offset_with_edit_2() { | ||
667 | let x = ArbTextWithOffsetAndEdits { | ||
668 | text: "aa\n".to_owned(), | ||
669 | offset: 1.into(), | ||
670 | edits: vec![AtomTextEdit::delete(TextRange::from_to(0.into(), 2.into()))], | ||
671 | }; | ||
672 | let line_index = LineIndex::new(&x.text); | ||
673 | let expected = translate_after_edit(&x.text, x.offset, x.edits.clone()); | ||
674 | let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits); | ||
675 | // assert_eq!(actual, expected); | ||
676 | assert_eq!(actual.line, expected.line); | ||
677 | } | ||
678 | |||
679 | #[test] | ||
680 | fn test_translate_offset_with_edit_3() { | ||
681 | let x = ArbTextWithOffsetAndEdits { | ||
682 | text: "".to_owned(), | ||
683 | offset: 0.into(), | ||
684 | edits: vec![AtomTextEdit::insert(0.into(), "\n".into())], | ||
685 | }; | ||
686 | let line_index = LineIndex::new(&x.text); | ||
687 | let expected = translate_after_edit(&x.text, x.offset, x.edits.clone()); | ||
688 | let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits); | ||
689 | // assert_eq!(actual, expected); | ||
690 | assert_eq!(actual.line, expected.line); | ||
691 | } | ||
692 | |||
693 | } | ||