Skip to content

feat(extension-find-and-replace): support capture groups in replacements - #8129

Open
Aslam97 wants to merge 18 commits into
mainfrom
feat/find-and-replace-capture-groups
Open

feat(extension-find-and-replace): support capture groups in replacements#8129
Aslam97 wants to merge 18 commits into
mainfrom
feat/find-and-replace-capture-groups

Conversation

@Aslam97

@Aslam97 Aslam97 commented Jul 28, 2026

Copy link
Copy Markdown
Member

Changes and review

This PR improves the standalone, headless Find and Replace extension:

Checklist

  • I have added a changeset if necessary.
  • I have added regression tests.
  • I have documented the behavioral change and migration path.
  • I have manually tested my changes.

Responsibility

  • I have reviewed and understand these changes, and I take responsibility for this PR, even if an AI agent created it.

AI assistance

This contribution was developed with assistance from OpenAI Codex. I reviewed all generated changes and manually verified the resulting behavior.

UI Component requirement

Screenshot 2026-07-30 at 20 02 09

@Aslam97
Aslam97 requested a review from a team as a code owner July 28, 2026 01:04
@netlify

netlify Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploy Preview for tiptap-embed ready!

Name Link
🔨 Latest commit 1094147
🔍 Latest deploy log https://app.netlify.com/projects/tiptap-embed/deploys/6a7933f0c4d356000812d3b3
😎 Deploy Preview https://deploy-preview-8129--tiptap-embed.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1094147

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 76 packages
Name Type
@tiptap/extension-find-and-replace Major
@tiptap/core Major
@tiptap/extension-audio Major
@tiptap/extension-blockquote Major
@tiptap/extension-bold Major
@tiptap/extension-bubble-menu Major
@tiptap/extension-bullet-list Major
@tiptap/extension-code-block-lowlight Major
@tiptap/extension-code-block Major
@tiptap/extension-code Major
@tiptap/extension-collaboration-caret Major
@tiptap/extension-collaboration Major
@tiptap/extension-color Major
@tiptap/extension-details Major
@tiptap/extension-document Major
@tiptap/extension-drag-handle-react Major
@tiptap/extension-drag-handle-vue-2 Major
@tiptap/extension-drag-handle-vue-3 Major
@tiptap/extension-drag-handle Major
@tiptap/extension-emoji Major
@tiptap/extension-file-handler Major
@tiptap/extension-floating-menu Major
@tiptap/extension-font-family Major
@tiptap/extension-hard-break Major
@tiptap/extension-heading Major
@tiptap/extension-highlight Major
@tiptap/extension-horizontal-rule Major
@tiptap/extension-image Major
@tiptap/extension-invisible-characters Major
@tiptap/extension-italic Major
@tiptap/extension-link Major
@tiptap/extension-list Major
@tiptap/extension-mathematics Major
@tiptap/extension-mention Major
@tiptap/extension-node-range Major
@tiptap/extension-ordered-list Major
@tiptap/extension-paragraph Major
@tiptap/extension-ruby-text Major
@tiptap/extension-strike Major
@tiptap/extension-subscript Major
@tiptap/extension-superscript Major
@tiptap/extension-table-of-contents Major
@tiptap/extension-table Major
@tiptap/extension-text-align Major
@tiptap/extension-text-style Major
@tiptap/extension-text Major
@tiptap/extension-twitch Major
@tiptap/extension-typography Major
@tiptap/extension-underline Major
@tiptap/extension-unique-id Major
@tiptap/extension-youtube Major
@tiptap/extensions Major
@tiptap/html Major
@tiptap/markdown Major
@tiptap/pm Major
@tiptap/react Major
@tiptap/starter-kit Major
@tiptap/static-renderer Major
@tiptap/suggestion Major
@tiptap/vue-2 Major
@tiptap/vue-3 Major
@tiptap/extension-character-count Major
@tiptap/extension-dropcursor Major
@tiptap/extension-focus Major
@tiptap/extension-gapcursor Major
@tiptap/extension-history Major
@tiptap/extension-list-item Major
@tiptap/extension-list-keymap Major
@tiptap/extension-placeholder Major
@tiptap/extension-table-cell Major
@tiptap/extension-table-header Major
@tiptap/extension-table-row Major
@tiptap/extension-task-item Major
@tiptap/extension-task-list Major
@tiptap/ai-toolkit Major
@tiptap/server-ai-toolkit Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Aslam97 Aslam97 changed the title Feat/find and replace capture groups feat(extension-find-and-replace): support capture groups in replacements Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary

  • Regex replacements now support complete matches and capture groups with $$, $&, $1$99, and $<name>.
  • Literal replacements remain unchanged when Regex mode is off.
  • Whole-word matching now works in Regex mode with Match case and Unicode-aware boundaries.
  • Regex replacements are calculated safely for each match.
  • Added regression tests for replacement tokens, capture groups, invalid patterns, whole-word matching, document structures, and performance.
  • Updated the React and Vue demos, documentation, and changeset.

