Change replace to keep its position in the new result. Add a few members. (#8)

Add move-operations to character range for selecting everything before or after the current range.

Add getters for the characters or strings before and after the current range (and characters of the current range, to complement `current` which returns a string).

Change replace operations to return a range on the replaced result which contains the original range post-modification.
diff --git a/.travis.yml b/.travis.yml
index 5ed3b1d..27cdad6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
 language: dart
 dart:
 - dev
-- 2.4.0
+- 2.5.0
 # Only building master means that we don't run two builds for each pull request.
 dart_task:
 - test: --platform vm,chrome
diff --git a/lib/src/characters.dart b/lib/src/characters.dart
index 3e5170c..eec3ca8 100644
--- a/lib/src/characters.dart
+++ b/lib/src/characters.dart
@@ -178,15 +178,20 @@
 
   /// Replaces [pattern] with [replacement].
   ///
-  /// Returns a new [GrapehemeClusters] where all occurrences of the
-  /// [pattern] character sequence are replaced by [replacement],
-  /// unless the occurrence overlaps a prior replaced sequence.
+  /// Returns a new [Characters] sequence where all occurrences of the
+  /// [pattern] characters are replaced by [replacement],
+  /// unless the occurrence overlaps a prior
+  /// replaced occurrence of [pattern].
+  ///
+  /// Returns the current characters if there is no occurrence of [pattern].
   Characters replaceAll(Characters pattern, Characters replacement);
 
-  /// Replaces the first [pattern] with [replacement].
+  /// Replaces the first occurrence of [pattern] with [replacement].
   ///
   /// Returns a new [Characters] where the first occurence of the
   /// [pattern] character sequence, if any, is replaced by [replacement].
+  ///
+  /// Returns the current characters if there is no occurrence of [pattern].
   Characters replaceFirst(Characters pattern, Characters replacement);
 
   /// The characters of the lower-case version of [string].
@@ -250,7 +255,22 @@
   /// The code points of the current character range.
   Runes get runes;
 
-  /// Creates a copy of this [Character].
+  /// The characters of this range.
+  Characters get currentCharacters;
+
+  /// The characters before the current range.
+  Characters get charactersBefore;
+
+  /// The characters after the current range.
+  Characters get charactersAfter;
+
+  /// The string of the characters before the current range.
+  String get stringBefore;
+
+  /// The string of the characters after the current range.
+  String get stringAfter;
+
+  /// Creates a copy of this [CharacterRange].
   ///
   /// The copy is in the exact same state as this iterator.
   /// Can be used to iterate the following characters more than once
@@ -283,6 +303,9 @@
   /// and `false` if not.
   bool moveNext([int count = 1]);
 
+  /// Moves the range to be everything after the current range.
+  void moveNextAll();
+
   /// Moves the range to the next occurrence of [target]
   /// after the current range.
   ///
@@ -325,6 +348,9 @@
   /// and `false` if not.
   bool moveBack([int count = 1]);
 
+  /// Moves the range to be everything before the current range.
+  void moveBackAll();
+
   /// Moves the range to the last occurrence of [target]
   /// before the current range.
   ///
@@ -587,33 +613,58 @@
   /// which gives the same effect as [collapseToStart].
   void dropBackWhile(bool Function(String) test);
 
-  /// Creates a new [Characters] sequence by replacing the current range.
+  /// Replaces the current range with [replacement] and returns the result.
   ///
-  /// Replaces the current range in [source] with [replacement].
+  /// Replaces the current range in [source] with [replacement]
+  /// and returns a range of the resulting characters
+  /// which contains the replacement characters.
   ///
-  /// Returns a new [Characters] instance. Since the inserted characters
-  /// may combine with the preceding or following characters, grapheme cluster
-  /// boundaries need to be recomputed from scratch.
-  Characters replaceRange(Characters replacement);
+  /// The inserted characters may combine with
+  /// the preceding or following code points,
+  /// so that the start and end of the original range
+  /// are no longer grapheme cluster boundaries.
+  /// In that case, the returned range may extend into into the code points
+  /// before and after the original range.
+  CharacterRange replaceRange(Characters replacement);
 
-  /// Replaces all occurrences of [pattern] in the range with [replacement].
+  /// Replaces [pattern] in the current range with [replacement].
   ///
-  /// Replaces the first occurrence of [pattern] in the range, then repeatedly
-  /// finds and replaces the next occurrence which does not overlap with
-  /// the earlier, already replaced, occurrence.
+  /// Replaces all occurrences of [pattern] in the current range with
+  /// [replacement], unless they overlap with an earlier occurrence of
+  /// [pattern] which was replaced.
+  /// Then returns a range on the resulting characters
+  /// which contains all inserted replacement characters
+  /// and any remaining characters of the original range.
   ///
-  /// Returns new [Characters] instance for the resulting string.
-  Characters replaceAll(Characters pattern, Characters replacement);
+  /// The inserted characters may combine with
+  /// the preceding or following code points,
+  /// so that the start and end of the original range
+  /// are no longer grapheme cluster boundaries.
+  /// In that case, the returned range may extend into into the code points
+  /// before and after the original range.
+  ///
+  /// Returns `null` if there are no occurrences of [pattern]
+  /// in the current range.
+  CharacterRange /*?*/ replaceAll(Characters pattern, Characters replacement);
 
   /// Replaces the first occurrence of [pattern] with [replacement].
   ///
   /// Finds the first occurrence of [pattern] in the current range,
-  /// then replaces that occurrence with [replacement] and returns
-  /// the [Characters] of that string.
+  /// then replaces that occurrence with [replacement].
+  /// Then returns a range on the resulting characters
+  /// which contains the inserted replacement characters
+  /// and any remaining characters of the original range.
   ///
-  /// If there is no first occurrence of [pattern], then the
-  /// characters of the source string is returned.
-  Characters replaceFirst(Characters pattern, Characters replacement);
+  /// The inserted characters may combine with
+  /// the preceding or following code points,
+  /// so that the start and end of the original range
+  /// are no longer grapheme cluster boundaries.
+  /// In that case, the returned range may extend into into the code points
+  /// before and after the original range.
+  ///
+  /// Returns `null` if there are no occurrences of [pattern]
+  /// in the current range.
+  CharacterRange /*?*/ replaceFirst(Characters pattern, Characters replacement);
 
   /// Whether the current range starts with [characters].
   ///
diff --git a/lib/src/characters_impl.dart b/lib/src/characters_impl.dart
index 109d750..46b10b6 100644
--- a/lib/src/characters_impl.dart
+++ b/lib/src/characters_impl.dart
@@ -148,14 +148,11 @@
 
   @override
   Characters replaceAll(Characters pattern, Characters replacement) =>
-      _rangeAll.replaceAll(pattern, replacement);
+      _rangeAll.replaceAll(pattern, replacement)?.source ?? this;
 
   @override
-  Characters replaceFirst(Characters pattern, Characters replacement) {
-    var range = _rangeAll;
-    if (!range.collapseToFirst(pattern)) return this;
-    return range.replaceRange(replacement);
-  }
+  Characters replaceFirst(Characters pattern, Characters replacement) =>
+      _rangeAll.replaceFirst(pattern, replacement)?.source ?? this;
 
   @override
   bool containsAll(Characters other) =>
@@ -750,32 +747,37 @@
   }
 
   @override
