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)));