diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_editor/src/line_index_utils.rs | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/crates/ra_editor/src/line_index_utils.rs b/crates/ra_editor/src/line_index_utils.rs index e4fad04b2..6bd6a397e 100644 --- a/crates/ra_editor/src/line_index_utils.rs +++ b/crates/ra_editor/src/line_index_utils.rs | |||
@@ -1,6 +1,7 @@ | |||
1 | use ra_text_edit::{AtomTextEdit}; | 1 | use ra_text_edit::{AtomTextEdit}; |
2 | use ra_syntax::{TextUnit, TextRange}; | 2 | use ra_syntax::{TextUnit, TextRange}; |
3 | use crate::{LineIndex, LineCol}; | 3 | use crate::{LineIndex, LineCol}; |
4 | use superslice::Ext; | ||
4 | 5 | ||
5 | #[derive(Debug)] | 6 | #[derive(Debug)] |
6 | struct OffsetNewlineIter<'a> { | 7 | struct OffsetNewlineIter<'a> { |
@@ -58,6 +59,136 @@ trait TranslatedNewlineIterator { | |||
58 | } | 59 | } |
59 | } | 60 | } |
60 | 61 | ||
62 | #[derive(Debug)] | ||
63 | struct AltEdit<'a> { | ||
64 | insert_newlines: OffsetNewlineIter<'a>, | ||
65 | delete: TextRange, | ||
66 | diff: i64, | ||
67 | } | ||
68 | |||
69 | fn translate_range_by(range: TextRange, diff: i64) -> TextRange { | ||
70 | if diff == 0 { | ||
71 | range | ||
72 | } else { | ||
73 | let start = translate_by(range.start(), diff); | ||
74 | let end = translate_by(range.end(), diff); | ||
75 | TextRange::from_to(start, end) | ||
76 | } | ||
77 | } | ||
78 | |||
79 | fn translate_by(x: TextUnit, diff: i64) -> TextUnit { | ||
80 | if diff == 0 { | ||
81 | x | ||
82 | } else { | ||
83 | TextUnit::from((x.to_usize() as i64 + diff) as u32) | ||
84 | } | ||
85 | } | ||
86 | |||
87 | fn to_alt_edits<'a>(offset: TextUnit, edits: &'a [AtomTextEdit]) -> Vec<AltEdit<'a>> { | ||
88 | let mut xs: Vec<AltEdit<'a>> = Vec::with_capacity(edits.len()); | ||
89 | // collect and sort edits | ||
90 | for edit in edits { | ||
91 | // TODO discard after translating? | ||
92 | // if edit.delete.start() >= offset { | ||
93 | // continue; | ||
94 | // } | ||
95 | let insert_index = xs.upper_bound_by_key(&edit.delete.start(), |x| x.delete.start()); | ||
96 | let diff = edit.insert.len() as i64 - edit.delete.len().to_usize() as i64; | ||
97 | xs.insert( | ||
98 | insert_index, | ||
99 | AltEdit { | ||
100 | insert_newlines: OffsetNewlineIter { | ||
101 | offset: edit.delete.start(), | ||
102 | text: &edit.insert, | ||
103 | }, | ||
104 | delete: edit.delete, | ||
105 | diff: diff, | ||
106 | }, | ||
107 | ); | ||
108 | } | ||
109 | // translate edits by previous edits | ||
110 | for i in 1..xs.len() { | ||
111 | let (x, prevs) = xs[0..=i].split_last_mut().unwrap(); | ||
112 | for prev in prevs { | ||
113 | x.delete = translate_range_by(x.delete, prev.diff); | ||
114 | x.insert_newlines.offset = translate_by(x.insert_newlines.offset, prev.diff); | ||
115 | } | ||
116 | } | ||
117 | xs | ||
118 | } | ||
119 | |||
120 | #[derive(Debug)] | ||
121 | enum NextNewline { | ||
122 | Use, | ||
123 | Discard, | ||
124 | Replace(TextUnit), | ||
125 | New(TextUnit), | ||
126 | } | ||
127 | |||
128 | fn next_newline(candidate: Option<TextUnit>, edits: &mut [AltEdit]) -> NextNewline { | ||
129 | let mut candidate = match candidate { | ||
130 | None => { | ||
131 | for edit in edits { | ||
132 | if let Some(inserted) = edit.insert_newlines.next() { | ||
133 | return NextNewline::New(inserted); | ||
134 | } | ||
135 | } | ||
136 | return NextNewline::Use; // END | ||
137 | } | ||
138 | Some(x) => x, | ||
139 | }; | ||
140 | |||
141 | for edit in edits { | ||
142 | if candidate <= edit.delete.start() { | ||
143 | return NextNewline::Replace(candidate); | ||
144 | } else if candidate <= edit.delete.end() { | ||
145 | return match edit.insert_newlines.next() { | ||
146 | Some(x) => NextNewline::Replace(x), | ||
147 | None => NextNewline::Discard, | ||
148 | }; | ||
149 | } else { | ||
150 | if let Some(inserted) = edit.insert_newlines.next() { | ||
151 | return NextNewline::New(inserted); | ||
152 | } | ||
153 | candidate = translate_by(candidate, edit.diff); | ||
154 | } | ||
155 | } | ||
156 | return NextNewline::Replace(candidate); | ||
157 | } | ||
158 | |||
159 | fn count_newlines(offset: TextUnit, line_index: &LineIndex, edits: &[AtomTextEdit]) -> u32 { | ||
160 | let mut edits = to_alt_edits(offset, edits); | ||
161 | let mut orig_newlines = line_index.newlines().iter().map(|x| *x).peekable(); | ||
162 | |||
163 | let mut count = 0; | ||
164 | |||
165 | loop { | ||
166 | let res = next_newline(orig_newlines.peek().map(|x| *x), &mut edits); | ||
167 | let next = match res { | ||
168 | NextNewline::Use => orig_newlines.next(), | ||
169 | NextNewline::Discard => { | ||
170 | orig_newlines.next(); | ||
171 | continue; | ||
172 | } | ||
173 | NextNewline::Replace(new) => { | ||
174 | orig_newlines.next(); | ||
175 | Some(new) | ||
176 | } | ||
177 | NextNewline::New(new) => Some(new), | ||
178 | }; | ||
179 | match next { | ||
180 | Some(n) if n <= offset => { | ||
181 | count += 1; | ||
182 | } | ||
183 | _ => { | ||
184 | break; | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | |||
189 | count | ||
190 | } | ||
191 | |||
61 | struct TranslatedAtomEdit<'a> { | 192 | struct TranslatedAtomEdit<'a> { |
62 | delete: TextRange, | 193 | delete: TextRange, |
63 | insert: &'a str, | 194 | insert: &'a str, |
@@ -256,5 +387,13 @@ mod test { | |||
256 | let actual = translate_offset_with_edit(&line_index, x.offset, &x.edits); | 387 | let actual = translate_offset_with_edit(&line_index, x.offset, &x.edits); |
257 | assert_eq!(actual.line, expected.line); | 388 | assert_eq!(actual.line, expected.line); |
258 | } | 389 | } |
390 | |||
391 | #[test] | ||
392 | fn test_translate_offset_with_edit_alt(x in arb_text_with_offset_and_edits()) { | ||
393 | let line_index = LineIndex::new(&x.text); | ||
394 | let expected = translate_after_edit(&x.text, x.offset, x.edits.clone()); | ||
395 | let actual_lines = count_newlines(x.offset, &line_index, &x.edits); | ||
396 | assert_eq!(actual_lines, expected.line); | ||
397 | } | ||
259 | } | 398 | } |
260 | } | 399 | } |