-  Characters replaceFirst(Characters pattern, Characters replacement) {
+  CharacterRange /*?*/ replaceFirst(
+      Characters pattern, Characters replacement) {
     String patternString = pattern.string;
     String replacementString = replacement.string;
+    String replaced;
     if (patternString.isEmpty) {
-      return StringCharacters(
-          _string.replaceRange(_start, _start, replacementString));
+      replaced = _string.replaceRange(_start, _start, replacementString);
+    } else {
+      int index = _indexOf(_string, patternString, _start, _end);
+      if (index >= 0) {
+        replaced = _string.replaceRange(
+            index, index + patternString.length, replacementString);
+      } else {
+        return null;
+      }
     }
-    int index = _indexOf(_string, patternString, _start, _end);
-    String result = _string;
-    if (index >= 0) {
-      result = _string.replaceRange(
-          index, index + patternString.length, replacementString);
-    }
-    return StringCharacters(result);
+    int newEnd = replaced.length - _string.length + _end;
+    return _expandRange(replaced, _start, newEnd);
   }
 
   @override
-  Characters replaceAll(Characters pattern, Characters replacement) {
+  CharacterRange /*?*/ replaceAll(Characters pattern, Characters replacement) {
     var patternString = pattern.string;
     var replacementString = replacement.string;
     if (patternString.isEmpty) {
       var replaced = _explodeReplace(
           _string, _start, _end, replacementString, replacementString);
-      return StringCharacters(replaced);
+      int newEnd = replaced.length - (_string.length - _end);
+      return _expandRange(replaced, _start, newEnd);
     }
-    if (_start == _end) return Characters(_string);
+    if (_start == _end) return null;
     int start = 0;
     int cursor = _start;
     StringBuffer buffer;
@@ -786,14 +788,26 @@
       cursor += patternString.length;
       start = cursor;
     }
-    if (buffer == null) return Characters(_string);
+    if (buffer == null) return null;
     buffer.write(_string.substring(start));
-    return Characters(buffer.toString());
+    String replaced = buffer.toString();
+    int newEnd = replaced.length - (_string.length - _end);
+    return _expandRange(replaced, _start, newEnd);
   }
 
   @override
-  Characters replaceRange(Characters replacement) {
-    return Characters(_string.replaceRange(_start, _end, replacement.string));
+  CharacterRange replaceRange(Characters replacement) {
+    String replacementString = replacement.string;
+    String resultString = _string.replaceRange(_start, _end, replacementString);
+    return _expandRange(
+        resultString, _start, _start + replacementString.length);
+  }
+
+  // Expands a range if its start or end are not grapheme cluster boundaries.
+  StringCharacterRange _expandRange(String string, int start, int end) {
+    start = previousBreak(string, 0, string.length, start);
+    end = nextBreak(string, 0, string.length, end);
+    return StringCharacterRange._(string, start, end);
   }
 
   @override
@@ -856,6 +870,32 @@
     }
     return false;
   }
