Skip to content

fix: insert multiline code block as a fenced block on its own lines#7424

Open
Shevilll wants to merge 2 commits into
RocketChat:developfrom
Shevilll:fix/composer-code-block-markdown
Open

fix: insert multiline code block as a fenced block on its own lines#7424
Shevilll wants to merge 2 commits into
RocketChat:developfrom
Shevilll:fix/composer-code-block-markdown

Conversation

@Shevilll

@Shevilll Shevilll commented Jun 20, 2026

Copy link
Copy Markdown

Proposed changes

Tapping the multiline code-block formatting button produced a broken result: it wrapped the selection inline, e.g. typing nothing and tapping the button inserted six backticks with the cursor stuck in the middle of them on a single line (```|```). Fenced code blocks are only parsed by the renderer when the ``` markers sit alone on their own line, so this never actually rendered as a code block and forced users to manually add line breaks.

This makes the code-block button insert a proper fenced block:

  • the opening and closing ``` fences are placed on their own lines;
  • a leading newline is added when the cursor isn't already at the start of a line (so the opening fence isn't glued to preceding text), and a trailing newline when needed so the closing fence stays alone on its line;
  • the cursor is left on the (possibly empty) content line between the fences, ready to type/paste code;
  • any selected text is wrapped inside the block.

So tapping the button now yields:

​```
|
​```

(with the cursor | on the middle line), matching the behaviour requested in the issue. The other formatting buttons (bold, italic, strike, inline code) are unchanged.

The insertion logic is extracted into a pure wrapCodeBlock helper (mirroring the existing insertEmojiAtCursor helper) so it can be unit-tested directly.

Issue(s)

Closes #7099

