blob: dc314f71a7050c3ed30d7d1885f8f373283d5454 [file] [log] [blame]
// 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.length > 0;
/// 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(new _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.
String toString() {
var sb = new StringBuffer();
if (_edits.length == 0) return original;
// Sort edits by start location.
_edits.sort();
int consumed = 0;
for (var edit in _edits) {
if (consumed > edit.begin) {
sb = new 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 new 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(int begin, this.end, this.string, [int sortId])
: begin = begin,
sortId = sortId == null ? begin : sortId;
int get length => end - begin;
String toString() => '(Edit @ $begin,$end: "$string")';
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;
}
}