+
+  @override
+  Characters get charactersAfter => StringCharacters(_string.substring(_end));
+
+  @override
+  Characters get charactersBefore =>
+      StringCharacters(_string.substring(0, _start));
+
+  @override
+  Characters get currentCharacters => StringCharacters(current);
+
+  @override
+  void moveBackAll() {
+    _move(0, _start);
+  }
+
+  @override
+  void moveNextAll() {
+    _move(_end, _string.length);
+  }
+
+  @override
+  String get stringAfter => _string.substring(_end);
+
+  @override
+  String get stringBefore => _string.substring(0, _start);
 }
 
 class _CodeUnits extends ListBase<int> {
diff --git a/lib/src/grapheme_clusters/breaks.dart b/lib/src/grapheme_clusters/breaks.dart
index 9a41609..08d1d84 100644
--- a/lib/src/grapheme_clusters/breaks.dart
+++ b/lib/src/grapheme_clusters/breaks.dart
@@ -295,7 +295,7 @@
   return start != end;
 }
 
-/// The most recent break no later than [position] in
+/// The most recent break no later than [index] in
 /// `string.substring(start, end)`.
 int previousBreak(String text, int start, int end, int index) {
   assert(0 <= start);
@@ -328,7 +328,7 @@
       .nextBreak();
 }
 
-/// The next break no earlier than [position] in `string.substring(start, end)`.
+/// The next break no earlier than [index] in `string.substring(start, end)`.
 ///
 /// The index need not be at a grapheme cluster boundary.
 int nextBreak(String text, int start, int end, int index) {
diff --git a/pubspec.yaml b/pubspec.yaml
index 5f1858c..4084b0b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
 name: characters
-version: 0.2.0
+version: 0.3.0
 environment:
-  sdk: "^2.4.0"
+  sdk: "^2.5.0"
 dev_dependencies:
   test: "^1.6.0"
diff --git a/test/characters_test.dart b/test/characters_test.dart
index cbb6e03..8ef5ec2 100644
--- a/test/characters_test.dart
+++ b/test/characters_test.dart
@@ -477,7 +477,7 @@
 
     var cs2 = cs.replaceAll(c, gc(""));
     var cs3 = cs.replaceFirst(c, gc(""));
-    var cs4 = cs.findFirst(c).replaceRange(gc(""));
+    var cs4 = cs.findFirst(c).replaceRange(gc("")).source;
     var cse = gc("$a$b$d$e");
     expect(cs2, cse);
     expect(cs3, cse);
@@ -498,9 +498,11 @@
     it.expandTo(b + a);
     expect("$b$a$d$b$a", it.current);
     var cs10 = it.replaceAll(b + a, e + e);
-    expect(cs10, gc("$a$c$e$e$d$e$e"));
+    expect(cs10.currentCharacters, e + e + d + e + e);
+    expect(cs10.source, gc("$a$c$e$e$d$e$e"));
     var cs11 = it.replaceRange(e);
-    expect(cs11, gc("$a$c$e"));
+    expect(cs11.currentCharacters, e);
+    expect(cs11.source, gc("$a$c$e"));
 
     expect(cs.startsWith(gc("")), true);
     expect(cs.startsWith(a), true);
@@ -552,4 +554,66 @@
     expect(it.isFollowedBy(c + d + e), false);
     expect(it.isFollowedBy(e), false);
   });
