13 KiB
Code & Diff Components
Components for displaying code with syntax highlighting and diffs in OpenTUI.
Code Component
Display syntax-highlighted code blocks.
Basic Usage
// React
<code
code={`function hello() {
console.log("Hello, World!");
}`}
language="typescript"
/>
// Solid
<code
code={sourceCode}
language="javascript"
/>
// Core
const codeBlock = new CodeRenderable(renderer, {
id: "code",
code: sourceCode,
language: "typescript",
})
Supported Languages
OpenTUI uses Tree-sitter for syntax highlighting. Common languages:
typescript,javascriptpythonrustgojsonhtml,cssmarkdownbash,shell
Styling
<code
code={sourceCode}
language="typescript"
backgroundColor="#1a1a2e"
showLineNumbers
/>
onHighlight Callback
Intercept and modify syntax highlights before rendering:
// Core
const codeBlock = new CodeRenderable(renderer, {
id: "code",
code: sourceCode,
language: "typescript",
onHighlight: (highlights, context) => {
// Add custom highlights
highlights.push([10, 20, "custom.error", {}])
return highlights
},
})
// React/Solid
<code
code={sourceCode}
language="typescript"
onHighlight={(highlights, context) => {
// context: { content, filetype, syntaxStyle }
// Modify and return highlights array
return highlights.filter(h => h[2] !== "comment")
}}
/>
Callback signature:
highlights: SimpleHighlight[]- Array of[start, end, scope, metadata]context: { content, filetype, syntaxStyle }- Highlighting context- Return modified highlights array or
undefinedto use original
Supports async callbacks for fetching additional highlight data.
onChunks Callback
Post-process rendered text chunks after syntax highlighting. Runs after onHighlight and receives fully resolved chunks:
// Core
const codeBlock = new CodeRenderable(renderer, {
id: "code",
code: sourceCode,
language: "typescript",
onChunks: (chunks, context) => {
// Transform chunks (e.g., add link detection)
return chunks
},
})
// React/Solid
<code
code={sourceCode}
language="typescript"
onChunks={(chunks, context) => {
// context: { content, filetype, syntaxStyle, highlights }
return chunks
}}
/>
Link Detection Utility
Auto-detect URLs in code and add clickable hyperlinks:
import { detectLinks } from "@opentui/core"
<code
code={sourceCode}
language="typescript"
onChunks={(chunks, context) => detectLinks(chunks, context)}
/>
detectLinks examines Tree-sitter highlights to find URL tokens and sets chunk.link on matching chunks. Supports async usage.
TextTable Component
Render data tables with borders, word wrapping, and selection support.
Basic Usage
// Core
import { TextTableRenderable, type TextTableContent } from "@opentui/core"
const content: TextTableContent = [
[[ { text: "Name" } ], [ { text: "Age" } ], [ { text: "Role" } ]],
[[ { text: "Alice" } ], [ { text: "30" } ], [ { text: "Engineer" } ]],
[[ { text: "Bob" } ], [ { text: "25" } ], [ { text: "Designer" } ]],
]
const table = new TextTableRenderable(renderer, {
id: "table",
content,
wrapMode: "word", // "none" | "char" | "word"
columnWidthMode: "content", // "content" | "fill"
cellPadding: 0,
border: true,
outerBorder: true,
borderStyle: "single", // single | double | rounded | bold
selectable: true, // Allow text selection
columnFitter: "balanced", // "proportional" | "balanced"
})
Options
| Option | Type | Default | Description |
|---|---|---|---|
content |
TextTableContent |
- | 2D array of cell content |
wrapMode |
"none" | "char" | "word" |
"none" |
Text wrapping in cells |
columnWidthMode |
"content" | "fill" |
"content" |
Column sizing strategy |
cellPadding |
number |
0 |
Padding inside cells |
border |
boolean |
true |
Show inner borders |
outerBorder |
boolean |
true |
Show outer borders |
borderStyle |
string |
"single" |
Border style |
borderColor |
string | RGBA |
- | Border color |
selectable |
boolean |
false |
Allow text selection |
columnFitter |
"proportional" | "balanced" |
"proportional" |
Column width distribution |
Cell Content Format
Each cell is an array of styled text chunks:
type TextTableCellContent = { text: string; fg?: RGBA; bg?: RGBA }[]
type TextTableContent = TextTableCellContent[][] // rows -> cells -> chunks
Selection
table.getSelectedText() // Get selected text
table.hasSelection() // Check if text is selected
Columnar selection is supported: dragging vertically within a single column selects only that column's content.
Line Number Component
Code display with line numbers, highlighting, and diagnostics.
Basic Usage
// React
<line-number
code={sourceCode}
language="typescript"
/>
// Solid (note underscore)
<line_number
code={sourceCode}
language="typescript"
/>
// Core
const codeView = new LineNumberRenderable(renderer, {
id: "code-view",
code: sourceCode,
language: "typescript",
})
Line Number Options
// React
<line-number
code={sourceCode}
language="typescript"
startLine={1} // Starting line number
showLineNumbers={true} // Display line numbers
/>
// Solid
<line_number
code={sourceCode}
language="typescript"
startLine={1}
showLineNumbers={true}
/>
Line Highlighting
Highlight specific lines:
// React
<line-number
code={sourceCode}
language="typescript"
highlightedLines={[5, 10, 15]} // Highlight these lines
/>
// Solid
<line_number
code={sourceCode}
language="typescript"
highlightedLines={[5, 10, 15]}
/>
Diagnostics
Show errors, warnings, and info on specific lines:
// React
<line-number
code={sourceCode}
language="typescript"
diagnostics={[
{ line: 3, severity: "error", message: "Unexpected token" },
{ line: 7, severity: "warning", message: "Unused variable" },
{ line: 12, severity: "info", message: "Consider using const" },
]}
/>
// Solid
<line_number
code={sourceCode}
language="typescript"
diagnostics={[
{ line: 3, severity: "error", message: "Unexpected token" },
]}
/>
Diagnostic severity levels:
error- Red indicatorwarning- Yellow indicatorinfo- Blue indicatorhint- Gray indicator
Diff Highlighting
Show added/removed lines:
<line-number
code={sourceCode}
language="typescript"
addedLines={[5, 6, 7]} // Green background
removedLines={[10, 11]} // Red background
/>
Diff Component
Unified or split diff viewer with syntax highlighting.
Basic Usage
// React
<diff
oldCode={originalCode}
newCode={modifiedCode}
language="typescript"
/>
// Solid
<diff
oldCode={originalCode}
newCode={modifiedCode}
language="typescript"
/>
// Core
const diffView = new DiffRenderable(renderer, {
id: "diff",
oldCode: originalCode,
newCode: modifiedCode,
language: "typescript",
})
Display Modes
// Unified diff (default)
<diff
oldCode={old}
newCode={new}
mode="unified"
/>
// Split/side-by-side diff
<diff
oldCode={old}
newCode={new}
mode="split"
/>
Synchronized Scrolling (Split View)
In split view, enable synchronized scrolling between left and right panes:
// React/Solid
<diff
oldCode={old}
newCode={new}
mode="split"
syncScroll // Scrolling one pane syncs the other
/>
// Core
const diffView = new DiffRenderable(renderer, {
id: "diff",
diff: unifiedDiff,
view: "split",
syncScroll: true,
})
// Toggle at runtime
diffView.syncScroll = true
diffView.syncScroll = false
Options
<diff
oldCode={originalCode}
newCode={modifiedCode}
language="typescript"
mode="unified"
showLineNumbers
context={3} // Lines of context around changes
/>
Styling
<diff
oldCode={old}
newCode={new}
addedLineColor="#2d4f2d" // Background for added lines
removedLineColor="#4f2d2d" // Background for removed lines
unchangedLineColor="transparent"
/>
Line Highlighting API (Core)
Programmatically highlight specific lines in a diff:
// Set a single line's color
diffView.setLineColor(5, "#2d4f2d")
diffView.setLineColor(5, { gutter: "#333", content: "#2d4f2d" })
// Clear a single line's color
diffView.clearLineColor(5)
// Set multiple lines at once
diffView.setLineColors(new Map([
[1, "#2d4f2d"],
[2, "#4f2d2d"],
]))
// Highlight a range
diffView.highlightLines(10, 20, "#2d4f2d")
diffView.clearHighlightLines(10, 20)
// Clear all line colors
diffView.clearAllLineColors()
The LineNumberRenderable also supports programmatic highlighting:
lineNumberView.highlightLines(5, 10, "#2d4f2d")
lineNumberView.clearHighlightLines(5, 10)
## Markdown Component
Render markdown content with syntax highlighting for code blocks.
### Basic Usage
```tsx
// React
<markdown
content={markdownText}
syntaxStyle={mySyntaxStyle}
/>
// Solid
<markdown
content={markdownText}
syntaxStyle={mySyntaxStyle}
/>
// Core
import { MarkdownRenderable } from "@opentui/core"
const md = new MarkdownRenderable(renderer, {
id: "markdown",
content: "# Hello\n\nThis is **markdown**.",
syntaxStyle: mySyntaxStyle,
})
Options
<markdown
content={markdownText}
syntaxStyle={syntaxStyle}
treeSitterClient={client} // Optional: custom tree-sitter client
conceal={true} // Hide markdown syntax characters
streaming={true} // Enable streaming mode for incremental updates
tableOptions={{ // Customize markdown table rendering
widthMode: "full", // "content" | "full"
wrapMode: "word", // "none" | "char" | "word"
cellPadding: 0,
borders: true,
outerBorder: true,
borderStyle: "single",
borderColor: "#555",
selectable: true, // Tables are selectable by default
}}
/>
Custom Node Rendering
// Core
const md = new MarkdownRenderable(renderer, {
id: "markdown",
content: "# Custom Heading",
syntaxStyle,
renderNode: (node, ctx, defaultRender) => {
if (node.type === "heading") {
// Return custom renderable for headings
return new TextRenderable(ctx, {
content: `>> ${node.content} <<`,
})
}
return null // Use default rendering
},
})
Streaming Mode
For real-time content like LLM output:
const [content, setContent] = useState("")
// Append text as it arrives
useEffect(() => {
llmStream.on("token", (token) => {
setContent(c => c + token)
})
}, [])
<markdown
content={content}
syntaxStyle={syntaxStyle}
streaming={true} // Optimizes for incremental updates
/>
Use Cases
Code Editor
function CodeEditor() {
const [code, setCode] = useState(`function hello() {
console.log("Hello!");
}`)
return (
<box flexDirection="column" height="100%">
<box height={1}>
<text>editor.ts</text>
</box>
<textarea
value={code}
onChange={setCode}
language="typescript"
showLineNumbers
flexGrow={1}
focused
/>
</box>
)
}
Code Review
function CodeReview({ oldCode, newCode }) {
return (
<box flexDirection="column" height="100%">
<box height={1} backgroundColor="#333">
<text>Changes in src/utils.ts</text>
</box>
<diff
oldCode={oldCode}
newCode={newCode}
language="typescript"
mode="split"
showLineNumbers
/>
</box>
)
}
Syntax-Highlighted Preview
function MarkdownPreview({ content }) {
// Extract code blocks from markdown
const codeBlocks = extractCodeBlocks(content)
return (
<scrollbox height={20}>
{codeBlocks.map((block, i) => (
<box key={i} marginBottom={1}>
<code
code={block.code}
language={block.language}
/>
</box>
))}
</scrollbox>
)
}
Error Display
function ErrorView({ errors, code }) {
const diagnostics = errors.map(err => ({
line: err.line,
severity: "error",
message: err.message,
}))
return (
<line-number
code={code}
language="typescript"
diagnostics={diagnostics}
highlightedLines={errors.map(e => e.line)}
/>
)
}
Gotchas
Solid Uses Underscores
// React
<line-number />
// Solid
<line_number />
Language Required for Highlighting
// No highlighting (plain text)
<code code={text} />
// With highlighting
<code code={text} language="typescript" />
Large Files
For very large files, consider:
- Pagination or virtual scrolling
- Loading only visible portion
- Using
scrollboxwrapper
<scrollbox height={30}>
<line-number
code={largeFile}
language="typescript"
/>
</scrollbox>
Tree-sitter Loading
Syntax highlighting requires Tree-sitter grammars. If highlighting isn't working:
- Check the language is supported
- Verify grammars are installed
- Check
OTUI_TREE_SITTER_WORKER_PATHif using custom path