Skip to content

feat: implement gfm style codeblock - #607

Merged
eszlamczyk merged 12 commits into
mainfrom
feat/implement-gfm-blockcode
Jul 31, 2026
Merged

feat: implement gfm style codeblock#607
eszlamczyk merged 12 commits into
mainfrom
feat/implement-gfm-blockcode

Conversation

@eszlamczyk

@eszlamczyk eszlamczyk commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

What/Why?

Implements GFM-style rendering for fenced code blocks: with flavor: 'github', a code block is no longer rendered as spans inside the single text view but as a separate block segment view, using the same segment mechanism as tables and math, on Android, iOS, and macOS.

Block component

  • New CodeBlock segment kind in the AST segmentation on both platforms (splitASTIntoSegments / SegmentRenderer), reconciled by an FNV signature salted with the segment kind.
  • CodeBlockContainerView (Android) / ENRMCodeBlockContainerView (iOS/macOS) render the block with the markdownStyle.codeBlock style: background, border, padding, a header bar with the language display name ("python" becomes "Python") on the left and a copy-code button on the right, a hairline divider, and the wrapped code text below. Long-press still opens the Copy / Copy as Markdown context menu; the copy action is also exposed to VoiceOver as a custom accessibility action.
  • Long lines do not wrap: the code pane scrolls horizontally (same mechanism as wide tables) while the header stays fixed. The scroll indicator adapts to dark code block backgrounds without affecting other scroll views.
  • Measurement parity: the shadow-node measure path builds the same styled text and derives the header height from the same font metrics as the view, so reported and rendered heights always match.

Syntax highlighting scaffolding

  • Shared C++ seam at core/cpp/highlight/CodeBlockHighlighter.hpp returns semantic tokens (UTF-16 ranges plus a token type). Platform adapters (CodeBlockHighlighter.kt via JNI, ENRMCodeBlockHighlighter.mm) map token types to foreground colors only, so highlighting can never change text metrics.
  • The real implementation is compiled only when ENRICHED_MARKDOWN_CODE_HIGHLIGHT is defined; nothing defines it yet, so the stub returns no tokens and blocks render uncolored. Phase 2 vendors the tree-sitter runtime plus a curated grammar set behind a gradle property / podspec option (math-module style), replacing only the stub. The token palette is provisional until per-token colors land on the codeBlock style.

Testing

  • Storybook: Block -> Code Block story (now defaults to flavor: 'github' with a python fence); tune the markdownStyle.codeBlock controls.
  • Verified on the iOS and Android example apps: header layout, language label, copy button (copies raw code), divider, long-press menu (Copy / Copy as Markdown with fence rebuilt from fenceChar + language).
  • Builds: Android library Kotlin compile + ktlint, app CMake build for arm64 (JNI nativeHighlightCode and Markdown::highlightCode symbols verified in the .so), C++ stub compiled in both flag states, clang-format on all touched native files.
iOS Android
image image
Screen.Recording.2026-07-28.at.16.18.34.mov
Screen.Recording.2026-07-28.at.16.19.20.mov

PR Checklist

  • Code compiles and runs on iOS
  • Code compiles and runs on Android
  • Updated documentation/README if applicable
  • Ran example app to verify changes
  • E2E tests are passing
  • Required E2E tests have been added (if applicable)

Copilot AI 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.

Pull request overview

Adds GitHub-flavor (“gfm-style”) fenced code block rendering by promoting code blocks into their own block segments (instead of spans inside text segments), with shared scaffolding for future syntax highlighting.

