aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_editor
diff options
context:
space:
mode:
authorBernardo <[email protected]>2018-12-18 17:46:54 +0000
committerBernardo <[email protected]>2018-12-25 18:59:02 +0000
commit8c9df62c1c6a778a8df9ea028d1dce98c91c4d9d (patch)
tree0d4f3055025ca19b1b375a13b5e4ed1458d90afb /crates/ra_editor
parent881c29192d39f657bf518baf399c47a5bfdc922f (diff)
move translate_offset_with_edit to ra_editor
Diffstat (limited to 'crates/ra_editor')
-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
4 files changed, 266 insertions, 2 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}