@@ -15,15 +15,18 @@ use core::iter;
15
15
use core:: ops:: Range ;
16
16
use core:: slice;
17
17
18
+ #[ cfg( feature = "emit" ) ]
19
+ use std:: io;
20
+
18
21
use crate as rune;
19
22
#[ cfg( feature = "std" ) ]
20
23
use crate :: alloc:: borrow:: Cow ;
21
24
use crate :: alloc:: path:: Path ;
22
25
use crate :: alloc:: prelude:: * ;
23
26
use crate :: alloc:: { self , Box } ;
24
-
25
- #[ cfg( feature = "emit" ) ]
26
27
use crate :: ast:: Span ;
28
+ #[ cfg( feature = "emit" ) ]
29
+ use crate :: termcolor:: { self , WriteColor } ;
27
30
28
31
/// Error raised when constructing a source.
29
32
#[ derive( Debug ) ]
@@ -176,7 +179,6 @@ impl Source {
176
179
}
177
180
178
181
/// Access all line starts in the source.
179
- #[ cfg( feature = "emit" ) ]
180
182
pub ( crate ) fn line_starts ( & self ) -> & [ usize ] {
181
183
& self . line_starts
182
184
}
@@ -219,10 +221,27 @@ impl Source {
219
221
self . path . as_deref ( )
220
222
}
221
223
222
- /// Convert the given offset to a utf-16 line and character.
223
- #[ cfg( feature = "languageserver" ) ]
224
- pub ( crate ) fn pos_to_utf16cu_linecol ( & self , offset : usize ) -> ( usize , usize ) {
225
- let ( line, offset, rest) = self . position ( offset) ;
224
+ /// Convert the given position to a utf-8 line position in code units.
225
+ ///
226
+ /// A position is a character offset into the source in utf-8 characters.
227
+ ///
228
+ /// Note that utf-8 code units is what you'd count when using the
229
+ /// [`str::chars()`] iterator.
230
+ pub fn find_line_column ( & self , position : usize ) -> ( usize , usize ) {
231
+ let ( line, offset, rest) = self . position ( position) ;
232
+ let col = rest. char_indices ( ) . take_while ( |& ( n, _) | n < offset) . count ( ) ;
233
+ ( line, col)
234
+ }
235
+
236
+ /// Convert the given position to a utf-16 code units line and character.
237
+ ///
238
+ /// A position is a character offset into the source in utf-16 characters.
239
+ ///
240
+ /// Note that utf-16 code units is what you'd count when iterating over the
241
+ /// string in terms of characters as-if they would have been encoded with
242
+ /// [`char::encode_utf16()`].
243
+ pub fn find_utf16cu_line_column ( & self , position : usize ) -> ( usize , usize ) {
244
+ let ( line, offset, rest) = self . position ( position) ;
226
245
227
246
let col = rest
228
247
. char_indices ( )
@@ -232,11 +251,19 @@ impl Source {
232
251
( line, col)
233
252
}
234
253
235
- /// Convert the given offset to a utf-16 line and character.
236
- pub fn pos_to_utf8_linecol ( & self , offset : usize ) -> ( usize , usize ) {
237
- let ( line, offset, rest) = self . position ( offset) ;
238
- let col = rest. char_indices ( ) . take_while ( |& ( n, _) | n < offset) . count ( ) ;
239
- ( line, col)
254
+ /// Fetch [`SourceLine`] information for the given span.
255
+ pub fn source_line ( & self , span : Span ) -> Option < SourceLine < ' _ > > {
256
+ let ( line, column, text, _span) = line_for ( self , span) ?;
257
+
258
+ Some ( SourceLine {
259
+ #[ cfg( feature = "emit" ) ]
260
+ name : self . name ( ) ,
261
+ line,
262
+ column,
263
+ text,
264
+ #[ cfg( feature = "emit" ) ]
265
+ span : _span,
266
+ } )
240
267
}
241
268
242
269
/// Get the line index for the given byte.
@@ -265,7 +292,7 @@ impl Source {
265
292
#[ cfg( feature = "emit" ) ]
266
293
pub ( crate ) fn line ( & self , span : Span ) -> Option < ( usize , usize , [ & str ; 3 ] ) > {
267
294
let from = span. range ( ) ;
268
- let ( lin, col) = self . pos_to_utf8_linecol ( from. start ) ;
295
+ let ( lin, col) = self . find_line_column ( from. start ) ;
269
296
let line = self . line_range ( lin) ?;
270
297
271
298
let start = from. start . checked_sub ( line. start ) ?;
@@ -320,6 +347,61 @@ impl fmt::Debug for Source {
320
347
}
321
348
}
322
349
350
+ /// An extracted source line.
351
+ pub struct SourceLine < ' a > {
352
+ #[ cfg( feature = "emit" ) ]
353
+ name : & ' a str ,
354
+ /// The line number in the source.
355
+ pub line : usize ,
356
+ /// The column number in the source.
357
+ pub column : usize ,
358
+ /// The text of the span.
359
+ pub text : & ' a str ,
360
+ #[ cfg( feature = "emit" ) ]
361
+ span : Span ,
362
+ }
363
+
364
+ impl SourceLine < ' _ > {
365
+ /// Pretty write a source line to the given output.
366
+ #[ cfg( feature = "emit" ) ]
367
+ pub ( crate ) fn write ( & self , o : & mut dyn WriteColor ) -> io:: Result < ( ) > {
368
+ let mut highlight = termcolor:: ColorSpec :: new ( ) ;
369
+ highlight. set_fg ( Some ( termcolor:: Color :: Yellow ) ) ;
370
+
371
+ let mut new_line = termcolor:: ColorSpec :: new ( ) ;
372
+ new_line. set_fg ( Some ( termcolor:: Color :: Red ) ) ;
373
+
374
+ let text = self . text . trim_end ( ) ;
375
+ let end = self . span . end . into_usize ( ) . min ( text. len ( ) ) ;
376
+
377
+ let before = & text[ 0 ..self . span . start . into_usize ( ) ] . trim_start ( ) ;
378
+ let inner = & text[ self . span . start . into_usize ( ) ..end] ;
379
+ let after = & text[ end..] ;
380
+
381
+ {
382
+ let name = self . name ;
383
+ let line = self . line + 1 ;
384
+ let start = self . column + 1 ;
385
+ let end = start + inner. chars ( ) . count ( ) ;
386
+ write ! ( o, "{name}:{line}:{start}-{end}: " ) ?;
387
+ }
388
+
389
+ write ! ( o, "{before}" ) ?;
390
+ o. set_color ( & highlight) ?;
391
+ write ! ( o, "{inner}" ) ?;
392
+ o. reset ( ) ?;
393
+ write ! ( o, "{after}" ) ?;
394
+
395
+ if self . span . end != end {
396
+ o. set_color ( & new_line) ?;
397
+ write ! ( o, "\\ n" ) ?;
398
+ o. reset ( ) ?;
399
+ }
400
+
401
+ Ok ( ( ) )
402
+ }
403
+ }
404
+
323
405
/// Holder for the name of a source.
324
406
#[ derive( Default , Debug , TryClone , PartialEq , Eq ) ]
325
407
enum SourceName {
@@ -331,6 +413,46 @@ enum SourceName {
331
413
Name ( Box < str > ) ,
332
414
}
333
415
416
+ #[ inline( always) ]
334
417
fn line_starts ( source : & str ) -> impl Iterator < Item = usize > + ' _ {
335
418
iter:: once ( 0 ) . chain ( source. match_indices ( '\n' ) . map ( |( i, _) | i + 1 ) )
336
419
}
420
+
421
+ /// Get the line number and source line for the given source and span.
422
+ fn line_for ( source : & Source , span : Span ) -> Option < ( usize , usize , & str , Span ) > {
423
+ let line_starts = source. line_starts ( ) ;
424
+
425
+ let line = match line_starts. binary_search ( & span. start . into_usize ( ) ) {
426
+ Ok ( n) => n,
427
+ Err ( n) => n. saturating_sub ( 1 ) ,
428
+ } ;
429
+
430
+ let start = * line_starts. get ( line) ?;
431
+ let end = line. checked_add ( 1 ) ?;
432
+
433
+ let s = if let Some ( end) = line_starts. get ( end) {
434
+ source. get ( start..* end) ?
435
+ } else {
436
+ source. get ( start..) ?
437
+ } ;
438
+
439
+ let line_end = span. start . into_usize ( ) . saturating_sub ( start) ;
440
+
441
+ let column = s
442
+ . get ( ..line_end)
443
+ . into_iter ( )
444
+ . flat_map ( |s| s. chars ( ) )
445
+ . count ( ) ;
446
+
447
+ let start = start. try_into ( ) . unwrap ( ) ;
448
+
449
+ Some ( (
450
+ line,
451
+ column,
452
+ s,
453
+ Span :: new (
454
+ span. start . saturating_sub ( start) ,
455
+ span. end . saturating_sub ( start) ,
456
+ ) ,
457
+ ) )
458
+ }
0 commit comments