Walkthrough

Regex-mode find-and-replace now supports capture-based substitutions for single and bulk replacements, Unicode-aware whole-word matching, and matcher-aware search. Tests and React/Vue demos document the updated behavior.

Changes

Regex replacement capture expansion

Layer / File(s) Summary
Matcher-aware search
packages/extension-find-and-replace/src/search/*
Search uses matcher context, selected capture groups, safe regex matching, and Unicode whole-word boundaries.
Replacement token engine
packages/extension-find-and-replace/src/utils/expandReplacement.ts, packages/extension-find-and-replace/src/utils/createResultReplacement.ts
Replacement templates support numbered, named, whole-match, and dollar tokens.
Per-result replacement resolution
packages/extension-find-and-replace/src/find-and-replace.ts, packages/extension-find-and-replace/src/utils/replace*.ts
Single and bulk replacements compute result-specific text and apply it through grouped replacement callbacks.
Validation and examples
packages/extension-find-and-replace/__tests__/*, demos/src/Extensions/FindAndReplace/*
Tests cover regex replacement, whole-word boundaries, invalid expressions, transaction behavior, and updated capture-group examples.
Documentation and release metadata
packages/extension-find-and-replace/src/types.ts, .changeset/moody-sheep-kiss.md
Public descriptions and the Changesets entry document regex-mode whole-word and replacement behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Editor
  participant FindAndReplace
  participant SearchMatcher
  participant ReplacementEngine
  participant ProseMirror
  Editor->>FindAndReplace: submit search and replacement terms
  FindAndReplace->>SearchMatcher: create matcher from search options
  SearchMatcher-->>FindAndReplace: return matches and capture-group metadata
  FindAndReplace->>ReplacementEngine: expand replacement for each result
  ReplacementEngine-->>FindAndReplace: return result-specific text
  FindAndReplace->>ProseMirror: apply grouped document replacements
Loading

Possibly related PRs

Suggested labels: area: core, area: docs, area: react, area: vue, complexity: medium

Suggested reviewers: bdbch

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: support for capture groups in regex replacements.
Description check ✅ Passed The description explains the main changes, verification steps, tests, changeset, documentation, and author responsibility.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added area: core Core editor engine and ProseMirror integration area: react React integration and components area: vue Vue integration and components complexity: hard Multiple components, research needed labels Jul 28, 2026

@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: 0956eef217

ℹ️ 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 thread .changeset/moody-sheep-kiss.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
.changeset/moody-sheep-kiss.md (1)

5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Worth one extra line about escaping.

Existing users with a literal $1 in their replace term will see different output in regex mode. Mentioning that $$ produces a literal $ saves a support round-trip.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/moody-sheep-kiss.md at line 5, Update the regex-mode changeset
description to document the escaping behavior: users can produce a literal
dollar sign with `$$`, including when replacement text would otherwise be
interpreted as a capture-group substitution.
packages/extension-find-and-replace/__tests__/find-and-replace.spec.ts (2)

624-641: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Guard the transaction listener against unrelated transactions.

transactions records every transaction after the listener is attached, so any future change that dispatches an extra transaction during replaceAll() (a re-search, a selection update) turns this into a confusing failure. Filtering to transactions that actually change the doc keeps the assertion focused on "one step".

♻️ Suggested tightening
     editor.on('transaction', ({ transaction }) => {
-      transactions.push(transaction.steps.length)
+      if (transaction.docChanged) {
+        transactions.push(transaction.steps.length)
+      }
     })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension-find-and-replace/__tests__/find-and-replace.spec.ts`
around lines 624 - 641, Update the transaction listener in the “expands many
regex captures with one indexed pass and one transaction step” test to record
only transactions that change the document, while preserving the existing
transaction.steps.length assertion for the replacement transaction.

358-380: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Nice coverage. Optional: pull the setup boilerplate into a helper.

Almost every new test repeats editor.destroy(); editor = createEditor(...) plus the same four command calls. Something like setupRegexEditor(html, { search, replace }) would make the intent of each test easier to spot for a newcomer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension-find-and-replace/__tests__/find-and-replace.spec.ts`
around lines 358 - 380, Extract the repeated regex test setup into a local
helper such as setupRegexEditor, accepting the initial HTML and search/replace
terms, and have it destroy the existing editor, create the new editor, enable
regex mode, and configure both terms. Update the affected tests, including
“expands capture groups per regex match when replacing all” and “reorders
multiple regex capture groups,” to use the helper while preserving their
assertions.
packages/extension-find-and-replace/src/find-and-replace.ts (1)

380-388: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Small duplication: same "regex or null" block appears in replaceResult too (Lines 190-192).

A tiny helper like replacementRegex(pluginState) would keep the two call sites in sync if the options ever change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension-find-and-replace/src/find-and-replace.ts` around lines 380
- 388, Extract the duplicated regex-or-null construction from replaceAllResults
and replaceResult into a shared helper such as replacementRegex(pluginState).
Update both call sites to use the helper, preserving the existing
createSearchRegex options and null behavior when useRegex is disabled.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/extension-find-and-replace/src/utils/createResultReplacement.ts`:
- Around line 37-52: Replace the WeakMap<Node, ReplacementContext> cache in the
result callback with a cache keyed by each textblock’s resolved start position,
such as $from.start() - 1. Update the lookup and insertion in the context
initialization path while preserving the existing createTextblockSearchContext
and replacement-expander behavior.

---

Nitpick comments:
In @.changeset/moody-sheep-kiss.md:
- Line 5: Update the regex-mode changeset description to document the escaping
behavior: users can produce a literal dollar sign with `$$`, including when
replacement text would otherwise be interpreted as a capture-group substitution.

In `@packages/extension-find-and-replace/__tests__/find-and-replace.spec.ts`:
- Around line 624-641: Update the transaction listener in the “expands many
regex captures with one indexed pass and one transaction step” test to record
only transactions that change the document, while preserving the existing
transaction.steps.length assertion for the replacement transaction.
- Around line 358-380: Extract the repeated regex test setup into a local helper
such as setupRegexEditor, accepting the initial HTML and search/replace terms,
and have it destroy the existing editor, create the new editor, enable regex
mode, and configure both terms. Update the affected tests, including “expands
capture groups per regex match when replacing all” and “reorders multiple regex
capture groups,” to use the helper while preserving their assertions.

In `@packages/extension-find-and-replace/src/find-and-replace.ts`:
- Around line 380-388: Extract the duplicated regex-or-null construction from
replaceAllResults and replaceResult into a shared helper such as
replacementRegex(pluginState). Update both call sites to use the helper,
preserving the existing createSearchRegex options and null behavior when
useRegex is disabled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: a2472ed0-7436-4424-9043-fccf159dd6f1

📥 Commits

Reviewing files that changed from the base of the PR and between 896564a and 0956eef.

📒 Files selected for processing (12)
  • .changeset/moody-sheep-kiss.md
  • demos/src/Extensions/FindAndReplace/React/index.jsx
  • demos/src/Extensions/FindAndReplace/Vue/index.vue
  • packages/extension-find-and-replace/__tests__/expandReplacement.spec.ts
  • packages/extension-find-and-replace/__tests__/find-and-replace.spec.ts
  • packages/extension-find-and-replace/src/find-and-replace.ts
  • packages/extension-find-and-replace/src/search/text-segments.ts
  • packages/extension-find-and-replace/src/search/textblock-search.ts
  • packages/extension-find-and-replace/src/utils/createResultReplacement.ts
  • packages/extension-find-and-replace/src/utils/expandReplacement.ts
  • packages/extension-find-and-replace/src/utils/replaceAllResults.ts
  • packages/extension-find-and-replace/src/utils/replaceGroup.ts

Comment thread packages/extension-find-and-replace/src/utils/createResultReplacement.ts Outdated
@coderabbitai coderabbitai Bot added complexity: medium Moderate change, possibly multiple files impact: low No direct user impact and removed complexity: hard Multiple components, research needed labels Jul 28, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 28, 2026
@Aslam97

Aslam97 commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added complexity: hard Multiple components, research needed impact: medium Affects some users or workflows and removed complexity: medium Moderate change, possibly multiple files impact: low No direct user impact labels Jul 30, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 30, 2026
@coderabbitai coderabbitai Bot removed the impact: medium Affects some users or workflows label Jul 30, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 30, 2026
bdbch
bdbch previously approved these changes Jul 31, 2026
Comment thread packages/extension-find-and-replace/src/search/find-safe-matcher-matches.ts Outdated
@Aslam97
Aslam97 dismissed stale reviews from bdbch and coderabbitai[bot] via 728ccc5 July 31, 2026 16:23
@coderabbitai coderabbitai Bot added area: docs Documentation and guides complexity: medium Moderate change, possibly multiple files and removed complexity: hard Multiple components, research needed area: editor Editor behavior, commands, and transactions impact: low No direct user impact labels Jul 31, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 31, 2026
@Aslam97
Aslam97 requested a review from bdbch July 31, 2026 18:28
@bdbch

bdbch commented Aug 10, 2026

Copy link
Copy Markdown
Member

@coderabbitai please do another review of the changes since your last review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

@bdbch I will review the changes added since the last review.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/extension-find-and-replace/src/utils/expandReplacement.ts (1)

31-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the Unicode advancement helper.

The Unicode code-point advancement logic is duplicated in packages/extension-find-and-replace/src/search/find-safe-matcher-matches.ts Lines 31-42. If the two implementations diverge, search results and replacement lookups can use different offsets. Extract one utility, such as packages/extension-find-and-replace/src/utils/advanceStringIndex.ts, and use it from both files. (raw.githubusercontent.com)

As per coding guidelines, TypeScript should use one reusable utility per file and name the file after its export.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension-find-and-replace/src/utils/expandReplacement.ts` around
lines 31 - 41, Extract advanceStringIndex into a dedicated utility file named
after the export, preserving its non-Unicode and UTF-16 code-point behavior.
Remove the duplicate implementation from find-safe-matcher-matches.ts and update
both that file and expandReplacement.ts to import and reuse the shared utility.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/moody-sheep-kiss.md:
- Around line 5-6: Update the changeset description to one short sentence
covering the complete regex replacement syntax, including capture groups such as
$1, the whole-match token $&, and the literal-dollar token $$; retain the
separate whole-word and match-case search behavior in that same sentence.

---

Nitpick comments:
In `@packages/extension-find-and-replace/src/utils/expandReplacement.ts`:
- Around line 31-41: Extract advanceStringIndex into a dedicated utility file
named after the export, preserving its non-Unicode and UTF-16 code-point
behavior. Remove the duplicate implementation from find-safe-matcher-matches.ts
and update both that file and expandReplacement.ts to import and reuse the
shared utility.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: bf33d7c8-6531-4142-a2fb-db21f5cf1a90

📥 Commits

Reviewing files that changed from the base of the PR and between 5508932 and 168338b.

📒 Files selected for processing (13)
  • .changeset/moody-sheep-kiss.md
  • demos/src/Extensions/FindAndReplace/React/index.jsx
  • demos/src/Extensions/FindAndReplace/Vue/index.vue
  • packages/extension-find-and-replace/__tests__/expandReplacement.spec.ts
  • packages/extension-find-and-replace/__tests__/find-and-replace.spec.ts
  • packages/extension-find-and-replace/src/find-and-replace.ts
  • packages/extension-find-and-replace/src/search/find-safe-matcher-matches.ts
  • packages/extension-find-and-replace/src/search/regex.ts
  • packages/extension-find-and-replace/src/search/search-matcher.ts
  • packages/extension-find-and-replace/src/search/unicode-word-character.ts
  • packages/extension-find-and-replace/src/types.ts
  • packages/extension-find-and-replace/src/utils/createResultReplacement.ts
  • packages/extension-find-and-replace/src/utils/expandReplacement.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/extension-find-and-replace/src/search/unicode-word-character.ts
  • packages/extension-find-and-replace/src/search/find-safe-matcher-matches.ts
  • packages/extension-find-and-replace/src/utils/createResultReplacement.ts
  • packages/extension-find-and-replace/src/types.ts
  • packages/extension-find-and-replace/src/search/regex.ts
  • packages/extension-find-and-replace/tests/expandReplacement.spec.ts
  • packages/extension-find-and-replace/tests/find-and-replace.spec.ts
  • packages/extension-find-and-replace/src/find-and-replace.ts
  • packages/extension-find-and-replace/src/search/search-matcher.ts

Comment thread .changeset/moody-sheep-kiss.md Outdated
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 10, 2026
…s and fix non-participating capture group handling
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: core Core editor engine and ProseMirror integration area: docs Documentation and guides area: react React integration and components area: vue Vue integration and components complexity: medium Moderate change, possibly multiple files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants