Skip to content

fix: zero-length regex matches (^, $, \b) cause FindNext to silently no-op#88

Merged
tig merged 3 commits into
developfrom
copilot/fix-zero-length-regex-no-op
May 12, 2026
Merged

fix: zero-length regex matches (^, $, \b) cause FindNext to silently no-op#88
tig merged 3 commits into
developfrom
copilot/fix-zero-length-regex-no-op

Conversation

Copilot AI commented May 12, 2026

Copy link
Copy Markdown
Contributor
  • Fix SelectSearchMatch to handle zero-length matches (move caret, clear selection)
  • Fix FindNext to skip zero-length match at current search start (advance by 1)
  • Fix FindPrevious to skip zero-length match at current search start
  • Fix TryMatchSelection to handle zero-length matches for ReplaceNext
  • Fix FindForward/FindBackward to allow zero-length search regions
  • Add tests: F3 cycle on ^ pattern lands at 0 → 6 → 12 → wrap → 0
  • Add tests: \b cycle on "ab cd" lands at 0 → 2 → 3 → 5 → wrap → 0
  • Add tests: ReplaceNext with ^ inserts at position (prepend to each line)
  • Add tests: ReplaceAll with ^ inserts at all line starts
  • Decision: ReplaceNext for zero-length matches inserts at position (vim/sed convention)
  • Merge conflicts with develop resolved

All 434 tests pass (230 Text + 96 Editor + 108 Integration).

…eNext

- SelectSearchMatch now accepts length==0, clears selection anchor, moves caret
- FindNext/FindPrevious skip zero-length match at current position (advance by 1)
- FindForward/FindBackward allow length==0 search regions
- TryMatchSelection detects zero-length match at caret for ReplaceNext
- ReplaceNext inserts at position for zero-length matches (vim/sed convention)
- Add tests for ^ and \b regex cycling, and ReplaceNext/ReplaceAll with anchors

Agent-Logs-Url: https://github.com/gui-cs/Text/sessions/ea950c9c-82cd-447a-8d94-3cacc050e1e9

Co-authored-by: tig <585482+tig@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix zero-length regex matches in FindNext functionality fix: zero-length regex matches (^, $, \b) cause FindNext to silently no-op May 12, 2026
Copilot AI requested a review from tig May 12, 2026 14:23
Copilot finished work on behalf of tig May 12, 2026 14:23
@tig
tig marked this pull request as ready for review May 12, 2026 14:44
@tig

tig commented May 12, 2026

Copy link
Copy Markdown
Member

@copilot resolve the merge conflicts in this pull request

…ength-regex-no-op

# Conflicts:
#	src/Terminal.Gui.Editor/Editor.FindReplace.cs
#	tests/Terminal.Gui.Editor.Tests/EditorFindReplaceTests.cs

Co-authored-by: tig <585482+tig@users.noreply.github.com>

Copilot AI commented May 12, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Merge conflicts resolved in f890272. The conflict was in FindPrevious (develop rewrote it to use FindAll + TakeWhile instead of FindBackward) and in the test file (both branches added new tests). The TakeWhile(r => r.Offset < caretOrSelStart) approach from develop naturally handles zero-length matches, so no extra skip logic was needed for FindPrevious. All 434 tests pass.

Copilot finished work on behalf of tig May 12, 2026 14:49
Copilot AI requested a review from tig May 12, 2026 14:49

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 82f14b7334

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +36 to +39
if (match is { Length: 0 } && match.Offset == startOffset)
{
match = FindForward (startOffset + 1);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve sole zero-length match in FindNext

When FindNext encounters a zero-length match at the current start offset, it always discards it and retries from startOffset + 1. In a document where that current position is the only valid match (for example ^ in a single-line buffer with caret at offset 0), the retry returns null and FindNext returns false even though a valid match exists. This regresses zero-length search by making some real matches unreachable unless another match exists elsewhere.

Useful? React with 👍 / 👎.


// The caret anchor (AfterInsertion) already moved past the inserted text.
// Force a refresh of virtual column state.
SetCaretOffset (insertPos + replacementText.Length, true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Advance past empty zero-length replacements

In the zero-length replacement path, caret advancement is based only on replacementText.Length. If the replacement resolves to an empty string (e.g. replacement is "" or an unmatched backreference), the caret stays at the same offset and the document is unchanged, so repeated ReplaceNext keeps matching the same anchor and returns true indefinitely without progress. This reintroduces a no-op loop for zero-length regex replacements.

Useful? React with 👍 / 👎.

@tig
tig merged commit 074b23a into develop May 12, 2026
6 checks passed
@tig
tig deleted the copilot/fix-zero-length-regex-no-op branch May 12, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: zero-length regex matches (^, $, \b) cause FindNext to silently no-op

2 participants