diff options
Diffstat (limited to 'crates/ra_editor/src/line_index.rs')
-rw-r--r-- | crates/ra_editor/src/line_index.rs | 283 |
1 files changed, 179 insertions, 104 deletions
diff --git a/crates/ra_editor/src/line_index.rs b/crates/ra_editor/src/line_index.rs index aab7e4081..898fee7e0 100644 --- a/crates/ra_editor/src/line_index.rs +++ b/crates/ra_editor/src/line_index.rs | |||
@@ -4,8 +4,8 @@ use superslice::Ext; | |||
4 | 4 | ||
5 | #[derive(Clone, Debug, PartialEq, Eq)] | 5 | #[derive(Clone, Debug, PartialEq, Eq)] |
6 | pub struct LineIndex { | 6 | pub struct LineIndex { |
7 | newlines: Vec<TextUnit>, | 7 | pub(crate) newlines: Vec<TextUnit>, |
8 | utf16_lines: FxHashMap<u32, Vec<Utf16Char>>, | 8 | pub(crate) utf16_lines: FxHashMap<u32, Vec<Utf16Char>>, |
9 | } | 9 | } |
10 | 10 | ||
11 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | 11 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] |
@@ -15,9 +15,9 @@ pub struct LineCol { | |||
15 | } | 15 | } |
16 | 16 | ||
17 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] | 17 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] |
18 | struct Utf16Char { | 18 | pub(crate) struct Utf16Char { |
19 | start: TextUnit, | 19 | pub(crate) start: TextUnit, |
20 | end: TextUnit, | 20 | pub(crate) end: TextUnit, |
21 | } | 21 | } |
22 | 22 | ||
23 | impl Utf16Char { | 23 | impl Utf16Char { |
@@ -62,6 +62,12 @@ impl LineIndex { | |||
62 | 62 | ||
63 | curr_col += char_len; | 63 | curr_col += char_len; |
64 | } | 64 | } |
65 | |||
66 | // Save any utf-16 characters seen in the last line | ||
67 | if utf16_chars.len() > 0 { | ||
68 | utf16_lines.insert(line, utf16_chars); | ||
69 | } | ||
70 | |||
65 | LineIndex { | 71 | LineIndex { |
66 | newlines, | 72 | newlines, |
67 | utf16_lines, | 73 | utf16_lines, |
@@ -122,111 +128,179 @@ impl LineIndex { | |||
122 | } | 128 | } |
123 | } | 129 | } |
124 | 130 | ||
125 | #[test] | 131 | #[cfg(test)] |
126 | fn test_line_index() { | 132 | /// Simple reference implementation to use in proptests |
127 | let text = "hello\nworld"; | 133 | pub fn to_line_col(text: &str, offset: TextUnit) -> LineCol { |
128 | let index = LineIndex::new(text); | 134 | let mut res = LineCol { |
129 | assert_eq!( | 135 | line: 0, |
130 | index.line_col(0.into()), | 136 | col_utf16: 0, |
131 | LineCol { | 137 | }; |
132 | line: 0, | 138 | for (i, c) in text.char_indices() { |
133 | col_utf16: 0 | 139 | if i + c.len_utf8() > offset.to_usize() { |
134 | } | 140 | // if it's an invalid offset, inside a multibyte char |
135 | ); | 141 | // return as if it was at the start of the char |
136 | assert_eq!( | 142 | break; |
137 | index.line_col(1.into()), | ||
138 | LineCol { | ||
139 | line: 0, | ||
140 | col_utf16: 1 | ||
141 | } | ||
142 | ); | ||
143 | assert_eq!( | ||
144 | index.line_col(5.into()), | ||
145 | LineCol { | ||
146 | line: 0, | ||
147 | col_utf16: 5 | ||
148 | } | ||
149 | ); | ||
150 | assert_eq!( | ||
151 | index.line_col(6.into()), | ||
152 | LineCol { | ||
153 | line: 1, | ||
154 | col_utf16: 0 | ||
155 | } | ||
156 | ); | ||
157 | assert_eq!( | ||
158 | index.line_col(7.into()), | ||
159 | LineCol { | ||
160 | line: 1, | ||
161 | col_utf16: 1 | ||
162 | } | ||
163 | ); | ||
164 | assert_eq!( | ||
165 | index.line_col(8.into()), | ||
166 | LineCol { | ||
167 | line: 1, | ||
168 | col_utf16: 2 | ||
169 | } | ||
170 | ); | ||
171 | assert_eq!( | ||
172 | index.line_col(10.into()), | ||
173 | LineCol { | ||
174 | line: 1, | ||
175 | col_utf16: 4 | ||
176 | } | ||
177 | ); | ||
178 | assert_eq!( | ||
179 | index.line_col(11.into()), | ||
180 | LineCol { | ||
181 | line: 1, | ||
182 | col_utf16: 5 | ||
183 | } | 143 | } |
184 | ); | 144 | if c == '\n' { |
185 | assert_eq!( | 145 | res.line += 1; |
186 | index.line_col(12.into()), | 146 | res.col_utf16 = 0; |
187 | LineCol { | 147 | } else { |
188 | line: 1, | 148 | res.col_utf16 += 1; |
189 | col_utf16: 6 | ||
190 | } | 149 | } |
191 | ); | 150 | } |
151 | res | ||
152 | } | ||
192 | 153 | ||
193 | let text = "\nhello\nworld"; | 154 | #[cfg(test)] |
194 | let index = LineIndex::new(text); | 155 | mod test_line_index { |
195 | assert_eq!( | 156 | use super::*; |
196 | index.line_col(0.into()), | 157 | use proptest::{prelude::*, proptest, proptest_helper}; |
197 | LineCol { | 158 | use ra_text_edit::test_utils::{arb_text, arb_offset}; |
159 | |||
160 | #[test] | ||
161 | fn test_line_index() { | ||
162 | let text = "hello\nworld"; | ||
163 | let index = LineIndex::new(text); | ||
164 | assert_eq!( | ||
165 | index.line_col(0.into()), | ||
166 | LineCol { | ||
167 | line: 0, | ||
168 | col_utf16: 0 | ||
169 | } | ||
170 | ); | ||
171 | assert_eq!( | ||
172 | index.line_col(1.into()), | ||
173 | LineCol { | ||
174 | line: 0, | ||
175 | col_utf16: 1 | ||
176 | } | ||
177 | ); | ||
178 | assert_eq!( | ||
179 | index.line_col(5.into()), | ||
180 | LineCol { | ||
181 | line: 0, | ||
182 | col_utf16: 5 | ||
183 | } | ||
184 | ); | ||
185 | assert_eq!( | ||
186 | index.line_col(6.into()), | ||
187 | LineCol { | ||
188 | line: 1, | ||
189 | col_utf16: 0 | ||
190 | } | ||
191 | ); | ||
192 | assert_eq!( | ||
193 | index.line_col(7.into()), | ||
194 | LineCol { | ||
195 | line: 1, | ||
196 | col_utf16: 1 | ||
197 | } | ||
198 | ); | ||
199 | assert_eq!( | ||
200 | index.line_col(8.into()), | ||
201 | LineCol { | ||
202 | line: 1, | ||
203 | col_utf16: 2 | ||
204 | } | ||
205 | ); | ||
206 | assert_eq!( | ||
207 | index.line_col(10.into()), | ||
208 | LineCol { | ||
209 | line: 1, | ||
210 | col_utf16: 4 | ||
211 | } | ||
212 | ); | ||
213 | assert_eq!( | ||
214 | index.line_col(11.into()), | ||
215 | LineCol { | ||
216 | line: 1, | ||
217 | col_utf16: 5 | ||
218 | } | ||
219 | ); | ||
220 | assert_eq!( | ||
221 | index.line_col(12.into()), | ||
222 | LineCol { | ||
223 | line: 1, | ||
224 | col_utf16: 6 | ||
225 | } | ||
226 | ); | ||
227 | |||
228 | let text = "\nhello\nworld"; | ||
229 | let index = LineIndex::new(text); | ||
230 | assert_eq!( | ||
231 | index.line_col(0.into()), | ||
232 | LineCol { | ||
233 | line: 0, | ||
234 | col_utf16: 0 | ||
235 | } | ||
236 | ); | ||
237 | assert_eq!( | ||
238 | index.line_col(1.into()), | ||
239 | LineCol { | ||
240 | line: 1, | ||
241 | col_utf16: 0 | ||
242 | } | ||
243 | ); | ||
244 | assert_eq!( | ||
245 | index.line_col(2.into()), | ||
246 | LineCol { | ||
247 | line: 1, | ||
248 | col_utf16: 1 | ||
249 | } | ||
250 | ); | ||
251 | assert_eq!( | ||
252 | index.line_col(6.into()), | ||
253 | LineCol { | ||
254 | line: 1, | ||
255 | col_utf16: 5 | ||
256 | } | ||
257 | ); | ||
258 | assert_eq!( | ||
259 | index.line_col(7.into()), | ||
260 | LineCol { | ||
261 | line: 2, | ||
262 | col_utf16: 0 | ||
263 | } | ||
264 | ); | ||
265 | } | ||
266 | |||
267 | fn arb_text_with_offset() -> BoxedStrategy<(TextUnit, String)> { | ||
268 | arb_text() | ||
269 | .prop_flat_map(|text| (arb_offset(&text), Just(text))) | ||
270 | .boxed() | ||
271 | } | ||
272 | |||
273 | fn to_line_col(text: &str, offset: TextUnit) -> LineCol { | ||
274 | let mut res = LineCol { | ||
198 | line: 0, | 275 | line: 0, |
199 | col_utf16: 0 | 276 | col_utf16: 0, |
200 | } | 277 | }; |
201 | ); | 278 | for (i, c) in text.char_indices() { |
202 | assert_eq!( | 279 | if i + c.len_utf8() > offset.to_usize() { |
203 | index.line_col(1.into()), | 280 | // if it's an invalid offset, inside a multibyte char |
204 | LineCol { | 281 | // return as if it was at the start of the char |
205 | line: 1, | 282 | break; |
206 | col_utf16: 0 | 283 | } |
207 | } | 284 | if c == '\n' { |
208 | ); | 285 | res.line += 1; |
209 | assert_eq!( | 286 | res.col_utf16 = 0; |
210 | index.line_col(2.into()), | 287 | } else { |
211 | LineCol { | 288 | res.col_utf16 += 1; |
212 | line: 1, | 289 | } |
213 | col_utf16: 1 | ||
214 | } | ||
215 | ); | ||
216 | assert_eq!( | ||
217 | index.line_col(6.into()), | ||
218 | LineCol { | ||
219 | line: 1, | ||
220 | col_utf16: 5 | ||
221 | } | 290 | } |
222 | ); | 291 | res |
223 | assert_eq!( | 292 | } |
224 | index.line_col(7.into()), | 293 | |
225 | LineCol { | 294 | proptest! { |
226 | line: 2, | 295 | #[test] |
227 | col_utf16: 0 | 296 | fn test_line_index_proptest((offset, text) in arb_text_with_offset()) { |
297 | let expected = to_line_col(&text, offset); | ||
298 | let line_index = LineIndex::new(&text); | ||
299 | let actual = line_index.line_col(offset); | ||
300 | |||
301 | assert_eq!(actual, expected); | ||
228 | } | 302 | } |
229 | ); | 303 | } |
230 | } | 304 | } |
231 | 305 | ||
232 | #[cfg(test)] | 306 | #[cfg(test)] |
@@ -321,4 +395,5 @@ const C: char = \"メ メ\"; | |||
321 | 395 | ||
322 | assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextUnit::from_usize(15)); | 396 | assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextUnit::from_usize(15)); |
323 | } | 397 | } |
398 | |||
324 | } | 399 | } |