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 'package:path/path.dart' as p; | 10 import 'package:path/path.dart' as p; |
(...skipping 30 matching lines...) Expand all Loading... |
41 | 41 |
42 /// The length of the file in characters. | 42 /// The length of the file in characters. |
43 int get length => _decodedChars.length; | 43 int get length => _decodedChars.length; |
44 | 44 |
45 /// The number of lines in the file. | 45 /// The number of lines in the file. |
46 int get lines => _lineStarts.length; | 46 int get lines => _lineStarts.length; |
47 | 47 |
48 /// Creates a new source file from [text]. | 48 /// Creates a new source file from [text]. |
49 /// | 49 /// |
50 /// [url] may be either a [String], a [Uri], or `null`. | 50 /// [url] may be either a [String], a [Uri], or `null`. |
51 SourceFile(String text, {url}) | 51 SourceFile(String text, {url}) : this.decoded(text.runes, url: url); |
52 : this.decoded(text.runes, url: url); | |
53 | 52 |
54 /// Creates a new source file from a list of decoded characters. | 53 /// Creates a new source file from a list of decoded characters. |
55 /// | 54 /// |
56 /// [url] may be either a [String], a [Uri], or `null`. | 55 /// [url] may be either a [String], a [Uri], or `null`. |
57 SourceFile.decoded(Iterable<int> decodedChars, {url}) | 56 SourceFile.decoded(Iterable<int> decodedChars, {url}) |
58 : url = url is String ? Uri.parse(url) : url, | 57 : url = url is String ? Uri.parse(url) : url, |
59 _decodedChars = new Uint32List.fromList(decodedChars.toList()) { | 58 _decodedChars = new Uint32List.fromList(decodedChars.toList()) { |
60 for (var i = 0; i < _decodedChars.length; i++) { | 59 for (var i = 0; i < _decodedChars.length; i++) { |
61 var c = _decodedChars[i]; | 60 var c = _decodedChars[i]; |
62 if (c == _CR) { | 61 if (c == _CR) { |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
157 /// | 156 /// |
158 /// A [FileLocation] can be created using [SourceFile.location]. | 157 /// A [FileLocation] can be created using [SourceFile.location]. |
159 class FileLocation extends SourceLocation { | 158 class FileLocation extends SourceLocation { |
160 /// The [file] that [this] belongs to. | 159 /// The [file] that [this] belongs to. |
161 final SourceFile file; | 160 final SourceFile file; |
162 | 161 |
163 Uri get sourceUrl => file.url; | 162 Uri get sourceUrl => file.url; |
164 int get line => file.getLine(offset); | 163 int get line => file.getLine(offset); |
165 int get column => file.getColumn(offset); | 164 int get column => file.getColumn(offset); |
166 | 165 |
167 FileLocation._(this.file, int offset) | 166 FileLocation._(this.file, int offset) : super(offset) { |
168 : super(offset) { | |
169 if (offset > file.length) { | 167 if (offset > file.length) { |
170 throw new RangeError("Offset $offset must not be greater than the number " | 168 throw new RangeError("Offset $offset must not be greater than the number " |
171 "of characters in the file, ${file.length}."); | 169 "of characters in the file, ${file.length}."); |
172 } | 170 } |
173 } | 171 } |
174 | 172 |
175 FileSpan pointSpan() => new FileSpan._(file, offset, offset); | 173 FileSpan pointSpan() => new FileSpan._(file, offset, offset); |
176 } | 174 } |
177 | 175 |
178 /// A [SourceSpan] within a [SourceFile]. | 176 /// A [SourceSpan] within a [SourceFile]. |
179 /// | 177 /// |
180 /// Unlike the base [SourceSpan], [FileSpan] lazily computes its line and column | 178 /// Unlike the base [SourceSpan], [FileSpan] lazily computes its line and column |
181 /// values based on its offset and the contents of [file]. [FileSpan.message] is | 179 /// values based on its offset and the contents of [file]. [FileSpan.message] is |
182 /// also able to provide more context then [SourceSpan.message], and | 180 /// also able to provide more context then [SourceSpan.message], and |
183 /// [FileSpan.union] will return a [FileSpan] if possible. | 181 /// [FileSpan.union] will return a [FileSpan] if possible. |
184 /// | 182 /// |
185 /// A [FileSpan] can be created using [SourceFile.span]. | 183 /// A [FileSpan] can be created using [SourceFile.span]. |
186 class FileSpan extends SourceSpanMixin { | 184 class FileSpan extends SourceSpanMixin implements SourceSpanWithContext { |
187 /// The [file] that [this] belongs to. | 185 /// The [file] that [this] belongs to. |
188 final SourceFile file; | 186 final SourceFile file; |
189 | 187 |
190 /// The offset of the beginning of the span. | 188 /// The offset of the beginning of the span. |
191 /// | 189 /// |
192 /// [start] is lazily generated from this to avoid allocating unnecessary | 190 /// [start] is lazily generated from this to avoid allocating unnecessary |
193 /// objects. | 191 /// objects. |
194 final int _start; | 192 final int _start; |
195 | 193 |
196 /// The offset of the end of the span. | 194 /// The offset of the end of the span. |
197 /// | 195 /// |
198 /// [end] is lazily generated from this to avoid allocating unnecessary | 196 /// [end] is lazily generated from this to avoid allocating unnecessary |
199 /// objects. | 197 /// objects. |
200 final int _end; | 198 final int _end; |
201 | 199 |
202 Uri get sourceUrl => file.url; | 200 Uri get sourceUrl => file.url; |
203 int get length => _end - _start; | 201 int get length => _end - _start; |
204 FileLocation get start => new FileLocation._(file, _start); | 202 FileLocation get start => new FileLocation._(file, _start); |
205 FileLocation get end => new FileLocation._(file, _end); | 203 FileLocation get end => new FileLocation._(file, _end); |
206 String get text => file.getText(_start, _end); | 204 String get text => file.getText(_start, _end); |
207 | 205 |
| 206 String get context { |
| 207 var line = start.line; |
| 208 return file.getText(file.getOffset(line), |
| 209 line == file.lines - 1 ? null : file.getOffset(line + 1)); |
| 210 } |
| 211 |
208 FileSpan._(this.file, this._start, this._end) { | 212 FileSpan._(this.file, this._start, this._end) { |
209 if (_end < _start) { | 213 if (_end < _start) { |
210 throw new ArgumentError('End $_end must come after start $_start.'); | 214 throw new ArgumentError('End $_end must come after start $_start.'); |
211 } else if (_end > file.length) { | 215 } else if (_end > file.length) { |
212 throw new RangeError("End $_end must not be greater than the number " | 216 throw new RangeError("End $_end must not be greater than the number " |
213 "of characters in the file, ${file.length}."); | 217 "of characters in the file, ${file.length}."); |
214 } else if (_start < 0) { | 218 } else if (_start < 0) { |
215 throw new RangeError("Start may not be negative, was $_start."); | 219 throw new RangeError("Start may not be negative, was $_start."); |
216 } | 220 } |
217 } | 221 } |
(...skipping 15 matching lines...) Expand all Loading... |
233 | 237 |
234 if (beginSpan._end < endSpan._start) { | 238 if (beginSpan._end < endSpan._start) { |
235 throw new ArgumentError("Spans $this and $other are disjoint."); | 239 throw new ArgumentError("Spans $this and $other are disjoint."); |
236 } | 240 } |
237 | 241 |
238 return span; | 242 return span; |
239 } | 243 } |
240 | 244 |
241 bool operator ==(other) { | 245 bool operator ==(other) { |
242 if (other is! FileSpan) return super == other; | 246 if (other is! FileSpan) return super == other; |
243 return _start == other._start && _end == other._end && | 247 return _start == other._start && |
| 248 _end == other._end && |
244 sourceUrl == other.sourceUrl; | 249 sourceUrl == other.sourceUrl; |
245 } | 250 } |
246 | 251 |
247 int get hashCode => _start.hashCode + 5 * _end.hashCode + | 252 int get hashCode => |
248 7 * sourceUrl.hashCode; | 253 _start.hashCode + 5 * _end.hashCode + 7 * sourceUrl.hashCode; |
249 | 254 |
250 /// Returns a new span that covers both [this] and [other]. | 255 /// Returns a new span that covers both [this] and [other]. |
251 /// | 256 /// |
252 /// Unlike [union], [other] may be disjoint from [this]. If it is, the text | 257 /// Unlike [union], [other] may be disjoint from [this]. If it is, the text |
253 /// between the two will be covered by the returned span. | 258 /// between the two will be covered by the returned span. |
254 FileSpan expand(FileSpan other) { | 259 FileSpan expand(FileSpan other) { |
255 if (sourceUrl != other.sourceUrl) { | 260 if (sourceUrl != other.sourceUrl) { |
256 throw new ArgumentError("Source URLs \"${sourceUrl}\" and " | 261 throw new ArgumentError("Source URLs \"${sourceUrl}\" and " |
257 " \"${other.sourceUrl}\" don't match."); | 262 " \"${other.sourceUrl}\" don't match."); |
258 } | 263 } |
259 | 264 |
260 var start = math.min(this._start, other._start); | 265 var start = math.min(this._start, other._start); |
261 var end = math.max(this._end, other._end); | 266 var end = math.max(this._end, other._end); |
262 return new FileSpan._(file, start, end); | 267 return new FileSpan._(file, start, end); |
263 } | 268 } |
264 | |
265 String message(String message, {color}) { | |
266 if (color == true) color = colors.RED; | |
267 if (color == false) color = null; | |
268 | |
269 var line = start.line; | |
270 var column = start.column; | |
271 | |
272 var buffer = new StringBuffer(); | |
273 buffer.write('line ${start.line + 1}, column ${start.column + 1}'); | |
274 if (sourceUrl != null) buffer.write(' of ${p.prettyUri(sourceUrl)}'); | |
275 buffer.write(': $message\n'); | |
276 | |
277 var textLine = file.getText(file.getOffset(line), | |
278 line == file.lines - 1 ? null : file.getOffset(line + 1)); | |
279 | |
280 column = math.min(column, textLine.length - 1); | |
281 var toColumn = | |
282 math.min(column + end.offset - start.offset, textLine.length); | |
283 | |
284 if (color != null) { | |
285 buffer.write(textLine.substring(0, column)); | |
286 buffer.write(color); | |
287 buffer.write(textLine.substring(column, toColumn)); | |
288 buffer.write(colors.NONE); | |
289 buffer.write(textLine.substring(toColumn)); | |
290 } else { | |
291 buffer.write(textLine); | |
292 } | |
293 if (!textLine.endsWith('\n')) buffer.write('\n'); | |
294 | |
295 buffer.write(' ' * column); | |
296 if (color != null) buffer.write(color); | |
297 buffer.write('^' * math.max(toColumn - column, 1)); | |
298 if (color != null) buffer.write(colors.NONE); | |
299 return buffer.toString(); | |
300 } | |
301 } | 269 } |
OLD | NEW |