@tooee/renderers 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CodeView.d.ts +5 -1
- package/dist/CodeView.d.ts.map +1 -1
- package/dist/CodeView.js +28 -61
- package/dist/CodeView.js.map +1 -1
- package/dist/MarkdownView.d.ts +6 -2
- package/dist/MarkdownView.d.ts.map +1 -1
- package/dist/MarkdownView.js +53 -48
- package/dist/MarkdownView.js.map +1 -1
- package/dist/RowDocumentRenderable.d.ts +87 -0
- package/dist/RowDocumentRenderable.d.ts.map +1 -0
- package/dist/RowDocumentRenderable.js +428 -0
- package/dist/RowDocumentRenderable.js.map +1 -0
- package/dist/Table.d.ts +5 -1
- package/dist/Table.d.ts.map +1 -1
- package/dist/Table.js +24 -31
- package/dist/Table.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/row-document.d.ts +7 -0
- package/dist/row-document.d.ts.map +1 -0
- package/dist/row-document.js +4 -0
- package/dist/row-document.js.map +1 -0
- package/package.json +2 -2
- package/src/CodeView.tsx +39 -81
- package/src/MarkdownView.tsx +93 -75
- package/src/RowDocumentRenderable.ts +607 -0
- package/src/Table.tsx +37 -35
- package/src/index.ts +6 -0
- package/src/row-document.ts +10 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"row-document.d.ts","sourceRoot":"","sources":["../src/row-document.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAIlE,OAAO,QAAQ,gBAAgB,CAAC;IAC9B,UAAU,iBAAiB;QACzB,cAAc,EAAE,OAAO,qBAAqB,CAAA;KAC7C;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"row-document.js","sourceRoot":"","sources":["../src/row-document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAElE,MAAM,CAAC,EAAE,cAAc,EAAE,qBAAqB,EAAE,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tooee/renderers",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Content renderers (markdown, code, image, table) for Tooee",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Gareth Andrew",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"typecheck": "tsc --noEmit"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@tooee/themes": "0.1.
|
|
40
|
+
"@tooee/themes": "0.1.7",
|
|
41
41
|
"marked": "^16.3.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
package/src/CodeView.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react"
|
|
2
|
-
import type { LineNumberRenderable } from "@opentui/core"
|
|
1
|
+
import { useEffect, useRef, type RefObject } from "react"
|
|
3
2
|
import { useTheme } from "@tooee/themes"
|
|
3
|
+
import type { RowDocumentRenderable, RowDocumentPalette, RowDocumentDecorations } from "./RowDocumentRenderable.js"
|
|
4
|
+
import "./row-document.js"
|
|
4
5
|
|
|
5
6
|
interface CodeViewProps {
|
|
6
7
|
content: string
|
|
@@ -12,6 +13,7 @@ interface CodeViewProps {
|
|
|
12
13
|
matchingLines?: Set<number>
|
|
13
14
|
currentMatchLine?: number
|
|
14
15
|
toggledLines?: Set<number>
|
|
16
|
+
docRef?: RefObject<RowDocumentRenderable | null>
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export function CodeView({
|
|
@@ -24,94 +26,50 @@ export function CodeView({
|
|
|
24
26
|
matchingLines,
|
|
25
27
|
currentMatchLine,
|
|
26
28
|
toggledLines,
|
|
29
|
+
docRef,
|
|
27
30
|
}: CodeViewProps) {
|
|
28
31
|
const { syntax, theme } = useTheme()
|
|
29
|
-
const
|
|
32
|
+
const internalRef = useRef<RowDocumentRenderable>(null)
|
|
33
|
+
const effectiveRef = docRef ?? internalRef
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
const palette: RowDocumentPalette = {
|
|
36
|
+
gutterFg: theme.textMuted,
|
|
37
|
+
gutterBg: theme.backgroundElement,
|
|
38
|
+
cursorBg: theme.cursorLine,
|
|
39
|
+
selectionBg: theme.selection,
|
|
40
|
+
matchBg: theme.warning,
|
|
41
|
+
currentMatchBg: theme.primary,
|
|
42
|
+
toggledBg: theme.backgroundPanel,
|
|
43
|
+
cursorSignFg: theme.primary,
|
|
44
|
+
matchSignFg: theme.warning,
|
|
45
|
+
currentMatchSignFg: theme.primary,
|
|
46
|
+
}
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Selection range
|
|
49
|
-
if (selectionStart != null && selectionEnd != null) {
|
|
50
|
-
for (let i = selectionStart; i <= selectionEnd; i++) {
|
|
51
|
-
ref.setLineColor(i, { content: theme.selection, gutter: theme.selection })
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (toggledLines) {
|
|
56
|
-
for (const line of toggledLines) {
|
|
57
|
-
const isSelected =
|
|
58
|
-
selectionStart != null && selectionEnd != null && line >= selectionStart && line <= selectionEnd
|
|
59
|
-
if (line === cursor || isSelected) continue
|
|
60
|
-
ref.setLineColor(line, {
|
|
61
|
-
content: theme.backgroundPanel,
|
|
62
|
-
gutter: theme.backgroundPanel,
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Cursor line (overwrites selection color on cursor line)
|
|
68
|
-
if (cursor != null) {
|
|
69
|
-
ref.setLineColor(cursor, { content: theme.cursorLine, gutter: theme.cursorLine })
|
|
70
|
-
ref.setLineSign(cursor, {
|
|
71
|
-
before: "▸",
|
|
72
|
-
beforeColor: theme.primary,
|
|
73
|
-
// Preserve search match sign if present
|
|
74
|
-
...(matchingLines?.has(cursor)
|
|
75
|
-
? {
|
|
76
|
-
after: "●",
|
|
77
|
-
afterColor: cursor === currentMatchLine ? theme.primary : theme.warning,
|
|
78
|
-
}
|
|
79
|
-
: {}),
|
|
80
|
-
})
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const decorations: RowDocumentDecorations = {
|
|
50
|
+
cursorRow: cursor,
|
|
51
|
+
selection: selectionStart != null && selectionEnd != null
|
|
52
|
+
? { start: selectionStart, end: selectionEnd }
|
|
53
|
+
: null,
|
|
54
|
+
matchingRows: matchingLines,
|
|
55
|
+
currentMatchRow: currentMatchLine,
|
|
56
|
+
toggledRows: toggledLines,
|
|
81
57
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
cursor,
|
|
85
|
-
selectionStart,
|
|
86
|
-
selectionEnd,
|
|
87
|
-
matchingLines,
|
|
88
|
-
currentMatchLine,
|
|
89
|
-
toggledLines,
|
|
90
|
-
theme,
|
|
91
|
-
])
|
|
58
|
+
effectiveRef.current?.setDecorations(decorations)
|
|
59
|
+
}, [cursor, selectionStart, selectionEnd, matchingLines, currentMatchLine, toggledLines])
|
|
92
60
|
|
|
93
61
|
const codeElement = <code content={content} filetype={language} syntaxStyle={syntax} />
|
|
94
62
|
|
|
95
63
|
return (
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
64
|
+
<row-document
|
|
65
|
+
ref={effectiveRef}
|
|
66
|
+
key={theme.textMuted + theme.backgroundElement}
|
|
67
|
+
showLineNumbers={showLineNumbers}
|
|
68
|
+
palette={palette}
|
|
69
|
+
signColumnWidth={1}
|
|
70
|
+
style={{ flexGrow: 1 }}
|
|
100
71
|
>
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
ref={lineNumRef}
|
|
104
|
-
key={theme.textMuted + theme.backgroundElement}
|
|
105
|
-
fg={theme.textMuted}
|
|
106
|
-
bg={theme.backgroundElement}
|
|
107
|
-
paddingRight={1}
|
|
108
|
-
showLineNumbers
|
|
109
|
-
>
|
|
110
|
-
{codeElement}
|
|
111
|
-
</line-number>
|
|
112
|
-
) : (
|
|
113
|
-
codeElement
|
|
114
|
-
)}
|
|
115
|
-
</box>
|
|
72
|
+
{codeElement}
|
|
73
|
+
</row-document>
|
|
116
74
|
)
|
|
117
75
|
}
|
package/src/MarkdownView.tsx
CHANGED
|
@@ -1,83 +1,79 @@
|
|
|
1
1
|
import { marked, type Token, type Tokens } from "marked"
|
|
2
|
-
import type
|
|
2
|
+
import { useEffect, useRef, type ReactNode, type RefObject } from "react"
|
|
3
|
+
import { useTerminalDimensions } from "@opentui/react"
|
|
3
4
|
import { useTheme, type ResolvedTheme } from "@tooee/themes"
|
|
4
5
|
import type { SyntaxStyle } from "@opentui/core"
|
|
5
|
-
import {
|
|
6
|
+
import { computeColumnWidths, buildBorderLine, buildDataLine, isNumeric } from "./Table.js"
|
|
7
|
+
import type { RowDocumentRenderable, RowDocumentPalette, RowDocumentDecorations } from "./RowDocumentRenderable.js"
|
|
8
|
+
import "./row-document.js"
|
|
6
9
|
|
|
7
10
|
interface MarkdownViewProps {
|
|
8
11
|
content: string
|
|
12
|
+
showLineNumbers?: boolean
|
|
9
13
|
activeBlock?: number
|
|
10
14
|
selectedBlocks?: { start: number; end: number }
|
|
11
15
|
matchingBlocks?: Set<number>
|
|
12
16
|
currentMatchBlock?: number
|
|
13
17
|
toggledBlocks?: Set<number>
|
|
18
|
+
docRef?: RefObject<RowDocumentRenderable | null>
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
export function MarkdownView({
|
|
17
22
|
content,
|
|
23
|
+
showLineNumbers = true,
|
|
18
24
|
activeBlock,
|
|
19
25
|
selectedBlocks,
|
|
20
26
|
matchingBlocks,
|
|
21
27
|
currentMatchBlock,
|
|
22
28
|
toggledBlocks,
|
|
29
|
+
docRef,
|
|
23
30
|
}: MarkdownViewProps) {
|
|
24
31
|
const { theme, syntax } = useTheme()
|
|
32
|
+
const internalRef = useRef<RowDocumentRenderable>(null)
|
|
33
|
+
const effectiveRef = docRef ?? internalRef
|
|
25
34
|
const tokens = marked.lexer(content)
|
|
26
35
|
const blocks = tokens.filter((t) => t.type !== "space")
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const blockContent = (
|
|
41
|
-
<TokenRenderer key={index} token={token} theme={theme} syntax={syntax} />
|
|
42
|
-
)
|
|
37
|
+
const palette: RowDocumentPalette = {
|
|
38
|
+
gutterFg: theme.textMuted,
|
|
39
|
+
gutterBg: theme.backgroundElement,
|
|
40
|
+
cursorBg: theme.cursorLine,
|
|
41
|
+
selectionBg: theme.selection,
|
|
42
|
+
matchBg: theme.warning,
|
|
43
|
+
currentMatchBg: theme.primary,
|
|
44
|
+
toggledBg: theme.backgroundPanel,
|
|
45
|
+
cursorSignFg: theme.primary,
|
|
46
|
+
matchSignFg: theme.warning,
|
|
47
|
+
currentMatchSignFg: theme.primary,
|
|
48
|
+
}
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
)
|
|
55
|
-
}
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const decorations: RowDocumentDecorations = {
|
|
52
|
+
cursorRow: activeBlock,
|
|
53
|
+
selection: selectedBlocks ? { start: selectedBlocks.start, end: selectedBlocks.end } : null,
|
|
54
|
+
matchingRows: matchingBlocks,
|
|
55
|
+
currentMatchRow: currentMatchBlock,
|
|
56
|
+
toggledRows: toggledBlocks,
|
|
57
|
+
}
|
|
58
|
+
effectiveRef.current?.setDecorations(decorations)
|
|
59
|
+
}, [activeBlock, selectedBlocks, matchingBlocks, currentMatchBlock, toggledBlocks])
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
61
|
-
}
|
|
61
|
+
const blockElements = blocks.map((token, index) => (
|
|
62
|
+
<TokenRenderer key={index} token={token} theme={theme} syntax={syntax} />
|
|
63
|
+
))
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (currentMatchBlock === index) return { accent: theme.accent, background: null }
|
|
76
|
-
if (matchingBlocks?.has(index)) return { accent: theme.warning, background: null }
|
|
77
|
-
if (selectedBlocks && index >= selectedBlocks.start && index <= selectedBlocks.end) {
|
|
78
|
-
return { accent: theme.secondary, background: theme.backgroundPanel }
|
|
79
|
-
}
|
|
80
|
-
return { accent: null, background: null }
|
|
65
|
+
return (
|
|
66
|
+
<row-document
|
|
67
|
+
ref={effectiveRef}
|
|
68
|
+
key={theme.textMuted + theme.backgroundElement}
|
|
69
|
+
showLineNumbers={showLineNumbers}
|
|
70
|
+
palette={palette}
|
|
71
|
+
signColumnWidth={1}
|
|
72
|
+
style={{ flexGrow: 1 }}
|
|
73
|
+
>
|
|
74
|
+
{blockElements}
|
|
75
|
+
</row-document>
|
|
76
|
+
)
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
function TokenRenderer({
|
|
@@ -177,6 +173,7 @@ function CodeBlockRenderer({
|
|
|
177
173
|
theme: ResolvedTheme
|
|
178
174
|
syntax: SyntaxStyle
|
|
179
175
|
}) {
|
|
176
|
+
const lineCount = token.text.split("\n").length
|
|
180
177
|
return (
|
|
181
178
|
<box
|
|
182
179
|
style={{
|
|
@@ -190,7 +187,12 @@ function CodeBlockRenderer({
|
|
|
190
187
|
flexDirection: "column",
|
|
191
188
|
}}
|
|
192
189
|
>
|
|
193
|
-
<code
|
|
190
|
+
<code
|
|
191
|
+
content={token.text}
|
|
192
|
+
filetype={token.lang}
|
|
193
|
+
syntaxStyle={syntax}
|
|
194
|
+
style={{ height: lineCount }}
|
|
195
|
+
/>
|
|
194
196
|
</box>
|
|
195
197
|
)
|
|
196
198
|
}
|
|
@@ -274,30 +276,46 @@ function ListItemRenderer({
|
|
|
274
276
|
}
|
|
275
277
|
|
|
276
278
|
function MarkdownTableRenderer({ token }: { token: Tokens.Table }) {
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
279
|
+
const { theme } = useTheme()
|
|
280
|
+
const { width: terminalWidth } = useTerminalDimensions()
|
|
281
|
+
|
|
282
|
+
const headers = token.header.map((cell) => getPlainText(cell.tokens).trim())
|
|
283
|
+
const rowData = token.rows.map((row) =>
|
|
284
|
+
row.map((cell) => getPlainText(cell.tokens)),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
// Account for gutter + margins when computing available width
|
|
288
|
+
const effectiveMaxWidth = terminalWidth - 4
|
|
289
|
+
|
|
290
|
+
const colWidths = computeColumnWidths(headers, rowData, effectiveMaxWidth, {
|
|
291
|
+
minColumnWidth: 4,
|
|
292
|
+
maxColumnWidth: 50,
|
|
293
|
+
sampleSize: 100,
|
|
289
294
|
})
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
record[column.key] = getPlainText(cell.tokens)
|
|
296
|
-
}
|
|
297
|
-
})
|
|
298
|
-
return record
|
|
295
|
+
|
|
296
|
+
const alignments = headers.map((_header, colIdx) => {
|
|
297
|
+
const sampleValues = rowData.slice(0, 10).map((row) => row[colIdx] ?? "")
|
|
298
|
+
const numericCount = sampleValues.filter(isNumeric).length
|
|
299
|
+
return numericCount > sampleValues.length / 2
|
|
299
300
|
})
|
|
300
|
-
|
|
301
|
+
|
|
302
|
+
const topBorder = buildBorderLine(colWidths, "\u250c", "\u252c", "\u2510", "\u2500")
|
|
303
|
+
const headerSep = buildBorderLine(colWidths, "\u251c", "\u253c", "\u2524", "\u2500")
|
|
304
|
+
const bottomBorder = buildBorderLine(colWidths, "\u2514", "\u2534", "\u2518", "\u2500")
|
|
305
|
+
const headerLine = buildDataLine(headers, colWidths, alignments)
|
|
306
|
+
const dataLines = rowData.map((row) => buildDataLine(row, colWidths, alignments))
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<box style={{ marginLeft: 1, marginRight: 1, marginBottom: 1, flexDirection: "column" }}>
|
|
310
|
+
<text content={topBorder} style={{ fg: theme.border }} />
|
|
311
|
+
<text content={headerLine} style={{ fg: theme.primary }} />
|
|
312
|
+
<text content={headerSep} style={{ fg: theme.border }} />
|
|
313
|
+
{dataLines.map((line, i) => (
|
|
314
|
+
<text key={i} content={line} style={{ fg: theme.text }} />
|
|
315
|
+
))}
|
|
316
|
+
<text content={bottomBorder} style={{ fg: theme.border }} />
|
|
317
|
+
</box>
|
|
318
|
+
)
|
|
301
319
|
}
|
|
302
320
|
|
|
303
321
|
function HorizontalRule({ theme }: { theme: ResolvedTheme }) {
|