Merge pull request #27 from dart-lang/highlight-crlf
Produce better highlights for Windows newlines
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 74ab342..489f01a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,9 @@
* Produce better source span highlights for multi-line spans that cover the
entire last line of the span, including the newline.
+* Produce better source span highlights for spans that contain Windows-style
+ newlines.
+
# 1.5.0
* Improve the output of `SourceSpan.highlight()` and `SourceSpan.message()`:
diff --git a/lib/src/highlighter.dart b/lib/src/highlighter.dart
index 97d696d..3ed1749 100644
--- a/lib/src/highlighter.dart
+++ b/lib/src/highlighter.dart
@@ -58,66 +58,99 @@
if (color == true) color = colors.RED;
if (color == false) color = null;
- // Normalize [span] to ensure that it's a [SourceSpanWithContext] whose
- // context actually contains its text at the expected column. If it's not,
- // adjust the start and end locations' line and column fields so that the
- // highlighter can assume they match up with the context.
- SourceSpanWithContext newSpan;
- if (span is SourceSpanWithContext &&
- findLineStart(span.context, span.text, span.start.column) != null) {
- newSpan = span;
- } else {
- newSpan = new SourceSpanWithContext(
- new SourceLocation(span.start.offset,
- sourceUrl: span.sourceUrl, line: 0, column: 0),
- new SourceLocation(span.end.offset,
- sourceUrl: span.sourceUrl,
- line: countCodeUnits(span.text, $lf),
- column: _lastColumn(span.text)),
- span.text,
- span.text);
- }
-
- // Normalize [span] to remove a trailing newline from `span.context`. If
- // necessary, also adjust `span.end` so that it doesn't point past where the
- // trailing newline used to be.
- if (newSpan.context.endsWith("\n")) {
- var context = newSpan.context.substring(0, newSpan.context.length - 1);
-
- var text = newSpan.text;
- var start = newSpan.start;
- var end = newSpan.end;
- if (newSpan.text.endsWith("\n") && _isTextAtEndOfContext(newSpan)) {
- text = newSpan.text.substring(0, newSpan.text.length - 1);
- end = new SourceLocation(newSpan.end.offset - 1,
- sourceUrl: newSpan.sourceUrl,
- line: newSpan.end.line - 1,
- column: _lastColumn(text));
- start =
- newSpan.start.offset == newSpan.end.offset ? end : newSpan.start;
- }
- newSpan = new SourceSpanWithContext(start, end, text, context);
- }
-
- // Normalize [span] so that the end location is at the end of a line, rather
- // than on the beginning of the next line.
- if (newSpan.end.column == 0 && newSpan.end.line != newSpan.start.line) {
- assert(newSpan.text.endsWith("\n"));
-
- var text = newSpan.text.substring(0, newSpan.text.length - 1);
- newSpan = new SourceSpanWithContext(
- newSpan.start,
- new SourceLocation(span.end.offset - 1,
- sourceUrl: span.sourceUrl,
- line: span.end.line - 1,
- column: _lastColumn(text)),
- text,
- newSpan.context);
- }
+ var newSpan = _normalizeContext(span);
+ newSpan = _normalizeNewlines(newSpan);
+ newSpan = _normalizeTrailingNewline(newSpan);
+ newSpan = _normalizeEndOfLine(newSpan);
return new Highlighter._(newSpan, color);
}
+ /// Normalizes [span] to ensure that it's a [SourceSpanWithContext] whose
+ /// context actually contains its text at the expected column.
+ ///
+ /// If it's not already a [SourceSpanWithContext], adjust the start and end
+ /// locations' line and column fields so that the highlighter can assume they
+ /// match up with the context.
+ static SourceSpanWithContext _normalizeContext(SourceSpan span) =>
+ span is SourceSpanWithContext &&
+ findLineStart(span.context, span.text, span.start.column) != null
+ ? span
+ : new SourceSpanWithContext(
+ new SourceLocation(span.start.offset,
+ sourceUrl: span.sourceUrl, line: 0, column: 0),
+ new SourceLocation(span.end.offset,
+ sourceUrl: span.sourceUrl,
+ line: countCodeUnits(span.text, $lf),
+ column: _lastColumn(span.text)),
+ span.text,
+ span.text);
+
+ /// Normalizes [span] to replace Windows-style newlines with Unix-style
+ /// newlines.
+ static SourceSpanWithContext _normalizeNewlines(SourceSpanWithContext span) {
+ var text = span.text;
+ if (!text.contains("\r\n")) return span;
+
+ var endOffset = span.end.offset;
+ for (var i = 0; i < text.length - 1; i++) {
+ if (text.codeUnitAt(i) == $cr && text.codeUnitAt(i + 1) == $lf) {
+ endOffset--;
+ }
+ }
+
+ return new SourceSpanWithContext(
+ span.start,
+ new SourceLocation(endOffset,
+ sourceUrl: span.sourceUrl,
+ line: span.end.line,
+ column: span.end.column),
+ text.replaceAll("\r\n", "\n"),
+ span.context.replaceAll("\r\n", "\n"));
+ }
+
+ /// Normalizes [span] to remove a trailing newline from `span.context`.
+ ///
+ /// If necessary, also adjust `span.end` so that it doesn't point past where
+ /// the trailing newline used to be.
+ static SourceSpanWithContext _normalizeTrailingNewline(
+ SourceSpanWithContext span) {
+ if (!span.context.endsWith("\n")) return span;
+
+ var context = span.context.substring(0, span.context.length - 1);
+ var text = span.text;
+ var start = span.start;
+ var end = span.end;
+ if (span.text.endsWith("\n") && _isTextAtEndOfContext(span)) {
+ text = span.text.substring(0, span.text.length - 1);
+ end = new SourceLocation(span.end.offset - 1,
+ sourceUrl: span.sourceUrl,
+ line: span.end.line - 1,
+ column: _lastColumn(text));
+ start = span.start.offset == span.end.offset ? end : span.start;
+ }
+ return new SourceSpanWithContext(start, end, text, context);
+ }
+
+ /// Normalizes [span] so that the end location is at the end of a line, rather
+ /// than on the beginning of the next line.
+ static SourceSpanWithContext _normalizeEndOfLine(SourceSpanWithContext span) {
+ if (span.end.column != 0) return span;
+ if (span.end.line == span.start.line) return span;
+
+ assert(span.text.endsWith("\n"));
+
+ var text = span.text.substring(0, span.text.length - 1);
+ return new SourceSpanWithContext(
+ span.start,
+ new SourceLocation(span.end.offset - 1,
+ sourceUrl: span.sourceUrl,
+ line: span.end.line - 1,
+ column: _lastColumn(text)),
+ text,
+ span.context);
+ }
+
/// Returns the (0-based) column number of the last column of the last line in [text].
static int _lastColumn(String text) =>
text.length - text.lastIndexOf("\n") + 1;
@@ -147,7 +180,7 @@
// on, write those first.
var lineStart =
findLineStart(_span.context, _span.text, _span.start.column);
- assert(lineStart != null); // enforced by [new Highlighter]
+ assert(lineStart != null); // enforced by [_normalizeContext]
var context = _span.context;
if (lineStart > 0) {
diff --git a/pubspec.yaml b/pubspec.yaml
index 13fa648..fc5ba2b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: source_span
-version: 1.5.1-dev
+version: 1.5.1
description: A library for identifying source spans and locations.
author: Dart Team <misc@dartlang.org>
diff --git a/test/highlight_test.dart b/test/highlight_test.dart
index e2536ba..b9e7ded 100644
--- a/test/highlight_test.dart
+++ b/test/highlight_test.dart
@@ -175,6 +175,21 @@
'"""));
});
+ test("highlights the full last line with a trailing Windows newline", () {
+ var file = new SourceFile.fromString("""
+foo bar baz\r
+whiz bang boom\r
+zip zap zop\r
+""");
+
+ expect(file.span(4, 29).highlight(), equals("""
+ ,
+1 | foo bar baz
+ | ,-----^
+2 | \\ whiz bang boom
+ '"""));
+ });
+
test("highlights the full last line at the end of the file", () {
expect(file.span(4, 39).highlight(), equals("""
,
@@ -186,8 +201,8 @@
});
test(
- "highlights the full last line at the end of the file with no trailing"
- " newline", () {
+ "highlights the full last line at the end of the file with no trailing "
+ "newline", () {
var file = new SourceFile.fromString("""
foo bar baz
whiz bang boom