diff options
Diffstat (limited to 'crates/ra_editor/src')
-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 |
3 files changed, 264 insertions, 2 deletions
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 | } | ||