Hmm...  Tim didn't turn "replace all" into a single undo block.
I think I like it better if it os, so here.
diff --git a/Tools/idle/ReplaceDialog.py b/Tools/idle/ReplaceDialog.py
index 813de48..e29d4d6 100644
--- a/Tools/idle/ReplaceDialog.py
+++ b/Tools/idle/ReplaceDialog.py
@@ -82,6 +82,7 @@
         ok = 1
         first = last = None
         # XXX ought to replace circular instead of top-to-bottom when wrapping
+        text.undo_block_start()
         while 1:
             res = self.engine.search_forward(text, prog, line, col, 0, ok)
             if not res:
@@ -103,6 +104,7 @@
                     text.insert(first, new)
             col = i + len(new)
             ok = 0
+        text.undo_block_stop()
         if first and last:
             self.show_hit(first, last)
         self.close()