+  test("replace methods", () {
+    // Unicode grapheme breaking character classes,
+    // represented by their first value.
+
+    var pattern = gc("\t"); // A non-combining entry to be replaced.
+    var non = gc("");
+
+    var c = otr + cr + pattern + lf + pic + pattern + zwj + pic + otr;
+    var r = c.replaceAll(pattern, non);
+    expect(r, otr + cr + lf + pic + zwj + pic + otr);
+    var ci = c.iterator..moveNextAll();
+    var ri = ci.replaceAll(pattern, non);
+    expect(ri.currentCharacters, otr + cr + lf + pic + zwj + pic + otr);
+    ci.dropFirst();
+    ci.dropLast();
+    expect(ci.currentCharacters, cr + pattern + lf + pic + pattern + zwj + pic);
+    expect(ci.currentCharacters.length, 7);
+    ri = ci.replaceAll(pattern, non);
+    expect(ri.currentCharacters, cr + lf + pic + zwj + pic);
+    expect(ri.currentCharacters.length, 2);
+    ci.dropFirst();
+    ci.dropLast(5);
+    expect(ci.currentCharacters, pattern);
+    ri = ci.replaceAll(pattern, non);
+    expect(ri.currentCharacters, cr + lf);
+    ci.moveNext(2);
+    ci.moveNext(1);
+    expect(ci.currentCharacters, pattern);
+    ri = ci.replaceAll(pattern, non);
+    expect(ri.currentCharacters, pic + zwj + pic);
+
+    c = otr + pic + ext + pattern + pic + ext + otr;
+    expect(c.length, 5);
+    ci = c.iterator..moveTo(pattern);
+    expect(ci.currentCharacters, pattern);
+    ri = ci.replaceAll(pattern, zwj);
+    expect(ri.currentCharacters, pic + ext + zwj + pic + ext);
+
+    c = reg + pattern + reg + reg;
+    ci = c.iterator..moveTo(pattern);
+    ri = ci.replaceRange(non);
+    expect(ri.currentCharacters, reg + reg);
+    expect(ri.moveNext(), true);
+    expect(ri.currentCharacters, reg);
+  });
 }
+
+/// Sample characters from each breaking algorithm category.
+final Characters ctl = gc("\x00"); // Control, NUL.
+final Characters cr = gc("\r"); // Carriage Return, CR.
+final Characters lf = gc("\n"); // Newline, NL.
+final Characters otr = gc(" "); // Other, Space.
+final Characters ext = gc("\u0300"); // Extend, Combining Grave Accent.
+final Characters spc = gc("\u0903"); // Spacing Mark, Devanagari Sign Visarga.
+final Characters pre = gc("\u0600"); // Prepend, Arabic Number Sign.
+final Characters zwj = gc("\u200d"); // Zero-Width Joiner.
+final Characters pic = gc("\u00a9"); // Extended Pictographic, Copyright.
+final Characters reg = gc("\u{1f1e6}"); // Regional Identifier "a".
+final Characters hanl = gc("\u1100"); // Hangul L, Choseong Kiyeok.
+final Characters hanv = gc("\u1160"); // Hangul V, Jungseong Filler.
+final Characters hant = gc("\u11a8"); // Hangul T, Jongseong Kiyeok.
+final Characters hanlv = gc("\uac00"); // Hangul LV, Syllable Ga.
+final Characters hanlvt = gc("\uac01"); // Hangul LVT, Syllable Gag.