Changes:

  • Introduces a new CodeBlock segment kind in AST segmentation/rendering on iOS/macOS and Android, including signature salting for reconciliation.
  • Implements platform code block container views (header with language label + copy button, divider, horizontally-scrollable code pane) and hooks them into measurement/layout and selection/copy labels propagation.
  • Adds a shared C++ highlighting seam (stubbed unless ENRICHED_MARKDOWN_CODE_HIGHLIGHT is enabled), with JNI/ObjC++ adapters and docs/story updates.

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/react-native-enriched-markdown/ReactNativeEnrichedMarkdown.podspec Includes new C++ highlight sources and header search paths for iOS build.
packages/react-native-enriched-markdown/ios/views/ENRMCodeBlockContainerView.m New iOS/macOS code block block-view implementation (header, copy, divider, horizontal scrolling, measuring).
packages/react-native-enriched-markdown/ios/views/ENRMCodeBlockContainerView.h Public interface for the new iOS/macOS code block container view.
packages/react-native-enriched-markdown/ios/utils/SegmentRenderer.m Splits AST into segments including new code block segments; salts signatures for reconciliation.
packages/react-native-enriched-markdown/ios/utils/RenderedMarkdownSegment.m Adds ENRMCodeBlockSegment and rendered-segment factory for code blocks.
packages/react-native-enriched-markdown/ios/utils/RenderedMarkdownSegment.h Extends segment kinds/types to include code block segments.
packages/react-native-enriched-markdown/ios/utils/ENRMCodeBlockHighlighter.mm ObjC++ adapter mapping C++ highlight tokens to NSAttributedString foreground colors.
packages/react-native-enriched-markdown/ios/utils/ENRMCodeBlockHighlighter.h Declares platform adapter API for optional code highlighting.
packages/react-native-enriched-markdown/ios/EnrichedMarkdown.mm Registers/creates/updates code block segment views; includes layout + copy-label propagation.
packages/react-native-enriched-markdown/android/src/main/jni/CMakeLists.txt Adds highlight C++ sources/includes to Android JNI build.
packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt New Android code block block-view implementation (header, copy, divider, horizontal scrolling, measuring).
packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/utils/common/SegmentSignature.kt Adds code block segment kind salt.
packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/utils/common/RenderedSegment.kt Adds rendered segment type for code blocks and signature computation.
packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownSegment.kt Adds code block segment type and segmentation logic.
packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/utils/common/CodeBlockHighlighter.kt Android adapter over native highlighter returning semantic tokens.
packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt Adds measurement support for code block segments.
packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt Integrates code block segments into view creation/updating and label propagation.
packages/react-native-enriched-markdown/android/src/main/cpp/jni-adapter.cpp Exposes JNI nativeHighlightCode to return flattened token triplets.
packages/core/EnrichedMarkdownCore.podspec Includes highlight sources/headers in core pod.
packages/core/cpp/highlight/CodeBlockHighlighter.hpp Defines shared highlighting seam contract (token types, UTF-16 offsets).
packages/core/cpp/highlight/CodeBlockHighlighter.cpp Provides stub implementation when highlighting module is not enabled.
docs/STYLES.md Documents GitHub-flavor code block header behavior and how it derives from code block style.
docs/ELEMENTS_STRUCTURE.md Updates element structure notes for GitHub-flavor block-style code blocks.
docs/COPY_OPTIONS.md Notes that copy labels apply to code block menus and the header button accessibility label.
docs/API_REFERENCE.md Expands flavor="github" docs to include block-style code blocks (and behavior differences).
apps/react-native-example/.rnstorybook/stories/components/EnrichedMarkdownText/block/CodeBlock.stories.tsx Updates story to default to GitHub flavor and demonstrates fenced code block with language.
Comments suppressed due to low confidence (3)

packages/react-native-enriched-markdown/ios/views/ENRMCodeBlockContainerView.m:503

  • fencedMarkdown always uses a 3-character fence. This can generate invalid Markdown when the code itself contains a run of the same fence character (e.g., ``` inside the code), because the closing fence would prematurely terminate the block. Compute the fence length as max(3, longestRunInCode + 1) instead.
- (NSString *)fencedMarkdown
{
  NSString *fence = [@"" stringByPaddingToLength:3 withString:_fenceChar startingAtIndex:0];
  return [NSString stringWithFormat:@"%@%@\n%@\n%@", fence, _cachedLanguage ?: @"", _cachedCode, fence];
}

packages/react-native-enriched-markdown/ios/views/ENRMCodeBlockContainerView.m:479

  • The header/code divider is hard-coded to 1 point tall. On high-density screens this is thicker than a hairline and won’t match the stated “hairline divider” design. Use a 1-pixel height (1.0 / screenScale) instead.
  RCTUIColor *dividerColor = [[_config codeBlockColor] colorWithAlphaComponent:kENRMHeaderDividerAlpha];
  if (dividerColor) {
    CGRect dividerRect = CGRectMake(borderWidth, headerH + inset / 2, self.bounds.size.width - borderWidth * 2, 1);
    [dividerColor setFill];

packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt:261

  • buildFencedMarkdown() always uses a 3-character fence. If the code contains a run of the same fence character (e.g. ``` inside the code), the copied markdown can become invalid because the closing fence may appear inside the payload. Compute the fence length as max(3, longestRunInCode + 1) instead.
  private fun buildFencedMarkdown(): String {
    val fence = fenceChar.repeat(3)
    return "$fence${language.orEmpty()}\n$code\n$fence"
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/react-native-enriched-markdown/ios/utils/ENRMCodeBlockHighlighter.mm Outdated
@eszlamczyk
eszlamczyk requested a review from hryhoriiK97 July 29, 2026 08:40
@eszlamczyk
eszlamczyk marked this pull request as ready for review July 29, 2026 08:44
@eszlamczyk
eszlamczyk merged commit 07d3855 into main Jul 31, 2026
9 checks passed
@eszlamczyk
eszlamczyk deleted the feat/implement-gfm-blockcode branch July 31, 2026 07:45
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.

3 participants