OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library source_span.file; | 5 library source_span.file; |
6 | 6 |
7 import 'dart:math' as math; | 7 import 'dart:math' as math; |
8 import 'dart:typed_data'; | 8 import 'dart:typed_data'; |
9 | 9 |
10 import 'location.dart'; | 10 import 'location.dart'; |
11 import 'span.dart'; | 11 import 'span.dart'; |
12 import 'span_mixin.dart'; | 12 import 'span_mixin.dart'; |
13 import 'span_with_context.dart'; | 13 import 'span_with_context.dart'; |
14 import 'utils.dart'; | |
15 | 14 |
16 // Constants to determine end-of-lines. | 15 // Constants to determine end-of-lines. |
17 const int _LF = 10; | 16 const int _LF = 10; |
18 const int _CR = 13; | 17 const int _CR = 13; |
19 | 18 |
20 /// A class representing a source file. | 19 /// A class representing a source file. |
21 /// | 20 /// |
22 /// This doesn't necessarily have to correspond to a file on disk, just a chunk | 21 /// This doesn't necessarily have to correspond to a file on disk, just a chunk |
23 /// of text usually with a URL associated with it. | 22 /// of text usually with a URL associated with it. |
24 class SourceFile { | 23 class SourceFile { |
(...skipping 11 matching lines...) Expand all Loading... |
36 | 35 |
37 /// The code points of the characters in the file. | 36 /// The code points of the characters in the file. |
38 final Uint32List _decodedChars; | 37 final Uint32List _decodedChars; |
39 | 38 |
40 /// The length of the file in characters. | 39 /// The length of the file in characters. |
41 int get length => _decodedChars.length; | 40 int get length => _decodedChars.length; |
42 | 41 |
43 /// The number of lines in the file. | 42 /// The number of lines in the file. |
44 int get lines => _lineStarts.length; | 43 int get lines => _lineStarts.length; |
45 | 44 |
| 45 /// The line that the offset fell on the last time [getLine] was called. |
| 46 /// |
| 47 /// In many cases, sequential calls to getLine() are for nearby, usually |
| 48 /// increasing offsets. In that case, we can find the line for an offset |
| 49 /// quickly by first checking to see if the offset is on the same line as the |
| 50 /// previous result. |
| 51 int _cachedLine; |
| 52 |
46 /// Creates a new source file from [text]. | 53 /// Creates a new source file from [text]. |
47 /// | 54 /// |
48 /// [url] may be either a [String], a [Uri], or `null`. | 55 /// [url] may be either a [String], a [Uri], or `null`. |
49 SourceFile(String text, {url}) | 56 SourceFile(String text, {url}) |
50 : this.decoded(text.runes, url: url); | 57 : this.decoded(text.runes, url: url); |
51 | 58 |
52 /// Creates a new source file from a list of decoded characters. | 59 /// Creates a new source file from a list of decoded characters. |
53 /// | 60 /// |
54 /// [url] may be either a [String], a [Uri], or `null`. | 61 /// [url] may be either a [String], a [Uri], or `null`. |
55 SourceFile.decoded(Iterable<int> decodedChars, {url}) | 62 SourceFile.decoded(Iterable<int> decodedChars, {url}) |
(...skipping 22 matching lines...) Expand all Loading... |
78 FileLocation location(int offset) => new FileLocation._(this, offset); | 85 FileLocation location(int offset) => new FileLocation._(this, offset); |
79 | 86 |
80 /// Gets the 0-based line corresponding to [offset]. | 87 /// Gets the 0-based line corresponding to [offset]. |
81 int getLine(int offset) { | 88 int getLine(int offset) { |
82 if (offset < 0) { | 89 if (offset < 0) { |
83 throw new RangeError("Offset may not be negative, was $offset."); | 90 throw new RangeError("Offset may not be negative, was $offset."); |
84 } else if (offset > length) { | 91 } else if (offset > length) { |
85 throw new RangeError("Offset $offset must not be greater than the number " | 92 throw new RangeError("Offset $offset must not be greater than the number " |
86 "of characters in the file, $length."); | 93 "of characters in the file, $length."); |
87 } | 94 } |
88 return binarySearch(_lineStarts, (o) => o > offset) - 1; | 95 |
| 96 if (offset < _lineStarts.first) return -1; |
| 97 if (offset >= _lineStarts.last) return _lineStarts.length - 1; |
| 98 |
| 99 if (_isNearCachedLine(offset)) return _cachedLine; |
| 100 |
| 101 _cachedLine = _binarySearch(offset) - 1; |
| 102 return _cachedLine; |
| 103 } |
| 104 |
| 105 /// Returns `true` if [offset] is near [_cachedLine]. |
| 106 /// |
| 107 /// Checks on [_cachedLine] and the next line. If it's on the next line, it |
| 108 /// updates [_cachedLine] to point to that. |
| 109 bool _isNearCachedLine(int offset) { |
| 110 if (_cachedLine == null) return false; |
| 111 |
| 112 // See if it's before the cached line. |
| 113 if (offset < _lineStarts[_cachedLine]) return false; |
| 114 |
| 115 // See if it's on the cached line. |
| 116 if (_cachedLine >= _lineStarts.length - 1 || |
| 117 offset < _lineStarts[_cachedLine + 1]) { |
| 118 return true; |
| 119 } |
| 120 |
| 121 // See if it's on the next line. |
| 122 if (_cachedLine >= _lineStarts.length - 2 || |
| 123 offset < _lineStarts[_cachedLine + 2]) { |
| 124 _cachedLine++; |
| 125 return true; |
| 126 } |
| 127 |
| 128 return false; |
| 129 } |
| 130 |
| 131 /// Binary search through [_lineStarts] to find the line containing [offset]. |
| 132 /// |
| 133 /// Returns the index of the line in [_lineStarts]. |
| 134 int _binarySearch(int offset) { |
| 135 int min = 0; |
| 136 int max = _lineStarts.length - 1; |
| 137 while (min < max) { |
| 138 var half = min + ((max - min) ~/ 2); |
| 139 if (_lineStarts[half] > offset) { |
| 140 max = half; |
| 141 } else { |
| 142 min = half + 1; |
| 143 } |
| 144 } |
| 145 |
| 146 return max; |
89 } | 147 } |
90 | 148 |
91 /// Gets the 0-based column corresponding to [offset]. | 149 /// Gets the 0-based column corresponding to [offset]. |
92 /// | 150 /// |
93 /// If [line] is passed, it's assumed to be the line containing [offset] and | 151 /// If [line] is passed, it's assumed to be the line containing [offset] and |
94 /// is used to more efficiently compute the column. | 152 /// is used to more efficiently compute the column. |
95 int getColumn(int offset, {int line}) { | 153 int getColumn(int offset, {int line}) { |
96 if (offset < 0) { | 154 if (offset < 0) { |
97 throw new RangeError("Offset may not be negative, was $offset."); | 155 throw new RangeError("Offset may not be negative, was $offset."); |
98 } else if (offset > length) { | 156 } else if (offset > length) { |
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
280 var start = math.min(this._start, other._start); | 338 var start = math.min(this._start, other._start); |
281 var end = math.max(this._end, other._end); | 339 var end = math.max(this._end, other._end); |
282 return new _FileSpan(file, start, end); | 340 return new _FileSpan(file, start, end); |
283 } else { | 341 } else { |
284 var start = math.min(this._start, other.start.offset); | 342 var start = math.min(this._start, other.start.offset); |
285 var end = math.max(this._end, other.end.offset); | 343 var end = math.max(this._end, other.end.offset); |
286 return new _FileSpan(file, start, end); | 344 return new _FileSpan(file, start, end); |
287 } | 345 } |
288 } | 346 } |
289 } | 347 } |
OLD | NEW |