Skip to content

Commit

Permalink
Add SourceSpanWithContextExtension.subspan (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 authored Apr 5, 2022
1 parent dc189b4 commit 8ae724c
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 45 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
47 changes: 3 additions & 44 deletions lib/src/span.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -187,48 +187,7 @@ extension SourceSpanExtension on SourceSpan {
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));
}
}
15 changes: 15 additions & 0 deletions lib/src/span_with_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,18 @@ class SourceSpanWithContext extends SourceSpanBase {
}
}
}

// 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);
}
}
54 changes: 54 additions & 0 deletions lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -89,3 +92,54 @@ int? findLineStart(String context, String text, int column) {
// 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];
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
7 changes: 7 additions & 0 deletions test/span_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ void main() {
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)));

Expand Down

0 comments on commit 8ae724c

Please sign in to comment.