9.2 KiB
Code & Diff Components
Components for source code, line-number gutters, unified diffs, Markdown, and text tables.
Code Component
CodeRenderable displays plain text immediately and applies asynchronous
Tree-sitter highlighting when filetype and a parser are available.
Basic Usage
// React / Solid
<code content={sourceCode} filetype="typescript" syntaxStyle={syntaxStyle} />
// Core
const code = new CodeRenderable(renderer, {
content: sourceCode,
filetype: "typescript",
syntaxStyle,
wrapMode: "none", // "none" | "char" | "word"
})
OpenTUI bundles parsers for JavaScript/JSX, TypeScript/TSX, Markdown,
Markdown-inline, and Zig. Other grammars require Tree-sitter asset
configuration. Without filetype, Code renders unhighlighted text.
Highlight Hooks
onHighlight can replace the syntax ranges before styling. It receives
SimpleHighlight[] tuples and { content, filetype, syntaxStyle }; return an
array or undefined, synchronously or asynchronously.
<code
content={sourceCode}
filetype="typescript"
syntaxStyle={syntaxStyle}
onHighlight={(highlights, context) =>
highlights.filter((highlight) => highlight[2] !== "comment")
}
/>
onChunks runs afterward and can replace the resolved TextChunk[]. Its
context also includes highlights.
import { detectLinks } from "@opentui/core"
<code
content={markdown}
filetype="markdown"
syntaxStyle={syntaxStyle}
onChunks={(chunks, context) => detectLinks(chunks, context)}
/>
detectLinks applies links for recognized Markdown/URL highlight scopes.
TextTable Component
TextTableRenderable is Core-only. It displays styled chunk cells with borders,
wrapping, width fitting, and selection.
import {
TextTableRenderable,
bold,
fg,
type TextChunk,
type TextTableContent,
} from "@opentui/core"
const cell = (text: string): TextChunk[] => [{ __isChunk: true, text }]
const content: TextTableContent = [
[[bold("Service")], [bold("Status")], [bold("Notes")]],
[cell("api"), [fg("#00d4aa")("OK")], cell("latency 28ms")],
]
const table = new TextTableRenderable(renderer, {
content,
wrapMode: "word",
columnWidthMode: "full", // "content" | "full"
columnFitter: "balanced", // "proportional" | "balanced"
cellPadding: 1,
border: true,
outerBorder: true,
borderStyle: "rounded",
selectable: true,
})
| Option | Type | Default | Description |
|---|---|---|---|
content |
TextTableContent |
- | Rows of styled chunk cells |
wrapMode |
none | char | word |
none |
Cell wrapping |
columnWidthMode |
content | full |
full |
Natural or available-width sizing |
columnFitter |
proportional | balanced |
proportional |
Distribute constrained width |
cellPadding |
number |
0 |
Horizontal cell padding |
border, outerBorder |
boolean |
true |
Inner and outer borders |
borderStyle |
single | double | rounded | heavy |
single |
Border glyph set |
borderColor |
ColorInput |
- | Border color |
selectable |
boolean |
false |
Participate in text selection |
TextTableCellContent is TextChunk[] | null | undefined. Each literal chunk
needs __isChunk: true; styled-text helpers such as bold() and fg() already
return valid chunks. getSelectedText() and hasSelection() expose selection;
vertical drags within one column retain columnar selection.
Line Number Component
LineNumberRenderable is a gutter for another renderable that implements
LineInfoProvider; it does not accept source code itself.
// React (use <line_number> in Solid)
<line-number
ref={lineNumbersRef}
fg="#6b7280"
bg="#161b22"
minWidth={3}
paddingRight={1}
lineNumberOffset={0}
>
<code content={sourceCode} filetype="typescript" syntaxStyle={syntaxStyle} />
</line-number>
// Core
const code = new CodeRenderable(renderer, {
content: sourceCode,
filetype: "typescript",
syntaxStyle,
})
const lineNumbers = new LineNumberRenderable(renderer, {
target: code,
minWidth: 3,
paddingRight: 1,
})
Use methods rather than nonexistent diagnostics, addedLines,
removedLines, or highlightedLines props:
lineNumbers.setLineColor(4, "#1a4d1a")
lineNumbers.setLineSign(4, { after: " +", afterColor: "#22c55e" })
lineNumbers.highlightLines(9, 11, "#4d1a1a")
lineNumbers.clearHighlightLines(9, 11)
Other methods include clearLineColor(), setLineColors(),
clearAllLineColors(), clearLineSign(), setLineSigns(), and
clearAllLineSigns(). lineNumberOffset changes displayed numbering.
Diff Component
DiffRenderable accepts a unified diff string. It does not compute a diff from
old and new source strings.
// React / Solid
<diff
diff={unifiedPatch}
filetype="typescript"
syntaxStyle={syntaxStyle}
view="split"
syncScroll
showLineNumbers
/>
// Core
const diff = new DiffRenderable(renderer, {
diff: unifiedPatch,
filetype: "typescript",
syntaxStyle,
view: "unified",
})
Options
| Option | Type / Default | Description |
|---|---|---|
diff |
string |
Unified patch input |
view |
unified | split / unified |
Display mode |
syncScroll |
boolean / false |
Keep split panes aligned |
filetype |
string |
Tree-sitter language |
syntaxStyle |
SyntaxStyle |
Highlight style |
wrapMode |
word | char | none |
Source wrapping |
conceal |
boolean / false |
Conceal syntax tokens |
showLineNumbers |
boolean / true |
Show line-number gutters |
addedBg, removedBg, contextBg |
ColorInput |
Whole-line backgrounds |
addedContentBg, removedContentBg, contextContentBg |
ColorInput |
Changed-content backgrounds |
addedLineNumberBg, removedLineNumberBg, lineNumberBg |
ColorInput |
Gutter backgrounds |
addedSignColor, removedSignColor |
ColorInput |
+ and - colors |
Use view, not mode; use addedBg/removedBg/contextBg, not
addedLineColor/removedLineColor/unchangedLineColor. Context lines are
already encoded in the patch, so there is no context option. For multi-file
input, Diff currently displays only the first parsed file patch.
Programmatic Line Highlighting
diff.setLineColor(10, "#FFFF0030")
diff.clearLineColor(10)
diff.setLineColors(new Map([
[5, "#FF000030"],
[10, { bg: "#00FF0030", fg: "#FFFFFF" }],
]))
diff.highlightLines(20, 25, "#0000FF30")
diff.clearHighlightLines(20, 25)
diff.clearAllLineColors()
getHunkRowOffsets() returns display-row offsets for parsed hunks. Re-read it
after changing the patch, view, wrapping, or dimensions.
Markdown Component
MarkdownRenderable parses Markdown into styled renderables. Pass a
SyntaxStyle for fenced code highlighting.
<markdown
content={markdownText}
syntaxStyle={syntaxStyle}
conceal
concealCode={false}
streaming={false}
internalBlockMode="coalesced"
/>
| Option | Type | Default | Description |
|---|---|---|---|
content |
string |
"" |
Markdown source |
syntaxStyle |
SyntaxStyle |
- | Syntax colors |
treeSitterClient |
TreeSitterClient |
shared | Parser client |
conceal |
boolean |
true |
Hide Markdown markers |
concealCode |
boolean |
false |
Hide fenced-code markers |
streaming |
boolean |
false |
Optimize append-only content |
internalBlockMode |
coalesced | top-level |
coalesced |
Internal block grouping |
tableOptions |
MarkdownTableOptions |
- | Table layout and border options |
Custom Node Rendering
renderNode receives (token, context). Return a custom renderable,
context.defaultRender() for the built-in representation, or null/
undefined as appropriate.
const markdown = new MarkdownRenderable(renderer, {
content: "# Custom Heading",
syntaxStyle,
renderNode(token, context) {
if (token.type === "heading") {
return new TextRenderable(renderer, { content: `>> ${token.text} <<` })
}
return context.defaultRender()
},
})
For fenced-code specialization, use createMarkdownCodeBlockRenderer() to
dispatch normalized filetypes to custom renderers while retaining the default
renderer for unmatched tokens.
Streaming Markdown
<markdown
content={streamedContent}
syntaxStyle={syntaxStyle}
streaming={isStreaming}
internalBlockMode="top-level"
/>
Keep streaming true while appending and set it false when complete so the
final parse can settle. top-level mode exposes stable top-level blocks, which
is useful for LLM output and incremental views.
Gotchas
- React uses
<line-number>; Solid uses<line_number>. - Code uses
contentandfiletype, notcodeandlanguage. - Diff uses one unified
diffstring andview, not old/new strings andmode. - Line Number wraps a target; it does not render source code by itself.
- Tree-sitter loading is asynchronous. Use
OTUI_TREE_SITTER_WORKER_PATHwhen packaging requires a custom worker path. - Put large Code/Line Number views inside a height-constrained ScrollBox.
See Also
- Text & Display - Styled text and image rendering
- Containers - ScrollBox for large content
- Core API - Imperative renderables
- Testing - Frame and snapshot tests