Add SourceSpanWithContextExtension.subspan (#81)
diff --git a/CHANGELOG.md b/CHANGELOG.md index c0096b7..f906a87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -1,3 +1,8 @@ +# 1.9.0 + +* Add `SourceSpanWithContextExtension.subspan` that returns a + `SourceSpanWithContext` rather than a plain `SourceSpan`. + # 1.8.2 * Fix a bug where highlighting multiple spans with `null` URLs could cause an
diff --git a/lib/src/span.dart b/lib/src/span.dart index 30590ea..05fd340 100644 --- a/lib/src/span.dart +++ b/lib/src/span.dart
@@ -5,12 +5,12 @@ import 'package:path/path.dart' as p; import 'package:term_glyph/term_glyph.dart' as glyph; -import 'charcode.dart'; import 'file.dart'; import 'highlighter.dart'; import 'location.dart'; import 'span_mixin.dart'; import 'span_with_context.dart'; +import 'utils.dart'; /// A class that describes a segment of source text. abstract class SourceSpan implements Comparable<SourceSpan> { @@ -187,48 +187,7 @@ RangeError.checkValidRange(start, end, length); if (start == 0 && (end == null || end == length)) return this; - final text = this.text; - final startLocation = this.start; - var line = startLocation.line; - var column = startLocation.column; - - // Adjust [line] and [column] as necessary if the character at [i] in [text] - // is a newline. - void consumeCodePoint(int i) { - final codeUnit = text.codeUnitAt(i); - if (codeUnit == $lf || - // A carriage return counts as a newline, but only if it's not - // followed by a line feed. - (codeUnit == $cr && - (i + 1 == text.length || text.codeUnitAt(i + 1) != $lf))) { - line += 1; - column = 0; - } else { - column += 1; - } - } - - for (var i = 0; i < start; i++) { - consumeCodePoint(i); - } - - final newStartLocation = SourceLocation(startLocation.offset + start, - sourceUrl: sourceUrl, line: line, column: column); - - SourceLocation newEndLocation; - if (end == null || end == length) { - newEndLocation = this.end; - } else if (end == start) { - newEndLocation = newStartLocation; - } else { - for (var i = start; i < end; i++) { - consumeCodePoint(i); - } - newEndLocation = SourceLocation(startLocation.offset + end, - sourceUrl: sourceUrl, line: line, column: column); - } - - return SourceSpan( - newStartLocation, newEndLocation, text.substring(start, end)); + final locations = subspanLocations(this, start, end); + return SourceSpan(locations[0], locations[1], text.substring(start, end)); } }
diff --git a/lib/src/span_with_context.dart b/lib/src/span_with_context.dart index da09cc0..776c789 100644 --- a/lib/src/span_with_context.dart +++ b/lib/src/span_with_context.dart
@@ -34,3 +34,18 @@ } } } + +// TODO(#52): Move these to instance methods in the next breaking release. +/// Extension methods on the base [SourceSpan] API. +extension SourceSpanWithContextExtension on SourceSpanWithContext { + /// Returns a span from [start] code units (inclusive) to [end] code units + /// (exclusive) after the beginning of this span. + SourceSpanWithContext subspan(int start, [int? end]) { + RangeError.checkValidRange(start, end, length); + if (start == 0 && (end == null || end == length)) return this; + + final locations = subspanLocations(this, start, end); + return SourceSpanWithContext( + locations[0], locations[1], text.substring(start, end), context); + } +}
diff --git a/lib/src/utils.dart b/lib/src/utils.dart index ccc88bd..7df0baf 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart
@@ -2,7 +2,10 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'charcode.dart'; +import 'location.dart'; import 'span.dart'; +import 'span_with_context.dart'; /// Returns the minimum of [obj1] and [obj2] according to /// [Comparable.compareTo]. @@ -89,3 +92,54 @@ // ignore: avoid_returning_null return null; } + +/// Returns a two-element list containing the start and end locations of the +/// span from [start] code units (inclusive) to [end] code units (exclusive) +/// after the beginning of [span]. +/// +/// This is factored out so it can be shared between +/// [SourceSpanExtension.subspan] and [SourceSpanWithContextExtension.subspan]. +List<SourceLocation> subspanLocations(SourceSpan span, int start, [int? end]) { + final text = span.text; + final startLocation = span.start; + var line = startLocation.line; + var column = startLocation.column; + + // Adjust [line] and [column] as necessary if the character at [i] in [text] + // is a newline. + void consumeCodePoint(int i) { + final codeUnit = text.codeUnitAt(i); + if (codeUnit == $lf || + // A carriage return counts as a newline, but only if it's not + // followed by a line feed. + (codeUnit == $cr && + (i + 1 == text.length || text.codeUnitAt(i + 1) != $lf))) { + line += 1; + column = 0; + } else { + column += 1; + } + } + + for (var i = 0; i < start; i++) { + consumeCodePoint(i); + } + + final newStartLocation = SourceLocation(startLocation.offset + start, + sourceUrl: span.sourceUrl, line: line, column: column); + + SourceLocation newEndLocation; + if (end == null || end == span.length) { + newEndLocation = span.end; + } else if (end == start) { + newEndLocation = newStartLocation; + } else { + for (var i = start; i < end; i++) { + consumeCodePoint(i); + } + newEndLocation = SourceLocation(startLocation.offset + end, + sourceUrl: span.sourceUrl, line: line, column: column); + } + + return [newStartLocation, newEndLocation]; +}
diff --git a/pubspec.yaml b/pubspec.yaml index ad81db6..84db52e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml
@@ -1,5 +1,5 @@ name: source_span -version: 1.8.2 +version: 1.9.0 description: A library for identifying source spans and locations. homepage: https://github.com/dart-lang/source_span
diff --git a/test/span_test.dart b/test/span_test.dart index 348cc9e..22c498e 100644 --- a/test/span_test.dart +++ b/test/span_test.dart
@@ -206,6 +206,13 @@ expect(result.end.sourceUrl, equals(span.sourceUrl)); }); + test('preserves the context', () { + final start = SourceLocation(2); + final end = SourceLocation(5); + final span = SourceSpanWithContext(start, end, 'abc', '--abc--'); + expect(span.subspan(1, 2).context, equals('--abc--')); + }); + group('returns the original span', () { test('with an implicit end', () => expect(span.subspan(0), equals(span)));