Status: Done — engine + ted UI (PRs #76/#79) plus hit-highlight renderer + F3/Shift+F3/Ctrl+F/Ctrl+H keybindings (PR #104, merged into develop 2026-05-13); issue #100 closed
Created: 2026-05-10
Last updated: 2026-05-17
Depends on: search ✅ (lift landed in PR #76), rendering-pipeline ✅
Blocked by: —
Migrate the existing find/replace implementation from bespoke string.IndexOf to the ISearchStrategy abstraction (search lift). Expose Editor.SearchStrategy as the single seam, surface regex / whole-word / case-sensitivity through ted's FindReplaceDialog, and keep ReplaceAll collapsing into a single undo step. Hit-highlight rendering and the F3 / Ctrl+F / Ctrl+H keybindings remain as a follow-up slice.
The existing Editor.FindReplace.cs already had FindNext / FindPrevious / ReplaceNext / ReplaceAll — this work replaces their internals with the ISearchStrategy seam, adds the property entry point, and wires the toggle UI in ted.
Given the find dialog with regex mode enabled, When the user searches for \d{3}, Then all three-digit number sequences are found.
Given the find dialog with whole-word mode enabled, When the user searches for "cat", Then only standalone "cat" matches, not "catalog" or "scatter".
Given a document with 5 occurrences of "foo", When the user does Replace All with "bar", Then all 5 replacements happen and a single Ctrl+Z undoes all of them.
Given a search for "TODO" with 3 matches in the visible viewport, When the search is active, Then all 3 matches are visually highlighted via SearchHitRenderer.
Given active search highlights, When the document is edited (adding or removing a match), Then the highlights update immediately.
Given the caret is past the last match, When the user invokes Find Next, Then the search wraps around to the first match in the document.
Given the regex (\w+)=(\d+) and replacement $2:$1, When the user replaces a match count=42, Then the document contains 42:count.
- FR-001 ✅ Replace
_document.Text.IndexOfcalls withISearchStrategyfrom search. - FR-002 ✅
Editor.SearchStrategyproperty is the single search seam; string-based overloads are convenience wrappers that build aSearchMode.Normalstrategy and delegate. - FR-003 (deferred) — Implement
SearchHitRenderer : IBackgroundRendererto highlight matches. - FR-004 ✅
ReplaceAllmaterializes matches once viaFindAll, replaces in reverse under oneDocument.RunUpdate ()scope — both for the perf benefit (~4× faster, ~100× less allocation on N=1000 matches per the quick-find benchmark) and the single-step-undo invariant (R5). - FR-005 (deferred) — Keybindings: F3 (find next), Shift+F3 (find previous), Ctrl+F (open find), Ctrl+H (open replace).
- FR-006 (deferred) — Hit highlights invalidate on document change. Depends on FR-003.
Landed in this slice:
src/Terminal.Gui.Editor/Editor.FindReplace.cs— engine swap +SearchStrategyproperty.examples/ted/FindReplaceDialog.cs— Match case / Whole word / Regex checkboxes + regex-error status label.tests/Terminal.Gui.Editor.Tests/EditorFindReplaceTests.cs— 10 new tests covering property round-trip, regex through Editor, whole-word, backreference substitution, reverse-replace safety, single-step undo with regex.benchmarks/Terminal.Gui.Editor.Benchmarks/FindBenchmarks.cs+Program.cs— BDN engine comparison +--quick-findStopwatch microbench.
Deferred to follow-up:
src/Terminal.Gui.Editor/Editor.Commands.cs(find/replace commands)src/Terminal.Gui.Editor/Editor.Keyboard.cs(F3/Shift+F3/Ctrl+F/Ctrl+H bindings)src/Terminal.Gui.Editor/Rendering/SearchHitRenderer.cs(new)
Engine + UI slice (this PR):
-
Editor.FindReplace.csno longer references_document.Text.IndexOf. -
SearchStrategyis the single seam for all search operations. - Regex search test passes.
- Whole-word search test passes.
-
ReplaceAllundo collapses (single Ctrl+Z undoes all replacements). - Find-next wraparound test passes.
- ted's
FindReplaceDialogexposes Match case / Whole word / Regex toggles, builds anISearchStrategyfrom them, surfaces invalid-regex errors on a status label.
Follow-up slice (separate PR):
- Hit highlights paint via
SearchHitRenderer : IBackgroundRenderer. - Hit highlights invalidate on document change.
- Keybindings (F3, Shift+F3, Ctrl+F, Ctrl+H) wired.
- TextMate-based search scoping.
- Multi-file search.
- Rope-walking matcher (would fix per-keystroke document materialization in incremental search; out of scope for this lift slice — the lift carries AvaloniaEdit's
RegexSearchStrategyverbatim, which itself materializesdocument.Textonce perFindAll).
-
rendering-pipeline and search are both done; FR-003 / FR-005 / FR-006 are deferred not because of blockers but because the engine + UI slice is large enough to merit its own review.
-
The
Editor.SearchStrategyproperty is a new public surface —specs/public-api.mdupdated. -
Benchmarks (Stopwatch microbench,
dotnet run --project benchmarks/Terminal.Gui.Editor.Benchmarks -c Release -- --quick-find):Matches New (ms) Old (ms) Speedup Old alloc New alloc Memory ratio 10 0.53 0.58 ~equal 2.2 MB 493 KB 4.5× less 100 0.73 2.52 3.5× faster 19.9 MB 621 KB 32× less 1000 3.55 14.84 4.2× faster 191.8 MB 1.86 MB 103× less Per-call
FindNextcost is roughly equal between engines — the per-keystroke incremental-search materialization remains. The win is concentrated inReplaceAll, where the new path replaces N rope materializations with 1.