1
- //! `LineIndex` maps flat `TextSize` offsets into `(Line, Column)`
2
- //! representation.
1
+ //! See [`LineIndex`].
2
+
3
+ #![ deny( clippy:: pedantic, missing_debug_implementations, missing_docs, rust_2018_idioms) ]
4
+
5
+ #[ cfg( test) ]
6
+ mod tests;
7
+
3
8
use std:: { iter, mem} ;
4
9
5
- use stdx :: hash :: NoHashHashMap ;
6
- use syntax :: { TextRange , TextSize } ;
10
+ use non_hash :: NoHashHashMap ;
11
+ use text_size :: { TextRange , TextSize } ;
7
12
13
+ /// Maps flat [`TextSize`] offsets into `(line, column)` representation.
8
14
#[ derive( Clone , Debug , PartialEq , Eq ) ]
9
15
pub struct LineIndex {
10
16
/// Offset the beginning of each line, zero-based.
@@ -16,26 +22,29 @@ pub struct LineIndex {
16
22
/// Line/Column information in native, utf8 format.
17
23
#[ derive( Clone , Copy , Debug , PartialEq , Eq , Hash ) ]
18
24
pub struct LineCol {
19
- /// Zero-based
25
+ /// Zero-based.
20
26
pub line : u32 ,
21
- /// Zero-based utf8 offset
27
+ /// Zero-based UTF-8 offset.
22
28
pub col : u32 ,
23
29
}
24
30
31
+ /// A kind of wide character encoding.
25
32
#[ derive( Clone , Copy , Debug , PartialEq , Eq , Hash ) ]
26
33
pub enum WideEncoding {
34
+ /// UTF-16.
27
35
Utf16 ,
36
+ /// UTF-32.
28
37
Utf32 ,
29
38
}
30
39
31
40
/// Line/Column information in legacy encodings.
32
41
///
33
- /// Deliberately not a generic type and different from `LineCol`.
42
+ /// Deliberately not a generic type and different from [ `LineCol`] .
34
43
#[ derive( Clone , Copy , Debug , PartialEq , Eq , Hash ) ]
35
44
pub struct WideLineCol {
36
- /// Zero-based
45
+ /// Zero-based.
37
46
pub line : u32 ,
38
- /// Zero-based
47
+ /// Zero-based.
39
48
pub col : u32 ,
40
49
}
41
50
@@ -70,6 +79,7 @@ impl WideChar {
70
79
}
71
80
72
81
impl LineIndex {
82
+ /// Returns a `LineIndex` for the `text`.
73
83
pub fn new ( text : & str ) -> LineIndex {
74
84
let mut line_wide_chars = NoHashHashMap :: default ( ) ;
75
85
let mut wide_chars = Vec :: new ( ) ;
@@ -115,29 +125,34 @@ impl LineIndex {
115
125
LineIndex { newlines, line_wide_chars }
116
126
}
117
127
128
+ /// Transforms the `TextSize` into a `LineCol`.
118
129
pub fn line_col ( & self , offset : TextSize ) -> LineCol {
119
130
let line = self . newlines . partition_point ( |& it| it <= offset) - 1 ;
120
131
let line_start_offset = self . newlines [ line] ;
121
132
let col = offset - line_start_offset;
122
133
LineCol { line : line as u32 , col : col. into ( ) }
123
134
}
124
135
136
+ /// Transforms the `LineCol` into a `TextSize`.
125
137
pub fn offset ( & self , line_col : LineCol ) -> Option < TextSize > {
126
138
self . newlines
127
139
. get ( line_col. line as usize )
128
140
. map ( |offset| offset + TextSize :: from ( line_col. col ) )
129
141
}
130
142
143
+ /// Transforms the `LineCol` with the given `WideEncoding` into a `WideLineCol`.
131
144
pub fn to_wide ( & self , enc : WideEncoding , line_col : LineCol ) -> WideLineCol {
132
145
let col = self . utf8_to_wide_col ( enc, line_col. line , line_col. col . into ( ) ) ;
133
146
WideLineCol { line : line_col. line , col : col as u32 }
134
147
}
135
148
149
+ /// Transforms the `WideLineCol` with the given `WideEncoding` into a `LineCol`.
136
150
pub fn to_utf8 ( & self , enc : WideEncoding , line_col : WideLineCol ) -> LineCol {
137
151
let col = self . wide_to_utf8_col ( enc, line_col. line , line_col. col ) ;
138
152
LineCol { line : line_col. line , col : col. into ( ) }
139
153
}
140
154
155
+ /// Returns an iterator over the ranges for the lines.
141
156
pub fn lines ( & self , range : TextRange ) -> impl Iterator < Item = TextRange > + ' _ {
142
157
let lo = self . newlines . partition_point ( |& it| it < range. start ( ) ) ;
143
158
let hi = self . newlines . partition_point ( |& it| it <= range. end ( ) ) ;
@@ -183,135 +198,3 @@ impl LineIndex {
183
198
col. into ( )
184
199
}
185
200
}
186
-
187
- #[ cfg( test) ]
188
- mod tests {
189
- use test_utils:: skip_slow_tests;
190
-
191
- use super :: WideEncoding :: { Utf16 , Utf32 } ;
192
- use super :: * ;
193
-
194
- #[ test]
195
- fn test_line_index ( ) {
196
- let text = "hello\n world" ;
197
- let table = [
198
- ( 00 , 0 , 0 ) ,
199
- ( 01 , 0 , 1 ) ,
200
- ( 05 , 0 , 5 ) ,
201
- ( 06 , 1 , 0 ) ,
202
- ( 07 , 1 , 1 ) ,
203
- ( 08 , 1 , 2 ) ,
204
- ( 10 , 1 , 4 ) ,
205
- ( 11 , 1 , 5 ) ,
206
- ( 12 , 1 , 6 ) ,
207
- ] ;
208
-
209
- let index = LineIndex :: new ( text) ;
210
- for ( offset, line, col) in table {
211
- assert_eq ! ( index. line_col( offset. into( ) ) , LineCol { line, col } ) ;
212
- }
213
-
214
- let text = "\n hello\n world" ;
215
- let table = [ ( 0 , 0 , 0 ) , ( 1 , 1 , 0 ) , ( 2 , 1 , 1 ) , ( 6 , 1 , 5 ) , ( 7 , 2 , 0 ) ] ;
216
- let index = LineIndex :: new ( text) ;
217
- for ( offset, line, col) in table {
218
- assert_eq ! ( index. line_col( offset. into( ) ) , LineCol { line, col } ) ;
219
- }
220
- }
221
-
222
- #[ test]
223
- fn test_char_len ( ) {
224
- assert_eq ! ( 'メ' . len_utf8( ) , 3 ) ;
225
- assert_eq ! ( 'メ' . len_utf16( ) , 1 ) ;
226
- }
227
-
228
- #[ test]
229
- fn test_empty_index ( ) {
230
- let col_index = LineIndex :: new (
231
- "
232
- const C: char = 'x';
233
- " ,
234
- ) ;
235
- assert_eq ! ( col_index. line_wide_chars. len( ) , 0 ) ;
236
- }
237
-
238
- #[ test]
239
- fn test_every_chars ( ) {
240
- if skip_slow_tests ( ) {
241
- return ;
242
- }
243
-
244
- let text: String = {
245
- let mut chars: Vec < char > = ( ( 0 as char ) ..char:: MAX ) . collect ( ) ; // Neat!
246
- chars. extend ( "\n " . repeat ( chars. len ( ) / 16 ) . chars ( ) ) ;
247
- let mut rng = oorandom:: Rand32 :: new ( stdx:: rand:: seed ( ) ) ;
248
- stdx:: rand:: shuffle ( & mut chars, |i| rng. rand_range ( 0 ..i as u32 ) as usize ) ;
249
- chars. into_iter ( ) . collect ( )
250
- } ;
251
- assert ! ( text. contains( '💩' ) ) ; // Sanity check.
252
-
253
- let line_index = LineIndex :: new ( & text) ;
254
-
255
- let mut lin_col = LineCol { line : 0 , col : 0 } ;
256
- let mut col_utf16 = 0 ;
257
- let mut col_utf32 = 0 ;
258
- for ( offset, c) in text. char_indices ( ) {
259
- let got_offset = line_index. offset ( lin_col) . unwrap ( ) ;
260
- assert_eq ! ( usize :: from( got_offset) , offset) ;
261
-
262
- let got_lin_col = line_index. line_col ( got_offset) ;
263
- assert_eq ! ( got_lin_col, lin_col) ;
264
-
265
- for enc in [ Utf16 , Utf32 ] {
266
- let wide_lin_col = line_index. to_wide ( enc, lin_col) ;
267
- let got_lin_col = line_index. to_utf8 ( enc, wide_lin_col) ;
268
- assert_eq ! ( got_lin_col, lin_col) ;
269
-
270
- let want_col = match enc {
271
- Utf16 => col_utf16,
272
- Utf32 => col_utf32,
273
- } ;
274
- assert_eq ! ( wide_lin_col. col, want_col)
275
- }
276
-
277
- if c == '\n' {
278
- lin_col. line += 1 ;
279
- lin_col. col = 0 ;
280
- col_utf16 = 0 ;
281
- col_utf32 = 0 ;
282
- } else {
283
- lin_col. col += c. len_utf8 ( ) as u32 ;
284
- col_utf16 += c. len_utf16 ( ) as u32 ;
285
- col_utf32 += 1 ;
286
- }
287
- }
288
- }
289
-
290
- #[ test]
291
- fn test_splitlines ( ) {
292
- fn r ( lo : u32 , hi : u32 ) -> TextRange {
293
- TextRange :: new ( lo. into ( ) , hi. into ( ) )
294
- }
295
-
296
- let text = "a\n bb\n ccc\n " ;
297
- let line_index = LineIndex :: new ( text) ;
298
-
299
- let actual = line_index. lines ( r ( 0 , 9 ) ) . collect :: < Vec < _ > > ( ) ;
300
- let expected = vec ! [ r( 0 , 2 ) , r( 2 , 5 ) , r( 5 , 9 ) ] ;
301
- assert_eq ! ( actual, expected) ;
302
-
303
- let text = "" ;
304
- let line_index = LineIndex :: new ( text) ;
305
-
306
- let actual = line_index. lines ( r ( 0 , 0 ) ) . collect :: < Vec < _ > > ( ) ;
307
- let expected = vec ! [ ] ;
308
- assert_eq ! ( actual, expected) ;
309
-
310
- let text = "\n " ;
311
- let line_index = LineIndex :: new ( text) ;
312
-
313
- let actual = line_index. lines ( r ( 0 , 1 ) ) . collect :: < Vec < _ > > ( ) ;
314
- let expected = vec ! [ r( 0 , 1 ) ] ;
315
- assert_eq ! ( actual, expected)
316
- }
317
- }
0 commit comments