@tooee/renderers 0.1.11 → 0.1.12
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 +3 -7
- package/dist/CodeView.d.ts.map +1 -1
- package/dist/CodeView.js +5 -31
- package/dist/CodeView.js.map +1 -1
- package/dist/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette.js +1 -23
- package/dist/CommandPalette.js.map +1 -1
- package/dist/DecorationLayer.d.ts +14 -0
- package/dist/DecorationLayer.d.ts.map +1 -0
- package/dist/DecorationLayer.js +2 -0
- package/dist/DecorationLayer.js.map +1 -0
- package/dist/MarkdownView.d.ts +3 -9
- package/dist/MarkdownView.d.ts.map +1 -1
- package/dist/MarkdownView.js +11 -32
- package/dist/MarkdownView.js.map +1 -1
- package/dist/RowDocumentRenderable.d.ts +14 -26
- package/dist/RowDocumentRenderable.d.ts.map +1 -1
- package/dist/RowDocumentRenderable.js +74 -78
- package/dist/RowDocumentRenderable.js.map +1 -1
- package/dist/Table.d.ts +5 -9
- package/dist/Table.d.ts.map +1 -1
- package/dist/Table.js +17 -46
- package/dist/Table.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/parsers.d.ts.map +1 -1
- package/dist/parsers.js.map +1 -1
- package/dist/useGutterPalette.d.ts +3 -0
- package/dist/useGutterPalette.d.ts.map +1 -0
- package/dist/useGutterPalette.js +10 -0
- package/dist/useGutterPalette.js.map +1 -0
- package/package.json +18 -16
- package/src/CodeView.tsx +11 -54
- package/src/CommandPalette.tsx +1 -25
- package/src/DecorationLayer.ts +11 -0
- package/src/MarkdownView.tsx +19 -51
- package/src/RowDocumentRenderable.ts +91 -135
- package/src/Table.tsx +35 -71
- package/src/index.ts +4 -7
- package/src/parsers.ts +4 -1
- package/src/useGutterPalette.ts +15 -0
package/package.json
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tooee/renderers",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Content renderers (markdown, code, image, table) for Tooee",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"image",
|
|
8
|
+
"markdown",
|
|
9
|
+
"opentui",
|
|
10
|
+
"syntax-highlighting",
|
|
11
|
+
"terminal",
|
|
12
|
+
"tui"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/gingerhendrix/tooee",
|
|
15
|
+
"bugs": "https://github.com/gingerhendrix/tooee/issues",
|
|
5
16
|
"license": "MIT",
|
|
6
17
|
"author": "Gareth Andrew",
|
|
7
18
|
"repository": {
|
|
@@ -9,16 +20,9 @@
|
|
|
9
20
|
"url": "https://github.com/gingerhendrix/tooee.git",
|
|
10
21
|
"directory": "packages/renderers"
|
|
11
22
|
},
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"tui",
|
|
16
|
-
"terminal",
|
|
17
|
-
"cli",
|
|
18
|
-
"opentui",
|
|
19
|
-
"markdown",
|
|
20
|
-
"syntax-highlighting",
|
|
21
|
-
"image"
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src"
|
|
22
26
|
],
|
|
23
27
|
"type": "module",
|
|
24
28
|
"exports": {
|
|
@@ -29,15 +33,13 @@
|
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
},
|
|
32
|
-
"files": [
|
|
33
|
-
"dist",
|
|
34
|
-
"src"
|
|
35
|
-
],
|
|
36
36
|
"scripts": {
|
|
37
37
|
"typecheck": "tsc --noEmit"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@tooee/
|
|
40
|
+
"@tooee/fuzzy": "0.1.12",
|
|
41
|
+
"@tooee/marks": "0.1.12",
|
|
42
|
+
"@tooee/themes": "0.1.12",
|
|
41
43
|
"marked": "^17.0.4"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
package/src/CodeView.tsx
CHANGED
|
@@ -1,75 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type RefObject } from "react"
|
|
2
2
|
import { useTheme } from "@tooee/themes"
|
|
3
|
-
import type {
|
|
3
|
+
import type { MarkState } from "@tooee/marks"
|
|
4
|
+
import type { RowDocumentRenderable } from "./RowDocumentRenderable.js"
|
|
5
|
+
import { useGutterPalette } from "./useGutterPalette.js"
|
|
4
6
|
import "./row-document.js"
|
|
5
7
|
|
|
6
8
|
interface CodeViewProps {
|
|
7
9
|
content: string
|
|
8
10
|
language?: string
|
|
9
11
|
showLineNumbers?: boolean
|
|
10
|
-
|
|
11
|
-
selectionStart?: number
|
|
12
|
-
selectionEnd?: number
|
|
13
|
-
matchingLines?: Set<number>
|
|
14
|
-
currentMatchLine?: number
|
|
15
|
-
toggledLines?: Set<number>
|
|
12
|
+
marks?: MarkState
|
|
16
13
|
docRef?: RefObject<RowDocumentRenderable | null>
|
|
17
14
|
}
|
|
18
15
|
|
|
19
|
-
export function CodeView({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
showLineNumbers = true,
|
|
23
|
-
cursor,
|
|
24
|
-
selectionStart,
|
|
25
|
-
selectionEnd,
|
|
26
|
-
matchingLines,
|
|
27
|
-
currentMatchLine,
|
|
28
|
-
toggledLines,
|
|
29
|
-
docRef,
|
|
30
|
-
}: CodeViewProps) {
|
|
31
|
-
const { syntax, theme } = useTheme()
|
|
32
|
-
const internalRef = useRef<RowDocumentRenderable>(null)
|
|
33
|
-
const effectiveRef = docRef ?? internalRef
|
|
34
|
-
|
|
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
|
-
}
|
|
47
|
-
|
|
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,
|
|
57
|
-
}
|
|
58
|
-
effectiveRef.current?.setDecorations(decorations)
|
|
59
|
-
}, [cursor, selectionStart, selectionEnd, matchingLines, currentMatchLine, toggledLines])
|
|
60
|
-
|
|
61
|
-
const codeElement = <code content={content} filetype={language} syntaxStyle={syntax} />
|
|
16
|
+
export function CodeView({ content, language, showLineNumbers = true, marks, docRef }: CodeViewProps) {
|
|
17
|
+
const { syntax } = useTheme()
|
|
18
|
+
const palette = useGutterPalette()
|
|
62
19
|
|
|
63
20
|
return (
|
|
64
21
|
<row-document
|
|
65
|
-
ref={
|
|
66
|
-
key={theme.textMuted + theme.backgroundElement}
|
|
22
|
+
ref={docRef}
|
|
67
23
|
showLineNumbers={showLineNumbers}
|
|
68
24
|
palette={palette}
|
|
25
|
+
decorations={marks?.sets}
|
|
69
26
|
signColumnWidth={1}
|
|
70
27
|
style={{ flexGrow: 1 }}
|
|
71
28
|
>
|
|
72
|
-
{
|
|
29
|
+
<code content={content} filetype={language} syntaxStyle={syntax} />
|
|
73
30
|
</row-document>
|
|
74
31
|
)
|
|
75
32
|
}
|
package/src/CommandPalette.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useMemo, useCallback } from "react"
|
|
2
2
|
import { useKeyboard } from "@opentui/react"
|
|
3
3
|
import { useTheme } from "@tooee/themes"
|
|
4
|
+
import { fuzzyMatch } from "@tooee/fuzzy"
|
|
4
5
|
|
|
5
6
|
export interface CommandPaletteEntry {
|
|
6
7
|
id: string
|
|
@@ -16,31 +17,6 @@ interface CommandPaletteProps {
|
|
|
16
17
|
onClose: () => void
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
function fuzzyMatch(query: string, text: string): number | null {
|
|
20
|
-
const lowerQuery = query.toLowerCase()
|
|
21
|
-
const lowerText = text.toLowerCase()
|
|
22
|
-
|
|
23
|
-
let qi = 0
|
|
24
|
-
let score = 0
|
|
25
|
-
let lastMatchIndex = -2
|
|
26
|
-
|
|
27
|
-
for (let ti = 0; ti < lowerText.length && qi < lowerQuery.length; ti++) {
|
|
28
|
-
if (lowerText[ti] === lowerQuery[qi]) {
|
|
29
|
-
// Start of string bonus
|
|
30
|
-
if (ti === 0) score += 3
|
|
31
|
-
// Word boundary bonus (after space, hyphen, dot, slash)
|
|
32
|
-
else if (" -./".includes(lowerText[ti - 1]!)) score += 2
|
|
33
|
-
// Consecutive bonus
|
|
34
|
-
else if (ti === lastMatchIndex + 1) score += 1
|
|
35
|
-
|
|
36
|
-
lastMatchIndex = ti
|
|
37
|
-
qi++
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return qi === lowerQuery.length ? score : null
|
|
42
|
-
}
|
|
43
|
-
|
|
44
20
|
export function CommandPalette({ commands, onSelect, onClose }: CommandPaletteProps) {
|
|
45
21
|
const { theme } = useTheme()
|
|
46
22
|
const [filter, setFilter] = useState("")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface RowDecoration {
|
|
2
|
+
row: number
|
|
3
|
+
background?: string
|
|
4
|
+
gutterBackground?: string
|
|
5
|
+
sign?: { text: string; fg?: string }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface DecorationLayer {
|
|
9
|
+
readonly priority: number
|
|
10
|
+
forVisibleRows(from: number, to: number): Iterable<RowDecoration>
|
|
11
|
+
}
|
package/src/MarkdownView.tsx
CHANGED
|
@@ -1,73 +1,37 @@
|
|
|
1
1
|
import { marked, type Token, type Tokens } from "marked"
|
|
2
|
-
import {
|
|
2
|
+
import { useMemo, type ReactNode, type RefObject } from "react"
|
|
3
3
|
import { useTheme, type ResolvedTheme } from "@tooee/themes"
|
|
4
4
|
import { bold as boldChunk } from "@opentui/core"
|
|
5
5
|
import type { SyntaxStyle, TextTableContent, TextTableCellContent } from "@opentui/core"
|
|
6
|
-
import type {
|
|
6
|
+
import type { MarkState } from "@tooee/marks"
|
|
7
|
+
import type { RowDocumentRenderable } from "./RowDocumentRenderable.js"
|
|
8
|
+
import { useGutterPalette } from "./useGutterPalette.js"
|
|
7
9
|
import "./row-document.js"
|
|
8
10
|
import "./text-table.js"
|
|
9
11
|
|
|
10
12
|
interface MarkdownViewProps {
|
|
11
13
|
content: string
|
|
12
14
|
showLineNumbers?: boolean
|
|
13
|
-
|
|
14
|
-
selectedBlocks?: { start: number; end: number }
|
|
15
|
-
matchingBlocks?: Set<number>
|
|
16
|
-
currentMatchBlock?: number
|
|
17
|
-
toggledBlocks?: Set<number>
|
|
15
|
+
marks?: MarkState
|
|
18
16
|
docRef?: RefObject<RowDocumentRenderable | null>
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
export function MarkdownView({
|
|
22
|
-
content,
|
|
23
|
-
showLineNumbers = true,
|
|
24
|
-
activeBlock,
|
|
25
|
-
selectedBlocks,
|
|
26
|
-
matchingBlocks,
|
|
27
|
-
currentMatchBlock,
|
|
28
|
-
toggledBlocks,
|
|
29
|
-
docRef,
|
|
30
|
-
}: MarkdownViewProps) {
|
|
19
|
+
export function MarkdownView({ content, showLineNumbers = true, marks, docRef }: MarkdownViewProps) {
|
|
31
20
|
const { theme, syntax } = useTheme()
|
|
32
|
-
const
|
|
33
|
-
const effectiveRef = docRef ?? internalRef
|
|
21
|
+
const palette = useGutterPalette()
|
|
34
22
|
const tokens = marked.lexer(content)
|
|
35
23
|
const blocks = tokens.filter((t) => t.type !== "space")
|
|
36
24
|
|
|
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
|
-
}
|
|
49
|
-
|
|
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])
|
|
60
|
-
|
|
61
25
|
const blockElements = blocks.map((token, index) => (
|
|
62
26
|
<TokenRenderer key={index} token={token} theme={theme} syntax={syntax} />
|
|
63
27
|
))
|
|
64
28
|
|
|
65
29
|
return (
|
|
66
30
|
<row-document
|
|
67
|
-
ref={
|
|
68
|
-
key={theme.textMuted + theme.backgroundElement}
|
|
31
|
+
ref={docRef}
|
|
69
32
|
showLineNumbers={showLineNumbers}
|
|
70
33
|
palette={palette}
|
|
34
|
+
decorations={marks?.sets}
|
|
71
35
|
signColumnWidth={1}
|
|
72
36
|
style={{ flexGrow: 1 }}
|
|
73
37
|
>
|
|
@@ -265,6 +229,9 @@ function ListItemRenderer({
|
|
|
265
229
|
</text>
|
|
266
230
|
)
|
|
267
231
|
}
|
|
232
|
+
if (token.type === "list") {
|
|
233
|
+
return <ListRenderer key={idx} token={token as Tokens.List} theme={theme} />
|
|
234
|
+
}
|
|
268
235
|
if ("text" in token && typeof token.text === "string") {
|
|
269
236
|
return <text key={idx} style={{ fg: theme.markdownText }} content={token.text} />
|
|
270
237
|
}
|
|
@@ -279,13 +246,14 @@ function MarkdownTableRenderer({ token }: { token: Tokens.Table }) {
|
|
|
279
246
|
const { theme } = useTheme()
|
|
280
247
|
|
|
281
248
|
const content: TextTableContent = useMemo(() => {
|
|
282
|
-
const headerRow: TextTableCellContent[] = token.header.map(cell => [
|
|
283
|
-
boldChunk(getPlainText(cell.tokens).trim())
|
|
249
|
+
const headerRow: TextTableCellContent[] = token.header.map((cell) => [
|
|
250
|
+
boldChunk(getPlainText(cell.tokens).trim()),
|
|
284
251
|
])
|
|
285
|
-
const dataRows = token.rows.map(row =>
|
|
286
|
-
row.map(
|
|
287
|
-
|
|
288
|
-
|
|
252
|
+
const dataRows = token.rows.map((row) =>
|
|
253
|
+
row.map(
|
|
254
|
+
(cell) =>
|
|
255
|
+
[{ __isChunk: true as const, text: getPlainText(cell.tokens) }] as TextTableCellContent,
|
|
256
|
+
),
|
|
289
257
|
)
|
|
290
258
|
return [headerRow, ...dataRows]
|
|
291
259
|
}, [token])
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
RGBA,
|
|
9
9
|
} from "@opentui/core"
|
|
10
10
|
import type { OptimizedBuffer } from "@opentui/core"
|
|
11
|
+
import type { DecorationLayer, RowDecoration } from "./DecorationLayer.js"
|
|
11
12
|
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
// Types
|
|
@@ -16,25 +17,6 @@ import type { OptimizedBuffer } from "@opentui/core"
|
|
|
16
17
|
export interface RowDocumentPalette {
|
|
17
18
|
gutterFg?: string
|
|
18
19
|
gutterBg?: string
|
|
19
|
-
cursorBg?: string
|
|
20
|
-
selectionBg?: string
|
|
21
|
-
matchBg?: string
|
|
22
|
-
currentMatchBg?: string
|
|
23
|
-
toggledBg?: string
|
|
24
|
-
cursorSign?: string
|
|
25
|
-
cursorSignFg?: string
|
|
26
|
-
matchSign?: string
|
|
27
|
-
matchSignFg?: string
|
|
28
|
-
currentMatchSignFg?: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface RowDocumentDecorations {
|
|
32
|
-
cursorRow?: number
|
|
33
|
-
selection?: { start: number; end: number } | null
|
|
34
|
-
matchingRows?: Set<number>
|
|
35
|
-
currentMatchRow?: number
|
|
36
|
-
toggledRows?: Set<number>
|
|
37
|
-
signs?: Map<number, { text: string; fg?: string }>
|
|
38
20
|
}
|
|
39
21
|
|
|
40
22
|
export interface RowDocumentOptions extends ScrollBoxOptions {
|
|
@@ -52,6 +34,7 @@ export interface RowDocumentOptions extends ScrollBoxOptions {
|
|
|
52
34
|
|
|
53
35
|
// Colors
|
|
54
36
|
palette?: RowDocumentPalette
|
|
37
|
+
decorations?: readonly DecorationLayer[]
|
|
55
38
|
}
|
|
56
39
|
|
|
57
40
|
// ---------------------------------------------------------------------------
|
|
@@ -60,11 +43,7 @@ export interface RowDocumentOptions extends ScrollBoxOptions {
|
|
|
60
43
|
|
|
61
44
|
function isRowContentProvider(x: unknown): x is LineInfoProvider {
|
|
62
45
|
return (
|
|
63
|
-
!!x &&
|
|
64
|
-
typeof x === "object" &&
|
|
65
|
-
"lineInfo" in x &&
|
|
66
|
-
"lineCount" in x &&
|
|
67
|
-
"virtualLineCount" in x
|
|
46
|
+
!!x && typeof x === "object" && "lineInfo" in x && "lineCount" in x && "virtualLineCount" in x
|
|
68
47
|
)
|
|
69
48
|
}
|
|
70
49
|
|
|
@@ -83,6 +62,37 @@ function cachedColor(hex: string): RGBA {
|
|
|
83
62
|
return c
|
|
84
63
|
}
|
|
85
64
|
|
|
65
|
+
function normalizePalette(palette: RowDocumentPalette = {}): Required<RowDocumentPalette> {
|
|
66
|
+
return {
|
|
67
|
+
gutterFg: palette.gutterFg ?? "#6e7681",
|
|
68
|
+
gutterBg: palette.gutterBg ?? "#0d1117",
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeDecorationLayers(
|
|
73
|
+
layers: readonly DecorationLayer[] | undefined,
|
|
74
|
+
): readonly DecorationLayer[] {
|
|
75
|
+
if (!layers || layers.length === 0) return []
|
|
76
|
+
return [...layers].sort((a, b) => a.priority - b.priority)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function computeRowDocumentGutterWidth(opts: {
|
|
80
|
+
showLineNumbers: boolean
|
|
81
|
+
rowCount: number
|
|
82
|
+
lineNumberStart?: number
|
|
83
|
+
signColumnWidth?: number
|
|
84
|
+
gutterPaddingRight?: number
|
|
85
|
+
}): number {
|
|
86
|
+
let width = 0
|
|
87
|
+
if (opts.showLineNumbers) {
|
|
88
|
+
const maxLineNum = (opts.lineNumberStart ?? 1) + opts.rowCount - 1
|
|
89
|
+
width += Math.max(String(maxLineNum).length, 1)
|
|
90
|
+
}
|
|
91
|
+
width += opts.signColumnWidth ?? 0
|
|
92
|
+
width += opts.gutterPaddingRight ?? 1
|
|
93
|
+
return width
|
|
94
|
+
}
|
|
95
|
+
|
|
86
96
|
// ---------------------------------------------------------------------------
|
|
87
97
|
// RowDocumentRenderable
|
|
88
98
|
// ---------------------------------------------------------------------------
|
|
@@ -97,9 +107,9 @@ export class RowDocumentRenderable extends ScrollBoxRenderable {
|
|
|
97
107
|
private _gutterPaddingRight: number
|
|
98
108
|
private _rowChildOffset: number
|
|
99
109
|
private _palette: Required<RowDocumentPalette>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
private
|
|
110
|
+
private _layers: readonly DecorationLayer[] = []
|
|
111
|
+
private _layerGutterBgs = new Map<number, string>()
|
|
112
|
+
private _layerSigns = new Map<number, NonNullable<RowDecoration["sign"]>>()
|
|
103
113
|
|
|
104
114
|
// -- Geometry arrays --
|
|
105
115
|
private _rowVirtualStarts: number[] = []
|
|
@@ -120,29 +130,21 @@ export class RowDocumentRenderable extends ScrollBoxRenderable {
|
|
|
120
130
|
this._gutterPaddingRight = options.gutterPaddingRight ?? 1
|
|
121
131
|
this._rowChildOffset = options.rowChildOffset ?? 0
|
|
122
132
|
|
|
123
|
-
|
|
124
|
-
this.
|
|
125
|
-
gutterFg: p.gutterFg ?? "#6e7681",
|
|
126
|
-
gutterBg: p.gutterBg ?? "#0d1117",
|
|
127
|
-
cursorBg: p.cursorBg ?? "#1c2128",
|
|
128
|
-
selectionBg: p.selectionBg ?? "#1f3a5f",
|
|
129
|
-
matchBg: p.matchBg ?? "#3b2e00",
|
|
130
|
-
currentMatchBg: p.currentMatchBg ?? "#5a4a00",
|
|
131
|
-
toggledBg: p.toggledBg ?? "#1c2128",
|
|
132
|
-
cursorSign: p.cursorSign ?? "▸",
|
|
133
|
-
cursorSignFg: p.cursorSignFg ?? "#58a6ff",
|
|
134
|
-
matchSign: p.matchSign ?? "●",
|
|
135
|
-
matchSignFg: p.matchSignFg ?? "#d29922",
|
|
136
|
-
currentMatchSignFg: p.currentMatchSignFg ?? "#f0c000",
|
|
137
|
-
}
|
|
133
|
+
this._palette = normalizePalette(options.palette)
|
|
134
|
+
this._layers = normalizeDecorationLayers(options.decorations)
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
// -----------------------------------------------------------------------
|
|
141
138
|
// Decorations
|
|
142
139
|
// -----------------------------------------------------------------------
|
|
143
140
|
|
|
144
|
-
|
|
145
|
-
this.
|
|
141
|
+
set decorations(layers: readonly DecorationLayer[] | undefined) {
|
|
142
|
+
this._layers = normalizeDecorationLayers(layers)
|
|
143
|
+
this.requestRender()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
set palette(palette: RowDocumentPalette | undefined) {
|
|
147
|
+
this._palette = normalizePalette(palette)
|
|
146
148
|
this.requestRender()
|
|
147
149
|
}
|
|
148
150
|
|
|
@@ -178,9 +180,7 @@ export class RowDocumentRenderable extends ScrollBoxRenderable {
|
|
|
178
180
|
return this._virtualRowToRow.length
|
|
179
181
|
}
|
|
180
182
|
|
|
181
|
-
getRowMetrics(
|
|
182
|
-
row: number,
|
|
183
|
-
): { row: number; virtualTop: number; virtualHeight: number } | null {
|
|
183
|
+
getRowMetrics(row: number): { row: number; virtualTop: number; virtualHeight: number } | null {
|
|
184
184
|
if (row < 0 || row >= this._rowCount) return null
|
|
185
185
|
return {
|
|
186
186
|
row,
|
|
@@ -221,10 +221,7 @@ export class RowDocumentRenderable extends ScrollBoxRenderable {
|
|
|
221
221
|
// scrollToRow
|
|
222
222
|
// -----------------------------------------------------------------------
|
|
223
223
|
|
|
224
|
-
scrollToRow(
|
|
225
|
-
row: number,
|
|
226
|
-
align: "nearest" | "start" | "center" | "end" = "nearest",
|
|
227
|
-
): void {
|
|
224
|
+
scrollToRow(row: number, align: "nearest" | "start" | "center" | "end" = "nearest"): void {
|
|
228
225
|
const metrics = this.getRowMetrics(row)
|
|
229
226
|
if (!metrics) return
|
|
230
227
|
|
|
@@ -399,8 +396,7 @@ export class RowDocumentRenderable extends ScrollBoxRenderable {
|
|
|
399
396
|
|
|
400
397
|
// Finalize last row
|
|
401
398
|
if (currentRow >= 0) {
|
|
402
|
-
rowVirtualHeights[currentRow] =
|
|
403
|
-
lineSources.length - rowVirtualStarts[currentRow]
|
|
399
|
+
rowVirtualHeights[currentRow] = lineSources.length - rowVirtualStarts[currentRow]
|
|
404
400
|
}
|
|
405
401
|
|
|
406
402
|
this._finishGeometry(
|
|
@@ -439,17 +435,13 @@ export class RowDocumentRenderable extends ScrollBoxRenderable {
|
|
|
439
435
|
private _computeGutterWidth(): number {
|
|
440
436
|
if (!this._showGutter) return 0
|
|
441
437
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
width += this._signColumnWidth
|
|
450
|
-
width += this._gutterPaddingRight
|
|
451
|
-
|
|
452
|
-
return width
|
|
438
|
+
return computeRowDocumentGutterWidth({
|
|
439
|
+
showLineNumbers: this._showLineNumbers,
|
|
440
|
+
rowCount: this._rowCount,
|
|
441
|
+
lineNumberStart: this._lineNumberStart,
|
|
442
|
+
signColumnWidth: this._signColumnWidth,
|
|
443
|
+
gutterPaddingRight: this._gutterPaddingRight,
|
|
444
|
+
})
|
|
453
445
|
}
|
|
454
446
|
|
|
455
447
|
private _applyGutterPadding(): void {
|
|
@@ -462,22 +454,10 @@ export class RowDocumentRenderable extends ScrollBoxRenderable {
|
|
|
462
454
|
// -----------------------------------------------------------------------
|
|
463
455
|
|
|
464
456
|
private _paintDecorations(buffer: OptimizedBuffer): void {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
currentMatchRow,
|
|
470
|
-
toggledRows,
|
|
471
|
-
} = this._deco
|
|
472
|
-
|
|
473
|
-
const hasDecorations =
|
|
474
|
-
cursorRow != null ||
|
|
475
|
-
selection != null ||
|
|
476
|
-
(matchingRows && matchingRows.size > 0) ||
|
|
477
|
-
currentMatchRow != null ||
|
|
478
|
-
(toggledRows && toggledRows.size > 0)
|
|
479
|
-
|
|
480
|
-
if (!hasDecorations) return
|
|
457
|
+
this._layerGutterBgs = new Map()
|
|
458
|
+
this._layerSigns = new Map()
|
|
459
|
+
|
|
460
|
+
if (this._layers.length === 0) return
|
|
481
461
|
|
|
482
462
|
const vpX = this.viewport.x
|
|
483
463
|
const vpY = this.viewport.y
|
|
@@ -485,35 +465,32 @@ export class RowDocumentRenderable extends ScrollBoxRenderable {
|
|
|
485
465
|
const vpWidth = this.viewport.width
|
|
486
466
|
const top = Math.floor(this.scrollTop)
|
|
487
467
|
|
|
468
|
+
const { firstRow, lastRow } = this.getVisibleRange()
|
|
469
|
+
const rowBgs = new Map<number, string>()
|
|
470
|
+
const rowGutterBgs = new Map<number, string>()
|
|
471
|
+
const rowSigns = new Map<number, NonNullable<RowDecoration["sign"]>>()
|
|
472
|
+
|
|
473
|
+
for (const layer of this._layers) {
|
|
474
|
+
for (const deco of layer.forVisibleRows(firstRow, lastRow)) {
|
|
475
|
+
if (deco.background) rowBgs.set(deco.row, deco.background)
|
|
476
|
+
if (deco.gutterBackground) rowGutterBgs.set(deco.row, deco.gutterBackground)
|
|
477
|
+
if (deco.sign) rowSigns.set(deco.row, deco.sign)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
488
481
|
for (let screenY = 0; screenY < vpHeight; screenY++) {
|
|
489
482
|
const vRow = top + screenY
|
|
490
483
|
if (vRow >= this._virtualRowToRow.length) break
|
|
491
484
|
|
|
492
485
|
const row = this._virtualRowToRow[vRow]
|
|
493
486
|
if (row < 0) continue // Skip gap rows (margins)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
if (cursorRow != null && row === cursorRow) {
|
|
498
|
-
bg = cachedColor(this._palette.cursorBg)
|
|
499
|
-
}
|
|
500
|
-
if (toggledRows && toggledRows.has(row)) {
|
|
501
|
-
bg = cachedColor(this._palette.toggledBg)
|
|
502
|
-
}
|
|
503
|
-
if (selection && row >= selection.start && row <= selection.end) {
|
|
504
|
-
bg = cachedColor(this._palette.selectionBg)
|
|
505
|
-
}
|
|
506
|
-
if (matchingRows && matchingRows.has(row)) {
|
|
507
|
-
bg = cachedColor(this._palette.matchBg)
|
|
508
|
-
}
|
|
509
|
-
if (currentMatchRow != null && row === currentMatchRow) {
|
|
510
|
-
bg = cachedColor(this._palette.currentMatchBg)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
if (bg) {
|
|
514
|
-
buffer.fillRect(vpX, vpY + screenY, vpWidth, 1, bg)
|
|
515
|
-
}
|
|
487
|
+
const bg = rowBgs.get(row)
|
|
488
|
+
if (!bg) continue
|
|
489
|
+
buffer.fillRect(vpX, vpY + screenY, vpWidth, 1, cachedColor(bg))
|
|
516
490
|
}
|
|
491
|
+
|
|
492
|
+
this._layerGutterBgs = rowGutterBgs
|
|
493
|
+
this._layerSigns = rowSigns
|
|
517
494
|
}
|
|
518
495
|
|
|
519
496
|
// -----------------------------------------------------------------------
|
|
@@ -552,53 +529,32 @@ export class RowDocumentRenderable extends ScrollBoxRenderable {
|
|
|
552
529
|
const drawX = vpX
|
|
553
530
|
let col = 0
|
|
554
531
|
|
|
532
|
+
const rowGutterHex = this._layerGutterBgs.get(row)
|
|
533
|
+
const effectiveGutterBg = rowGutterHex ? cachedColor(rowGutterHex) : gutterBg
|
|
534
|
+
if (rowGutterHex) {
|
|
535
|
+
buffer.fillRect(drawX, vpY + screenY, gutterWidth, 1, effectiveGutterBg)
|
|
536
|
+
}
|
|
537
|
+
|
|
555
538
|
// Line number
|
|
556
539
|
if (this._showLineNumbers) {
|
|
557
540
|
const lineNum = String(this._lineNumberStart + row)
|
|
558
541
|
const padded = lineNum.padStart(lineNumWidth, " ")
|
|
559
|
-
buffer.drawText(padded, drawX + col, vpY + screenY, gutterFg,
|
|
542
|
+
buffer.drawText(padded, drawX + col, vpY + screenY, gutterFg, effectiveGutterBg)
|
|
560
543
|
col += lineNumWidth
|
|
561
544
|
}
|
|
562
545
|
|
|
563
546
|
// Sign column
|
|
564
547
|
if (this._signColumnWidth > 0) {
|
|
565
|
-
const
|
|
566
|
-
const customSign = this._deco.signs?.get(row)
|
|
548
|
+
const sign = this._layerSigns.get(row)
|
|
567
549
|
|
|
568
|
-
if (
|
|
569
|
-
const signFg =
|
|
570
|
-
? cachedColor(customSign.fg)
|
|
571
|
-
: gutterFg
|
|
550
|
+
if (sign) {
|
|
551
|
+
const signFg = sign.fg ? cachedColor(sign.fg) : gutterFg
|
|
572
552
|
buffer.drawText(
|
|
573
|
-
|
|
553
|
+
sign.text.slice(0, this._signColumnWidth),
|
|
574
554
|
drawX + col,
|
|
575
555
|
vpY + screenY,
|
|
576
556
|
signFg,
|
|
577
|
-
|
|
578
|
-
)
|
|
579
|
-
} else if (cursorRow != null && row === cursorRow) {
|
|
580
|
-
buffer.drawText(
|
|
581
|
-
this._palette.cursorSign,
|
|
582
|
-
drawX + col,
|
|
583
|
-
vpY + screenY,
|
|
584
|
-
cachedColor(this._palette.cursorSignFg),
|
|
585
|
-
gutterBg,
|
|
586
|
-
)
|
|
587
|
-
} else if (currentMatchRow != null && row === currentMatchRow) {
|
|
588
|
-
buffer.drawText(
|
|
589
|
-
this._palette.matchSign,
|
|
590
|
-
drawX + col,
|
|
591
|
-
vpY + screenY,
|
|
592
|
-
cachedColor(this._palette.currentMatchSignFg),
|
|
593
|
-
gutterBg,
|
|
594
|
-
)
|
|
595
|
-
} else if (matchingRows && matchingRows.has(row)) {
|
|
596
|
-
buffer.drawText(
|
|
597
|
-
this._palette.matchSign,
|
|
598
|
-
drawX + col,
|
|
599
|
-
vpY + screenY,
|
|
600
|
-
cachedColor(this._palette.matchSignFg),
|
|
601
|
-
gutterBg,
|
|
557
|
+
effectiveGutterBg,
|
|
602
558
|
)
|
|
603
559
|
}
|
|
604
560
|
}
|