aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_editor/Cargo.toml2
-rw-r--r--crates/ra_editor/src/lib.rs2
-rw-r--r--crates/ra_editor/src/line_index.rs4
-rw-r--r--crates/ra_editor/src/line_index_utils.rs260
-rw-r--r--crates/ra_lsp_server/Cargo.toml4
-rw-r--r--crates/ra_lsp_server/src/conv.rs305
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"
14ra_syntax = { path = "../ra_syntax" } 14ra_syntax = { path = "../ra_syntax" }
15ra_text_edit = { path = "../ra_text_edit" } 15ra_text_edit = { path = "../ra_text_edit" }
16 16
17proptest = "0.8.7"
18
17[dev-dependencies] 19[dev-dependencies]
18test_utils = { path = "../test_utils" } 20test_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;
2mod extend_selection; 2mod extend_selection;
3mod folding_ranges; 3mod folding_ranges;
4mod line_index; 4mod line_index;
5mod line_index_utils;
5mod symbols; 6mod symbols;
6#[cfg(test)] 7#[cfg(test)]
7mod test_utils; 8mod 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 @@
1use crate::{TextUnit, TextRange}; 1use crate::TextUnit;
2use rustc_hash::FxHashMap; 2use rustc_hash::FxHashMap;
3use superslice::Ext; 3use 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 @@
1use ra_text_edit::{AtomTextEdit};
2use ra_syntax::{TextUnit, TextRange};
3use crate::{LineIndex, LineCol};
4
5#[derive(Debug)]
6struct OffsetNewlineIter<'a> {
7 text: &'a str,
8 offset: TextUnit,
9}
10
11impl<'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)]
27enum TranslatedPos {
28 Before,
29 After,
30}
31
32/// None means it was deleted
33type TranslatedOffset = Option<(TranslatedPos, TextUnit)>;
34
35fn 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
47trait 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
61struct TranslatedAtomEdit<'a> {
62 delete: TextRange,
63 insert: &'a str,
64}
65
66struct TranslatedNewlines<'a, T: TranslatedNewlineIterator> {
67 inner: T,
68 next_inner: Option<TranslatedOffset>,
69 edit: TranslatedAtomEdit<'a>,
70 insert: OffsetNewlineIter<'a>,
71}
72
73impl<'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
101impl<'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
130impl<'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
137impl<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
146struct IteratorWrapper<T: Iterator<Item = TextUnit>>(T);
147
148impl<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
157impl<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
164fn 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
174pub 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)]
200mod 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" }
34gen_lsp_server = { path = "../gen_lsp_server" } 34gen_lsp_server = { path = "../gen_lsp_server" }
35ra_vfs = { path = "../ra_vfs" } 35ra_vfs = { path = "../ra_vfs" }
36 36
37proptest = "0.8.7"
38
39[dev-dependencies] 37[dev-dependencies]
40tempdir = "0.3.7" 38tempdir = "0.3.7"
41test_utils = { path = "../test_utils" } \ No newline at end of file 39test_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)]
300struct OffsetNewlineIter<'a> {
301 text: &'a str,
302 offset: TextUnit,
303}
304
305impl<'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)]
321enum TranslatedPos {
322 Before,
323 After,
324}
325
326/// None means it was deleted
327type TranslatedOffset = Option<(TranslatedPos, TextUnit)>;
328
329fn 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
341trait 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
355struct TranslatedAtomEdit<'a> {
356 delete: TextRange,
357 insert: &'a str,
358}
359
360struct TranslatedNewlines<'a, T: TranslatedNewlineIterator> {
361 inner: T,
362 next_inner: Option<TranslatedOffset>,
363 edit: TranslatedAtomEdit<'a>,
364 insert: OffsetNewlineIter<'a>,
365}
366
367impl<'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
395impl<'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
424impl<'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
431impl<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
440struct IteratorWrapper<T: Iterator<Item = TextUnit>>(T);
441
442impl<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
451impl<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
458fn 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)]
469fn 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
495impl TryConvWith for SourceFileEdit { 299impl 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)]
587mod 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}