| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| // 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. |
| |
| /// Defines [StringEditBuffer], a buffer that can be used to apply edits on a |
| /// string. |
| // TODO(sigmund): this should move to a separate package. |
| library dart2js.src.string_edit_buffer; |
| |
| /// A buffer meant to apply edits on a string (rather than building a string |
| /// from scratch). Each change is described using the location information on |
| /// the original string. Internally this buffer keeps track of how a |
| /// modification in one portion can offset a modification further down the |
| /// string. |
| class StringEditBuffer { |
| final String original; |
| final _edits = <_StringEdit>[]; |
| |
| StringEditBuffer(this.original); |
| |
| bool get hasEdits => _edits.isNotEmpty; |
| |
| /// Edit the original text, replacing text on the range [begin] and |
| /// exclusive [end] with the [replacement] string. |
| void replace(int begin, int end, String replacement, [int sortId]) { |
| _edits.add(_StringEdit(begin, end, replacement, sortId)); |
| } |
| |
| /// Insert [string] at [offset]. |
| /// Equivalent to `replace(offset, offset, string)`. |
| void insert(int offset, String string, [sortId]) => |
| replace(offset, offset, string, sortId); |
| |
| /// Remove text from the range [begin] to exclusive [end]. |
| /// Equivalent to `replace(begin, end, '')`. |
| void remove(int begin, int end) => replace(begin, end, ''); |
| |
| /// Applies all pending [edit]s and returns a new string. |
| /// |
| /// This method is non-destructive: it does not discard existing edits or |
| /// change the [original] string. Further edits can be added and this method |
| /// can be called again. |
| /// |
| /// Throws [UnsupportedError] if the edits were overlapping. If no edits were |
| /// made, the original string will be returned. |
| @override |
| String toString() { |
| var sb = StringBuffer(); |
| if (_edits.isEmpty) return original; |
| |
| // Sort edits by start location. |
| _edits.sort(); |
| |
| int consumed = 0; |
| for (var edit in _edits) { |
| if (consumed > edit.begin) { |
| sb = StringBuffer(); |
| sb.write('overlapping edits. Insert at offset '); |
| sb.write(edit.begin); |
| sb.write(' but have consumed '); |
| sb.write(consumed); |
| sb.write(' input characters. List of edits:'); |
| for (var e in _edits) { |
| sb.write('\n '); |
| sb.write(e); |
| } |
| throw UnsupportedError(sb.toString()); |
| } |
| |
| // Add characters from the original string between this edit and the last |
| // one, if any. |
| var betweenEdits = original.substring(consumed, edit.begin); |
| sb.write(betweenEdits); |
| sb.write(edit.string); |
| consumed = edit.end; |
| } |
| |
| // Add any text from the end of the original string that was not replaced. |
| sb.write(original.substring(consumed)); |
| return sb.toString(); |
| } |
| } |
| |
| /// A single edit in a [StringEditBuffer]. |
| class _StringEdit implements Comparable<_StringEdit> { |
| // Offset where edit begins |
| final int begin; |
| |
| // Offset where edit ends |
| final int end; |
| |
| // Sort index as a tie-breaker for edits that have the same location. |
| final int sortId; |
| |
| // String to insert |
| final String string; |
| |
| _StringEdit(this.begin, this.end, this.string, [int sortId]) |
| : sortId = sortId ?? begin; |
| |
| int get length => end - begin; |
| |
| @override |
| String toString() => '(Edit @ $begin,$end: "$string")'; |
| |
| @override |
| int compareTo(_StringEdit other) { |
| int diff = begin - other.begin; |
| if (diff != 0) return diff; |
| diff = end - other.end; |
| if (diff != 0) return diff; |
| // use edit order as a tie breaker |
| return sortId - other.sortId; |
| } |
| } |