How to test or reproduce

  1. Open the message composer, optionally type some text and/or select text.
  2. Open the formatting toolbar and tap the code block button.
  3. The selection (or an empty line) is now wrapped in a ``` fenced block on its own lines, with the cursor on the content line — instead of six backticks on a single line.

Automated: TZ=UTC pnpm test --testPathPattern='wrapCodeBlock|MessageComposer/MessageComposer.test' (new wrapCodeBlock unit tests + updated MessageComposer toolbar tests, 39 tests pass). pnpm lint (ESLint + tsc) passes.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)

Checklist

  • I have read the Contributing Guide
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective

Summary by CodeRabbit

  • Bug Fixes
    • Improved code-block formatting in the message composer so fenced markdown uses newline-separated triple-backtick delimiters.
    • Enhanced insertion behavior to better handle cursor/selection placement when adding code blocks, including empty blocks and existing-line newline edge cases.
  • Tests
    • Added coverage for code-block wrapping across common cursor and selection scenarios, and updated related expectations to match the new serialization format.

@CLAassistant

CLAassistant commented Jun 20, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9df87054-3854-4a7b-98ab-d0b26a495989

📥 Commits

Reviewing files that changed from the base of the PR and between 763fedd and d0cdcad.

📒 Files selected for processing (5)
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
  • app/containers/MessageComposer/helpers/index.ts
  • app/containers/MessageComposer/helpers/wrapCodeBlock.test.ts
  • app/containers/MessageComposer/helpers/wrapCodeBlock.ts
✅ Files skipped from review due to trivial changes (1)
  • app/containers/MessageComposer/helpers/index.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/containers/MessageComposer/helpers/wrapCodeBlock.test.ts
  • app/containers/MessageComposer/helpers/wrapCodeBlock.ts
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx

Walkthrough

Introduces a wrapCodeBlock helper that wraps selected text (or cursor position) in newline-separated triple-backtick fenced code block markers with correct selection offset recalculation. The helper is exported through the helpers barrel and integrated as a dedicated branch in ComposerInput's addMarkdown handler. Tests are updated to match the new fence format.

Changes

wrapCodeBlock helper and composer integration

Layer / File(s) Summary
wrapCodeBlock helper and unit tests
app/containers/MessageComposer/helpers/wrapCodeBlock.ts, app/containers/MessageComposer/helpers/wrapCodeBlock.test.ts, app/containers/MessageComposer/helpers/index.ts
Adds wrapCodeBlock, which conditionally prepends a newline, wraps content in triple-backtick fences with newline separators, and returns updated text plus recalculated selection offsets. Exported via barrel. Five unit tests cover empty cursor, leading-newline conditioning, selected-text wrapping, selection preservation, and trailing-newline fence placement.
ComposerInput code-block branch and integration tests
app/containers/MessageComposer/components/ComposerInput.tsx, app/containers/MessageComposer/MessageComposer.test.tsx
Imports wrapCodeBlock and adds an early-return branch in the addMarkdown handler for code-block style, delegating to wrapCodeBlock and applying the result via setInput. Integration test assertions updated from compact inline fences to the new newline-separated fence format.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing how multiline code blocks are inserted with proper fenced formatting on separate lines.
Linked Issues check ✅ Passed The code changes fully address the requirements from issue #7099: properly formatted fenced code blocks with opening/closing markers on dedicated lines, leading newline handling, and cursor positioning on the content line.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the code-block formatting issue: new wrapCodeBlock helper, updated ComposerInput to use it, tests covering the new functionality, and updated existing tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


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.

@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: 2

🤖 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 `@app/containers/MessageComposer/helpers/wrapCodeBlock.ts`:
- Around line 7-9: Replace all three instances of the deprecated substr() method
in the wrapCodeBlock.ts file with slice(). Change text.substr(0, start) to
text.slice(0, start) for the before variable, text.substr(start, end - start) to
text.slice(start, end) for the selected variable, and text.substr(end) to
text.slice(end) for the after variable. Note that slice() takes an end index
rather than a length parameter, so adjust the second argument accordingly (in
the selected case, the length end - start becomes the end index end).
- Around line 6-21: The wrapCodeBlock function is missing an explicit return
type annotation and the returned object shape is not defined as an interface.
Create a new interface that defines the return type structure with updatedText
as a string property and selection as an object with start and end number
properties. Then add this interface as the explicit return type annotation to
the wrapCodeBlock function signature.
🪄 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: 7703a01b-9b74-4ede-bdd5-d021e519aa44

📥 Commits

Reviewing files that changed from the base of the PR and between 6bbf88a and 763fedd.

📒 Files selected for processing (5)
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/components/ComposerInput.tsx
  • app/containers/MessageComposer/helpers/index.ts
  • app/containers/MessageComposer/helpers/wrapCodeBlock.test.ts
  • app/containers/MessageComposer/helpers/wrapCodeBlock.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/containers/MessageComposer/helpers/index.ts
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/helpers/wrapCodeBlock.test.ts
  • app/containers/MessageComposer/helpers/wrapCodeBlock.ts
  • app/containers/MessageComposer/components/ComposerInput.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode enabled

Files:

  • app/containers/MessageComposer/helpers/index.ts
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/helpers/wrapCodeBlock.test.ts
  • app/containers/MessageComposer/helpers/wrapCodeBlock.ts
  • app/containers/MessageComposer/components/ComposerInput.tsx
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses

Files:

  • app/containers/MessageComposer/helpers/index.ts
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/helpers/wrapCodeBlock.test.ts
  • app/containers/MessageComposer/helpers/wrapCodeBlock.ts
  • app/containers/MessageComposer/components/ComposerInput.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Enforce ESLint rules from @rocket.chat/eslint-config with React, React Native, TypeScript, and Jest plugins

Files:

  • app/containers/MessageComposer/helpers/index.ts
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/helpers/wrapCodeBlock.test.ts
  • app/containers/MessageComposer/helpers/wrapCodeBlock.ts
  • app/containers/MessageComposer/components/ComposerInput.tsx
app/containers/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place reusable UI components in 'app/containers/' directory

Files:

  • app/containers/MessageComposer/helpers/index.ts
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/helpers/wrapCodeBlock.test.ts
  • app/containers/MessageComposer/helpers/wrapCodeBlock.ts
  • app/containers/MessageComposer/components/ComposerInput.tsx
🧠 Learnings (1)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/containers/MessageComposer/helpers/index.ts
  • app/containers/MessageComposer/MessageComposer.test.tsx
  • app/containers/MessageComposer/helpers/wrapCodeBlock.test.ts
  • app/containers/MessageComposer/helpers/wrapCodeBlock.ts
  • app/containers/MessageComposer/components/ComposerInput.tsx
🔇 Additional comments (4)
app/containers/MessageComposer/helpers/index.ts (1)

6-6: LGTM!

app/containers/MessageComposer/helpers/wrapCodeBlock.test.ts (1)

1-38: LGTM!

app/containers/MessageComposer/components/ComposerInput.tsx (1)

17-17: LGTM!

Also applies to: 134-138

app/containers/MessageComposer/MessageComposer.test.tsx (1)

406-406: LGTM!

Also applies to: 425-425

Comment thread app/containers/MessageComposer/helpers/wrapCodeBlock.ts Outdated
Comment thread app/containers/MessageComposer/helpers/wrapCodeBlock.ts Outdated
The code-block formatting button wrapped the selection inline, but fenced code blocks are only parsed when the ``` markers are alone on a line. Tapping the button therefore produced six backticks around the cursor on a single line, which does not render as a code block.

Insert the opening and closing fences on their own lines instead, adding a leading newline when the cursor is not already at the start of a line, and leave the cursor on the (possibly empty) content line between the fences. Any selected text is wrapped inside the block.

The logic is extracted into a pure wrapCodeBlock helper with unit tests.

Closes RocketChat#7099
@Shevilll Shevilll force-pushed the fix/composer-code-block-markdown branch from 763fedd to 8e1d43e Compare June 20, 2026 08:18
Define ISelectionRange and IWrapCodeBlockResult for the helper's return
shape per the repo's TypeScript interface conventions.
@Shevilll

Copy link
Copy Markdown
Author

Hi team! I've updated the wrapCodeBlock helper to declare strict TypeScript return interfaces (IWrapCodeBlockResult) per the review suggestions. All Jest tests are passing successfully. Please let me know if this is good to merge! CC @diegolmello

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

multiline code markdown button in ios is erratic

2 participants