feat: implement gfm style codeblock - #607
Merged
Merged
Conversation
There was a problem hiding this comment.
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
CodeBlocksegment 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_HIGHLIGHTis 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
fencedMarkdownalways 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 asmax(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
1point 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 asmax(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.
eszlamczyk
marked this pull request as ready for review
July 29, 2026 08:44
hryhoriiK97
approved these changes
Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
CodeBlocksegment 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 themarkdownStyle.codeBlockstyle: 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.Syntax highlighting scaffolding
core/cpp/highlight/CodeBlockHighlighter.hppreturns semantic tokens (UTF-16 ranges plus a token type). Platform adapters (CodeBlockHighlighter.ktvia JNI,ENRMCodeBlockHighlighter.mm) map token types to foreground colors only, so highlighting can never change text metrics.ENRICHED_MARKDOWN_CODE_HIGHLIGHTis 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
flavor: 'github'with apythonfence); tune themarkdownStyle.codeBlockcontrols.fenceChar+language).nativeHighlightCodeandMarkdown::highlightCodesymbols verified in the.so), C++ stub compiled in both flag states, clang-format on all touched native files.Screen.Recording.2026-07-28.at.16.18.34.mov
Screen.Recording.2026-07-28.at.16.19.20.mov
PR Checklist