@@ -515,7 +515,19 @@ impl GraphicalReportHandler {
515
515
context : & LabeledSpan ,
516
516
labels : & [ LabeledSpan ] ,
517
517
) -> fmt:: Result {
518
- let ( contents, lines) = self . get_lines ( source, context. inner ( ) ) ?;
518
+ let ( contents, mut lines) = self . get_lines ( source, context. inner ( ) ) ?;
519
+
520
+ // If the number of lines doesn't match the content's line_count, it's
521
+ // because the content is either an empty source or because the last
522
+ // line finishes with a newline. In this case, add an empty line.
523
+ if lines. len ( ) != contents. line_count ( ) {
524
+ lines. push ( Line {
525
+ line_number : contents. line ( ) + contents. line_count ( ) ,
526
+ offset : contents. span ( ) . offset ( ) + contents. span ( ) . len ( ) ,
527
+ length : 0 ,
528
+ text : String :: new ( ) ,
529
+ } ) ;
530
+ }
519
531
520
532
// only consider labels from the context as primary label
521
533
let ctx_labels = labels. iter ( ) . filter ( |l| {
@@ -570,6 +582,10 @@ impl GraphicalReportHandler {
570
582
self . theme. characters. hbar,
571
583
) ?;
572
584
585
+ // Save that for later, since `content` might be moved before we need
586
+ // that info
587
+ let has_content = !contents. data ( ) . is_empty ( ) ;
588
+
573
589
// If there is a primary label, then use its span
574
590
// as the reference point for line/column information.
575
591
let primary_contents = match primary_label {
@@ -600,48 +616,53 @@ impl GraphicalReportHandler {
600
616
}
601
617
602
618
// Now it's time for the fun part--actually rendering everything!
603
- for line in & lines {
604
- // Line number, appropriately padded.
605
- self . write_linum ( f, linum_width, line. line_number ) ?;
606
-
607
- // Then, we need to print the gutter, along with any fly-bys We
608
- // have separate gutters depending on whether we're on the actual
609
- // line, or on one of the "highlight lines" below it.
610
- self . render_line_gutter ( f, max_gutter, line, & labels) ?;
611
-
612
- // And _now_ we can print out the line text itself!
613
- let styled_text =
614
- StyledList :: from ( highlighter_state. highlight_line ( & line. text ) ) . to_string ( ) ;
615
- self . render_line_text ( f, & styled_text) ?;
616
-
617
- // Next, we write all the highlights that apply to this particular line.
618
- let ( single_line, multi_line) : ( Vec < _ > , Vec < _ > ) = labels
619
- . iter ( )
620
- . filter ( |hl| line. span_applies ( hl) )
621
- . partition ( |hl| line. span_line_only ( hl) ) ;
622
- if !single_line. is_empty ( ) {
623
- // no line number!
624
- self . write_no_linum ( f, linum_width) ?;
625
- // gutter _again_
626
- self . render_highlight_gutter (
627
- f,
628
- max_gutter,
629
- line,
630
- & labels,
631
- LabelRenderMode :: SingleLine ,
632
- ) ?;
633
- self . render_single_line_highlights (
634
- f,
635
- line,
636
- linum_width,
637
- max_gutter,
638
- & single_line,
639
- & labels,
640
- ) ?;
641
- }
642
- for hl in multi_line {
643
- if hl. label ( ) . is_some ( ) && line. span_ends ( hl) && !line. span_starts ( hl) {
644
- self . render_multi_line_end ( f, & labels, max_gutter, linum_width, line, hl) ?;
619
+ // (but only if we have content or if we wanted content, to avoid
620
+ // pointless detailed rendering, e.g. arrows pointing to nothing in the
621
+ // middle of nothing)
622
+ if has_content || self . context_lines . is_some ( ) {
623
+ for ( line_no, line) in lines. iter ( ) . enumerate ( ) {
624
+ // Line number, appropriately padded.
625
+ self . write_linum ( f, linum_width, line. line_number ) ?;
626
+
627
+ // Then, we need to print the gutter, along with any fly-bys We
628
+ // have separate gutters depending on whether we're on the actual
629
+ // line, or on one of the "highlight lines" below it.
630
+ self . render_line_gutter ( f, max_gutter, line, & labels) ?;
631
+
632
+ // And _now_ we can print out the line text itself!
633
+ let styled_text =
634
+ StyledList :: from ( highlighter_state. highlight_line ( & line. text ) ) . to_string ( ) ;
635
+ self . render_line_text ( f, & styled_text) ?;
636
+
637
+ // Next, we write all the highlights that apply to this particular line.
638
+ let ( single_line, multi_line) : ( Vec < _ > , Vec < _ > ) = labels
639
+ . iter ( )
640
+ . filter ( |hl| line. span_applies ( hl, line_no == ( lines. len ( ) - 1 ) ) )
641
+ . partition ( |hl| line. span_line_only ( hl) ) ;
642
+ if !single_line. is_empty ( ) {
643
+ // no line number!
644
+ self . write_no_linum ( f, linum_width) ?;
645
+ // gutter _again_
646
+ self . render_highlight_gutter (
647
+ f,
648
+ max_gutter,
649
+ line,
650
+ & labels,
651
+ LabelRenderMode :: SingleLine ,
652
+ ) ?;
653
+ self . render_single_line_highlights (
654
+ f,
655
+ line,
656
+ linum_width,
657
+ max_gutter,
658
+ & single_line,
659
+ & labels,
660
+ ) ?;
661
+ }
662
+ for hl in multi_line {
663
+ if hl. label ( ) . is_some ( ) && line. span_ends ( hl) && !line. span_starts ( hl) {
664
+ self . render_multi_line_end ( f, & labels, max_gutter, linum_width, line, hl) ?;
665
+ }
645
666
}
646
667
}
647
668
}
@@ -1271,26 +1292,38 @@ impl Line {
1271
1292
1272
1293
/// Returns whether `span` should be visible on this line, either in the gutter or under the
1273
1294
/// text on this line
1274
- fn span_applies ( & self , span : & FancySpan ) -> bool {
1295
+ ///
1296
+ /// An empty span at a line boundary will preferable apply to the start of
1297
+ /// a line (i.e. the second/next line) rather than the end of one (i.e. the
1298
+ /// first/previous line). However if there are no "second" line, the span
1299
+ /// can only apply the "first". The `inclusive` parameter is there to
1300
+ /// indicate that `self` is the last line, i.e. that there are no "second"
1301
+ /// line.
1302
+ fn span_applies ( & self , span : & FancySpan , inclusive : bool ) -> bool {
1275
1303
// A span applies if its start is strictly before the line's end,
1276
1304
// i.e. the span is not after the line, and its end is strictly after
1277
1305
// the line's start, i.e. the span is not before the line.
1278
1306
//
1279
- // One corner case: if the span length is 0, then the span also applies
1280
- // if its end is *at* the line's start, not just strictly after.
1281
- ( span. offset ( ) < self . offset + self . length )
1282
- && match span. len ( ) == 0 {
1283
- true => ( span. offset ( ) + span. len ( ) ) >= self . offset ,
1284
- false => ( span. offset ( ) + span. len ( ) ) > self . offset ,
1285
- }
1307
+ // Two corner cases:
1308
+ // - if `inclusive` is true, then the span also applies if its start is
1309
+ // *at* the line's end, not just strictly before.
1310
+ // - if the span length is 0, then the span also applies if its end is
1311
+ // *at* the line's start, not just strictly after.
1312
+ ( match inclusive {
1313
+ true => span. offset ( ) <= self . offset + self . length ,
1314
+ false => span. offset ( ) < self . offset + self . length ,
1315
+ } ) && match span. len ( ) == 0 {
1316
+ true => ( span. offset ( ) + span. len ( ) ) >= self . offset ,
1317
+ false => ( span. offset ( ) + span. len ( ) ) > self . offset ,
1318
+ }
1286
1319
}
1287
1320
1288
1321
/// Returns whether `span` should be visible on this line in the gutter (so this excludes spans
1289
1322
/// that are only visible on this line and do not span multiple lines)
1290
1323
fn span_applies_gutter ( & self , span : & FancySpan ) -> bool {
1291
1324
// The span must covers this line and at least one of its ends must be
1292
1325
// on another line
1293
- self . span_applies ( span)
1326
+ self . span_applies ( span, false )
1294
1327
&& ( ( span. offset ( ) < self . offset )
1295
1328
|| ( ( span. offset ( ) + span. len ( ) ) >= ( self . offset + self . length ) ) )
1296
1329
}
0 commit comments