Properly indent multi-line labels for mutli-span highlights (#86)
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/analysis_options.yaml b/analysis_options.yaml
index 002297f..97c0552 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -39,7 +39,6 @@
- file_names
- hash_and_equals
- implementation_imports
- - invariant_booleans
- iterable_contains_unrelated_type
- library_names
- library_prefixes
diff --git a/lib/src/highlighter.dart b/lib/src/highlighter.dart
index d4e5ebf..53f3d63 100644
--- a/lib/src/highlighter.dart
+++ b/lib/src/highlighter.dart
@@ -367,12 +367,13 @@
_writeMultilineHighlights(line, highlightsByColumn, current: highlight);
if (highlightsByColumn.isNotEmpty) _buffer.write(' ');
- _colorize(() {
+ final underlineLength = _colorize(() {
+ final start = _buffer.length;
_writeUnderline(line, highlight.span,
highlight.isPrimary ? '^' : glyph.horizontalLineBold);
- _writeLabel(highlight.label);
+ return _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 +395,17 @@
_buffer.write(' ');
_writeMultilineHighlights(line, highlightsByColumn, current: highlight);
- _colorize(() {
+ final underlineLength = _colorize(() {
+ final 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);
+ return _buffer.length - start;
}, color: color);
- _buffer.writeln();
+ _writeLabel(highlight, highlightsByColumn, underlineLength);
replaceWithNull(highlightsByColumn, highlight);
}
}
@@ -442,9 +444,45 @@
..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 [highlightsByColumn] are used to write ongoing highlight lines if the
+ /// label is more than one line long.
+ ///
+ /// The [underlineLength] 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)) {
+ _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
@@ -496,10 +534,11 @@
/// Colors all text written to [_buffer] during [callback], if colorization is
/// enabled and [color] is not `null`.
- void _colorize(void Function() callback, {required String? color}) {
+ T _colorize<T>(T Function() callback, {required String? color}) {
if (_primaryColor != null && color != null) _buffer.write(color);
- callback();
+ final result = callback();
if (_primaryColor != null && color != null) _buffer.write(colors.none);
+ return result;
}
}
@@ -522,14 +561,15 @@
/// used in the same message.
final String? label;
- _Highlight(SourceSpan span, {this.label, bool primary = false})
+ _Highlight(SourceSpan span, {String? label, bool primary = false})
: span = (() {
var newSpan = _normalizeContext(span);
newSpan = _normalizeNewlines(newSpan);
newSpan = _normalizeTrailingNewline(newSpan);
return _normalizeEndOfLine(newSpan);
})(),
- isPrimary = primary;
+ isPrimary = primary,
+ label = label?.replaceAll('\r\n', '\n');
/// Normalizes [span] to ensure that it's a [SourceSpanWithContext] whose
/// context actually contains its text at the expected column.
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..ba4d686 100644
--- a/test/multiple_highlight_test.dart
+++ b/test/multiple_highlight_test.dart
@@ -328,4 +328,100 @@
| === 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
+ '"""));
+ });
+ });
}