Skip to content

fix(core): trigger codeblock input rule on Enter and place cursor inside#2686

Open
nperez0111 wants to merge 3 commits intomainfrom
feat/fix-codeblock-input-rule
Open

fix(core): trigger codeblock input rule on Enter and place cursor inside#2686
nperez0111 wants to merge 3 commits intomainfrom
feat/fix-codeblock-input-rule

Conversation

@nperez0111
Copy link
Copy Markdown
Contributor

@nperez0111 nperez0111 commented Apr 29, 2026

Summary

Fixes the broken \``` codeblock input rule, then extends it so pressing Enter (in addition to space) triggers the same conversion, and lands the cursor inside the new code block instead of after it.

Rationale

The space-trigger input rule had been silently broken: the handler called state.tr twice (creating two separate transactions) and read block.type from editor.getTextCursorPosition() which uses the editor's stale pre-insertion state, so language detection was off and the conversion path was unreliable. Beyond fixing that, users expected Enter to confirm the language and convert — the standard Tiptap codeblock UX — but BlockNote only fired on space. And after conversion the cursor landed after the new block, so the next keystroke went into the wrong block.

Changes

  • Fixed the input rule handler in ExtensionManager to use a single state.tr, derive block info from the post-insertion transaction via getBlockInfoFromTransaction(tr), and guard on isBlockContainer and inline content.
  • Added a sidecar Plugin next to inputRulesPlugin({ rules }) whose handleKeyDown intercepts Enter and delegates to the inputRules plugin's handleTextInput with text: "\n". The handlewithcare regex \s$ already matches \n, so any rule fires for both space and Enter through one path. Bypasses view.someProp so other plugins don't observe the synthetic \n, and keeps undo metadata keyed to the same plugin instance Tiptap's commands.undoInputRule reads from.
  • Cursor placement after conversion: after updateBlockTr, call setTextCursorPosition(tr, blockId, "start") so any rule whose replace clears content lands the caret inside the new block.
  • packages/core/src/blocks/Code/block.test.ts (new) — 19 tests covering both space and Enter triggers, getLanguageId alias resolution, negative cases (no trailing whitespace, two backticks, preceding text, in-codeblock retrigger), and cursor placement after each conversion path.

Impact

The Enter trigger applies to every input rule registered through ExtensionManager, so any \s$-anchored rule gets it for free — Divider's --- rule now also fires on Enter, and any future block input rule benefits without per-block code. No public API change.

Testing

  • New block.test.ts with 19 tests; all 306 unit tests in @blocknote/core pass.
  • pnpm --filter @blocknote/core build passes (TypeScript + Vite).

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests

    • Added end-to-end tests simulating typing and shortcuts to validate code block creation, language detection/resolution, edge cases, empty content handling, and caret placement inside new blocks.
  • Bug Fixes

    • Improved Enter/keyboard handling to reliably trigger block conversion, tightened conditions for conversion, ensured cursor is repositioned inside new blocks after replacement, and made input-rule conversions undoable.

Supercedes #2345

Fix the codeblock input rule (```ts + space) which had been broken by a
two-transaction handler that read stale block info, then extend it so
Enter triggers the same rule via a sidecar plugin in ExtensionManager.
After conversion, place the cursor inside the new block instead of
leaving it after.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview May 4, 2026 0:53am
blocknote-website Ready Ready Preview May 4, 2026 0:53am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

Adds Vitest/JSDOM tests for Code block conversion from triple-backticks and language IDs, and modifies ExtensionManager input-rules to handle Enter keydown, compute block context from transactions, reposition the cursor after replacements, and make input-rule replacements undoable.

Changes

