diff --git a/CHANGELOG.md b/CHANGELOG.md index 5722045..4c49079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -# 1.9.1-dev +# 1.9.1 + +* Properly handle multi-line labels for multi-span highlights. * Populate the pubspec `repository` field. diff --git a/lib/src/highlighter.dart b/lib/src/highlighter.dart index d4e5ebf..6957cd0 100644 --- a/lib/src/highlighter.dart +++ b/lib/src/highlighter.dart @@ -15,6 +15,8 @@ import 'span.dart'; import 'span_with_context.dart'; import 'utils.dart'; +import 'file.dart'; + /// A class for writing a chunk of text with a particular span highlighted. class Highlighter { /// The lines to display, including context around the highlighted spans. @@ -367,12 +369,14 @@ class Highlighter { _writeMultilineHighlights(line, highlightsByColumn, current: highlight); if (highlightsByColumn.isNotEmpty) _buffer.write(' '); + late int underlineLength; _colorize(() { + var start = _buffer.length; _writeUnderline(line, highlight.span, highlight.isPrimary ? '^' : glyph.horizontalLineBold); - _writeLabel(highlight.label); + underlineLength = _buffer.length - start; }, color: color); - _buffer.writeln(); + _writeLabel(highlight, highlightsByColumn, underlineLength); } else if (highlight.span.start.line == line.number) { if (highlightsByColumn.contains(highlight)) return; replaceFirstNull(highlightsByColumn, highlight); @@ -394,16 +398,18 @@ class Highlighter { _buffer.write(' '); _writeMultilineHighlights(line, highlightsByColumn, current: highlight); + late int underlineLength; _colorize(() { + var start = _buffer.length; if (coversWholeLine) { _buffer.write(glyph.horizontalLine * 3); } else { _writeArrow(line, math.max(highlight.span.end.column - 1, 0), beginning: false); } - _writeLabel(highlight.label); + underlineLength = _buffer.length - start; }, color: color); - _buffer.writeln(); + _writeLabel(highlight, highlightsByColumn, underlineLength); replaceWithNull(highlightsByColumn, highlight); } } @@ -442,9 +448,43 @@ class Highlighter { ..write('^'); } - /// Writes a space followed by [label] if [label] isn't `null`. - void _writeLabel(String? label) { - if (label != null) _buffer.write(' $label'); + /// Writes [highlight]'s label. + /// + /// The `_buffer` is assumed to be written to the point where the first line + /// of `highlight.label` can be written after a space, but this takes care of + /// writing indentation and highlight columns for later lines. + /// + /// The [underlines] is the length of the line written between the highlights + /// and the beginning of the first label. + void _writeLabel( + _Highlight highlight, List<_Highlight?> highlightsByColumn, int underlineLength) { + final label = highlight.label; + if (label == null) { + _buffer.writeln(); + return; + } + + final lines = label.split('\n'); + final color = highlight.isPrimary ? _primaryColor : _secondaryColor; + _colorize(() => _buffer.write(' ${lines.first}'), color: color); + _buffer.writeln(); + + for (var text in lines.skip(1)) { + final lineStart = _buffer.length; + _writeSidebar(); + _buffer.write(' '); + for (var columnHighlight in highlightsByColumn) { + if (columnHighlight == null || columnHighlight == highlight) { + _buffer.write(' '); + } else { + _buffer.write(glyph.verticalLine); + } + } + + _buffer.write(' ' * underlineLength); + _colorize(() => _buffer.write(' $text'), color: color); + _buffer.writeln(); + } } /// Writes a snippet from the source text, converting hard tab characters into diff --git a/pubspec.yaml b/pubspec.yaml index 63a64b0..8bde1c2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: source_span -version: 1.9.1-dev +version: 1.9.1 description: A library for identifying source spans and locations. repository: https://github.com/dart-lang/source_span diff --git a/test/multiple_highlight_test.dart b/test/multiple_highlight_test.dart index a9f5fda..480648b 100644 --- a/test/multiple_highlight_test.dart +++ b/test/multiple_highlight_test.dart @@ -328,4 +328,100 @@ quibble bibble boop | === two '""")); }); + + group('indents mutli-line labels', () { + test('for the primary label', () { + expect(file.span(17, 21).highlightMultiple('line 1\nline 2\nline 3', {}), + equals(""" + , +2 | whiz bang boom + | ^^^^ line 1 + | line 2 + | line 3 + '""")); + }); + + group('for a secondary label', () { + test('on the same line', () { + expect( + file.span(17, 21).highlightMultiple( + 'primary', {file.span(22, 26): 'line 1\nline 2\nline 3'}), + equals(""" + , +2 | whiz bang boom + | ^^^^ primary + | ==== line 1 + | line 2 + | line 3 + '""")); + }); + + test('on a different line', () { + expect( + file.span(17, 21).highlightMultiple( + 'primary', {file.span(31, 34): 'line 1\nline 2\nline 3'}), + equals(""" + , +2 | whiz bang boom + | ^^^^ primary +3 | zip zap zop + | === line 1 + | line 2 + | line 3 + '""")); + }); + }); + + group('for a multiline span', () { + test('that covers the whole last line', () { + expect( + file.span(12, 70).highlightMultiple('line 1\nline 2\nline 3', {}), + equals(""" + , +2 | / whiz bang boom +3 | | zip zap zop +4 | | fwee fwoo fwip +5 | | argle bargle boo + | '--- line 1 + | line 2 + | line 3 + '""")); + }); + + test('that covers part of the last line', () { + expect( + file.span(12, 66).highlightMultiple('line 1\nline 2\nline 3', {}), + equals(""" + , +2 | / whiz bang boom +3 | | zip zap zop +4 | | fwee fwoo fwip +5 | | argle bargle boo + | '------------^ line 1 + | line 2 + | line 3 + '""")); + }); + }); + + test('with an overlapping span', () { + expect( + file.span(12, 70).highlightMultiple('line 1\nline 2\nline 3', + {file.span(54, 89): 'two', file.span(0, 27): 'three'}), + equals(""" + , +1 | /- foo bar baz +2 | |/ whiz bang boom + | '+--- three +3 | | zip zap zop +4 | | fwee fwoo fwip +5 | /+ argle bargle boo + | |'--- line 1 + | | line 2 + | | line 3 +6 | | gibble bibble bop + | '---- two + '""")); + }); + }); }