Repository: asottile/babi

Issue:
replace all does not account for line change
The search and replace functionality does not account for line change when replacing all occurences.

Example:
Write a simple file with the following content:
```
A_A_
```
and have the cursor on the last position.
When replacing every `A` with `XXX` (or anything of length 3 or greater) the following change will be made:
```
XXX_A_
```

The expected bahavior would be to have the following result:
```
XXX_XXX_
```


Discussion:

Patch:
diff --git a/babi/file.py b/babi/file.py
index 852c197..0165154 100644
--- a/babi/file.py
+++ b/babi/file.py
@@ -189,6 +189,11 @@ class _SearchIter:
     def __iter__(self) -> _SearchIter:
         return self
 
+    def replaced(self, y: int, match: Match[str], new: str) -> None:
+        if not self.wrapped or y != self._start_y:
+            return
+        self._start_x += len(new) - match.end() - match.start()
+
     def _stop_if_past_original(self, y: int, match: Match[str]) -> Found:
         if (
                 self.wrapped and (
@@ -487,6 +492,7 @@ class File:
                 count += 1
                 with self.edit_action_context('replace', final=True):
                     replaced = match.expand(replace)
+                    search.replaced(line_y, match, replaced)
                     line = screen.file.buf[line_y]
                     if '\n' in replaced:
                         replaced_lines = replaced.split('\n')


Test patch:
diff --git a/tests/features/replace_test.py b/tests/features/replace_test.py
index f864450..262dd1a 100644
--- a/tests/features/replace_test.py
+++ b/tests/features/replace_test.py
@@ -239,6 +239,19 @@ def test_replace_multiple_occurrences_in_line(run):
         h.await_text('bqbq')
 
 
+def test_replace_multiple_occurences_with_line_length_change(run):
+    with run() as h, and_exit(h):
+        h.press('a_a_')
+        h.press('^\\')
+        h.await_text('search (to replace):')
+        h.press_and_enter('a')
+        h.await_text('replace with:')
+        h.press_and_enter('XXX')
+        h.await_text('replace [yes, no, all]?')
+        h.press('a')
+        h.await_text('XXX_XXX_')
+
+
 def test_replace_after_wrapping(run, ten_lines):
     with run(str(ten_lines)) as h, and_exit(h):
         h.press('Down')