Cohort / File(s) Summary
Code Block Tests
packages/core/src/blocks/Code/block.test.ts
New Vitest/JSDOM test (258 lines) that simulates typing/backtick sequences and Enter to assert conversion to codeBlock, language resolution (aliases, empty string), cursor placement inside new block, edge cases, and getLanguageId behavior.
ExtensionManager Plugin
packages/core/src/editor/managers/ExtensionManager/index.ts
Input-rules setup now returns original plugin plus an Enter keydown plugin that calls handleTextInput with a synthetic newline; per-InputRule replacement logic uses getBlockInfoFromTransaction(tr), tightens eligibility to inline content, repositions selection into created blocks via setTextCursorPosition when needed, and marks replacements as undoable.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant View as ProseMirror View
    participant KeyHandler as Enter Key Handler
    participant InputRules as InputRules Plugin
    participant RuleHandler as InputRule Handler
    participant Doc as Document/Transaction

    User->>View: Press Enter after typing ```lang
    View->>KeyHandler: handleKeyDown(Enter)
    KeyHandler->>InputRules: invoke handleTextInput("\\n") (synthetic)
    InputRules->>InputRules: match input rules
    InputRules->>RuleHandler: execute matched rule (replacement)
    RuleHandler->>Doc: getBlockInfoFromTransaction(tr)
    Doc-->>RuleHandler: block/container context
    RuleHandler->>Doc: replace content with codeBlock
    RuleHandler->>View: setTextCursorPosition inside new block
    View-->>User: render new code block with cursor
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • matthewlipski

Poem

🐇 I hopped on keys, three ticks in a row,

Enter nudged, and code began to grow.
A cursor nestled soft inside the block,
Languages whispered from alias stock.
Hooray — the editor hums; I twitch my nose! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 and specifically describes the main fix: triggering the codeblock input rule on Enter and placing the cursor inside the resulting block.
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.
Description check ✅ Passed The PR description comprehensively follows the template with all major sections filled: Summary clearly states the fix, Rationale explains the broken behavior and user expectations, Changes detail the implementation approach with specifics, Impact discusses scope, and Testing confirms passing tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/fix-codeblock-input-rule

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 and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 29, 2026

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/@blocknote/ariakit@2686

@blocknote/code-block

npm i https://pkg.pr.new/@blocknote/code-block@2686

@blocknote/core

npm i https://pkg.pr.new/@blocknote/core@2686

@blocknote/mantine

npm i https://pkg.pr.new/@blocknote/mantine@2686

@blocknote/react

npm i https://pkg.pr.new/@blocknote/react@2686

@blocknote/server-util

npm i https://pkg.pr.new/@blocknote/server-util@2686

@blocknote/shadcn

npm i https://pkg.pr.new/@blocknote/shadcn@2686

@blocknote/xl-ai

npm i https://pkg.pr.new/@blocknote/xl-ai@2686

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/@blocknote/xl-docx-exporter@2686

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/@blocknote/xl-email-exporter@2686

@blocknote/xl-multi-column

npm i https://pkg.pr.new/@blocknote/xl-multi-column@2686

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/@blocknote/xl-odt-exporter@2686

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/@blocknote/xl-pdf-exporter@2686

commit: ec7918e

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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/core/src/blocks/Code/block.test.ts (1)

37-40: pressKey skips the default Enter path.

view.someProp("handleKeyDown", ...) only invokes plugin handlers. When every handler returns false, this helper does nothing, so the negative Enter tests still pass even if normal Enter behavior is broken or swallowed. Please capture the handled result and simulate/assert the fallback behavior too.

One way to tighten the helper
 function pressKey(editor: BlockNoteEditor, key: string) {
   const view = editor.prosemirrorView;
   const event = new KeyboardEvent("keydown", { key });
-  view.someProp("handleKeyDown", (f) => f(view, event));
+  const handled = view.someProp("handleKeyDown", (f) => f(view, event));
+  return !!handled;
 }

Then assert pressKey(...) is true for conversion cases, and add an explicit fallback-behavior assertion for non-conversion cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/blocks/Code/block.test.ts` around lines 37 - 40, The helper
pressKey currently calls view.someProp("handleKeyDown", ...) but ignores the
boolean return (so it never exercises the editor's default Enter fallback);
update pressKey (used in the Code block tests) to capture the boolean handled
result from someProp("handleKeyDown") and, if handlers return false,
simulate/assert the editor's default Enter behavior (i.e., the fallback path
that ProseMirror would run when unhandled). Return the handled boolean from
pressKey so tests can assert pressKey(...) === true for conversion cases and add
explicit assertions that fallback behavior occurred when pressKey(...) === false
for non-conversion cases; reference pressKey, BlockNoteEditor, and
view.someProp("handleKeyDown") to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/editor/managers/ExtensionManager/index.ts`:
- Around line 384-400: The Enter-handling branch in handleKeyDown currently
treats modified Enter keys the same as plain Enter; update handleKeyDown(view,
event) to first return false when any modifier is pressed (event.shiftKey ||
event.ctrlKey || event.metaKey || event.altKey) so the branch only runs for an
unmodified Enter, then continue calling
inputRules.props.handleTextInput?.call(...) as before; reference the
handleKeyDown function, the event object, and inputRules.props.handleTextInput
to locate and change the check.

---

Nitpick comments:
In `@packages/core/src/blocks/Code/block.test.ts`:
- Around line 37-40: The helper pressKey currently calls
view.someProp("handleKeyDown", ...) but ignores the boolean return (so it never
exercises the editor's default Enter fallback); update pressKey (used in the
Code block tests) to capture the boolean handled result from
someProp("handleKeyDown") and, if handlers return false, simulate/assert the
editor's default Enter behavior (i.e., the fallback path that ProseMirror would
run when unhandled). Return the handled boolean from pressKey so tests can
assert pressKey(...) === true for conversion cases and add explicit assertions
that fallback behavior occurred when pressKey(...) === false for non-conversion
cases; reference pressKey, BlockNoteEditor, and view.someProp("handleKeyDown")
to locate the change.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dbaa54a9-e0d7-4b4f-9d88-994a4d92f193

📥 Commits

Reviewing files that changed from the base of the PR and between 6dbd6eb and 79614ef.

📒 Files selected for processing (2)
  • packages/core/src/blocks/Code/block.test.ts
  • packages/core/src/editor/managers/ExtensionManager/index.ts

Comment thread packages/core/src/editor/managers/ExtensionManager/index.ts
… combos

Skip the sidecar's input-rule trigger when Shift/Ctrl/Meta/Alt+Enter
is pressed so soft-break, submit, and similar handlers can run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants