306 lines
9.2 KiB
Markdown
306 lines
9.2 KiB
Markdown
# 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
|
|
|
|
```tsx
|
|
// 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.
|
|
|
|
```tsx
|
|
<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`.
|
|
|
|
```tsx
|
|
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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```tsx
|
|
// 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>
|
|
```
|
|
|
|
```typescript
|
|
// 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:
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```tsx
|
|
// 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
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```tsx
|
|
<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.
|
|
|
|
```typescript
|
|
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
|
|
|
|
```tsx
|
|
<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 `content` and `filetype`, not `code` and `language`.
|
|
- Diff uses one unified `diff` string and `view`, not old/new strings and
|
|
`mode`.
|
|
- Line Number wraps a target; it does not render source code by itself.
|
|
- Tree-sitter loading is asynchronous. Use `OTUI_TREE_SITTER_WORKER_PATH` when
|
|
packaging requires a custom worker path.
|
|
- Put large Code/Line Number views inside a height-constrained ScrollBox.
|
|
|
|
## See Also
|
|
|
|
- [Text & Display](./text-display.md) - Styled text and image rendering
|
|
- [Containers](./containers.md) - ScrollBox for large content
|
|
- [Core API](../core/api.md) - Imperative renderables
|
|
- [Testing](../testing/REFERENCE.md) - Frame and snapshot tests
|