@tooee/renderers 0.1.11 → 0.1.14
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 +16 -9
- package/dist/MarkdownView.d.ts.map +1 -1
- package/dist/MarkdownView.js +256 -107
- 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 +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- 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 +382 -164
- package/src/RowDocumentRenderable.ts +91 -135
- package/src/Table.tsx +35 -71
- package/src/index.ts +6 -8
- package/src/parsers.ts +4 -1
- package/src/useGutterPalette.ts +15 -0
|
@@ -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
|
}
|
package/src/Table.tsx
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { useTerminalDimensions } from "@opentui/react"
|
|
2
2
|
import { useTheme } from "@tooee/themes"
|
|
3
|
-
import {
|
|
3
|
+
import { type RefObject } from "react"
|
|
4
|
+
import type { MarkState } from "@tooee/marks"
|
|
4
5
|
import type { ColumnDef, TableRow } from "./table-types.js"
|
|
5
|
-
import
|
|
6
|
+
import {
|
|
7
|
+
computeRowDocumentGutterWidth,
|
|
8
|
+
type RowDocumentRenderable,
|
|
9
|
+
} from "./RowDocumentRenderable.js"
|
|
10
|
+
import { useGutterPalette } from "./useGutterPalette.js"
|
|
6
11
|
import "./row-document.js"
|
|
7
12
|
|
|
8
13
|
export interface TableProps {
|
|
@@ -18,12 +23,7 @@ export interface TableProps {
|
|
|
18
23
|
sampleSize?: number
|
|
19
24
|
/** Show line numbers in the gutter (default: true) */
|
|
20
25
|
showLineNumbers?: boolean
|
|
21
|
-
|
|
22
|
-
selectionStart?: number
|
|
23
|
-
selectionEnd?: number
|
|
24
|
-
matchingRows?: Set<number>
|
|
25
|
-
currentMatchRow?: number
|
|
26
|
-
toggledRows?: Set<number>
|
|
26
|
+
marks?: MarkState
|
|
27
27
|
docRef?: RefObject<RowDocumentRenderable | null>
|
|
28
28
|
/** Column width mode: "content" sizes to content (default), "fill" expands to fill available width */
|
|
29
29
|
columnWidthMode?: "content" | "fill"
|
|
@@ -73,7 +73,10 @@ function computeColumnWidths(
|
|
|
73
73
|
// Use Bun.stringWidth for correct display width with CJK/emoji
|
|
74
74
|
const naturalWidths = headers.map((header, col) => {
|
|
75
75
|
const headerLen = Bun.stringWidth(header)
|
|
76
|
-
const maxRowLen = sampledRows.reduce(
|
|
76
|
+
const maxRowLen = sampledRows.reduce(
|
|
77
|
+
(max, row) => Math.max(max, Bun.stringWidth(row[col] ?? "")),
|
|
78
|
+
0,
|
|
79
|
+
)
|
|
77
80
|
const contentWidth = Math.max(headerLen, maxRowLen)
|
|
78
81
|
// Apply min/max constraints before adding padding
|
|
79
82
|
const constrainedWidth = Math.min(maxColumnWidth, Math.max(minColumnWidth, contentWidth))
|
|
@@ -124,22 +127,6 @@ function computeColumnWidths(
|
|
|
124
127
|
})
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
/**
|
|
128
|
-
* Pre-compute gutter width to subtract from available column space.
|
|
129
|
-
* Mirrors RowDocumentRenderable._computeGutterWidth logic.
|
|
130
|
-
*/
|
|
131
|
-
function computeGutterWidth(rowCount: number, showLineNumbers: boolean): number {
|
|
132
|
-
let width = 0
|
|
133
|
-
if (showLineNumbers) {
|
|
134
|
-
// lineNumberStart defaults to 1, so max line number = rowCount
|
|
135
|
-
const maxLineNum = rowCount
|
|
136
|
-
width += Math.max(String(maxLineNum).length, 1)
|
|
137
|
-
}
|
|
138
|
-
width += 1 // signColumnWidth
|
|
139
|
-
width += 1 // gutterPaddingRight (default)
|
|
140
|
-
return width
|
|
141
|
-
}
|
|
142
|
-
|
|
143
130
|
function formatCellValue(value: unknown): string {
|
|
144
131
|
if (value == null) return ""
|
|
145
132
|
if (typeof value === "string") return value
|
|
@@ -160,20 +147,20 @@ export function Table({
|
|
|
160
147
|
maxColumnWidth = DEFAULT_MAX_COL_WIDTH,
|
|
161
148
|
sampleSize = DEFAULT_SAMPLE_SIZE,
|
|
162
149
|
showLineNumbers = true,
|
|
163
|
-
|
|
164
|
-
selectionStart,
|
|
165
|
-
selectionEnd,
|
|
166
|
-
matchingRows,
|
|
167
|
-
currentMatchRow,
|
|
168
|
-
toggledRows,
|
|
150
|
+
marks,
|
|
169
151
|
docRef,
|
|
170
152
|
columnWidthMode = "content",
|
|
171
153
|
}: TableProps) {
|
|
172
154
|
const { theme } = useTheme()
|
|
155
|
+
const palette = useGutterPalette()
|
|
173
156
|
const { width: terminalWidth } = useTerminalDimensions()
|
|
174
157
|
|
|
175
158
|
// Compute available content width: start with total space, subtract margins and gutter
|
|
176
|
-
const gutterWidth =
|
|
159
|
+
const gutterWidth = computeRowDocumentGutterWidth({
|
|
160
|
+
showLineNumbers,
|
|
161
|
+
rowCount: rows.length,
|
|
162
|
+
signColumnWidth: 1,
|
|
163
|
+
})
|
|
177
164
|
const effectiveMaxWidth = Math.max(0, (maxWidth ?? terminalWidth) - MARGIN * 2 - gutterWidth)
|
|
178
165
|
|
|
179
166
|
const headers = columns.map((column) => column.header ?? column.key)
|
|
@@ -197,37 +184,16 @@ export function Table({
|
|
|
197
184
|
return numericCount > sampleValues.length / 2
|
|
198
185
|
})
|
|
199
186
|
|
|
200
|
-
const internalRef = useRef<RowDocumentRenderable>(null)
|
|
201
|
-
const effectiveRef = docRef ?? internalRef
|
|
202
|
-
|
|
203
|
-
const palette: RowDocumentPalette = {
|
|
204
|
-
gutterFg: theme.textMuted,
|
|
205
|
-
gutterBg: theme.backgroundElement,
|
|
206
|
-
cursorSignFg: theme.primary,
|
|
207
|
-
matchSignFg: theme.warning,
|
|
208
|
-
currentMatchSignFg: theme.primary,
|
|
209
|
-
cursorBg: theme.cursorLine,
|
|
210
|
-
selectionBg: theme.selection,
|
|
211
|
-
matchBg: theme.warning,
|
|
212
|
-
currentMatchBg: theme.primary,
|
|
213
|
-
toggledBg: theme.backgroundPanel,
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
useEffect(() => {
|
|
217
|
-
const decorations: RowDocumentDecorations = {
|
|
218
|
-
cursorRow: cursor,
|
|
219
|
-
selection: selectionStart != null && selectionEnd != null
|
|
220
|
-
? { start: selectionStart, end: selectionEnd }
|
|
221
|
-
: null,
|
|
222
|
-
matchingRows: matchingRows,
|
|
223
|
-
currentMatchRow: currentMatchRow,
|
|
224
|
-
toggledRows: toggledRows,
|
|
225
|
-
}
|
|
226
|
-
effectiveRef.current?.setDecorations(decorations)
|
|
227
|
-
}, [cursor, selectionStart, selectionEnd, matchingRows, currentMatchRow, toggledRows])
|
|
228
|
-
|
|
229
187
|
return (
|
|
230
|
-
<box
|
|
188
|
+
<box
|
|
189
|
+
style={{
|
|
190
|
+
flexDirection: "column",
|
|
191
|
+
flexGrow: 1,
|
|
192
|
+
marginLeft: MARGIN,
|
|
193
|
+
marginRight: MARGIN,
|
|
194
|
+
marginBottom: MARGIN,
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
231
197
|
{/* Fixed header row — outside row-document so it stays visible */}
|
|
232
198
|
<box style={{ flexDirection: "row", flexShrink: 0, paddingLeft: gutterWidth }}>
|
|
233
199
|
{headers.map((h, i) => (
|
|
@@ -254,13 +220,14 @@ export function Table({
|
|
|
254
220
|
|
|
255
221
|
{/* Scrollable data rows */}
|
|
256
222
|
<row-document
|
|
257
|
-
ref={
|
|
223
|
+
ref={docRef}
|
|
258
224
|
mode="multi"
|
|
259
225
|
rowChildOffset={0}
|
|
260
226
|
showGutter={true}
|
|
261
227
|
showLineNumbers={showLineNumbers}
|
|
262
228
|
signColumnWidth={1}
|
|
263
229
|
palette={palette}
|
|
230
|
+
decorations={marks?.sets}
|
|
264
231
|
style={{ flexGrow: 1 }}
|
|
265
232
|
>
|
|
266
233
|
{normalizedRows.map((row, i) => (
|
|
@@ -268,9 +235,10 @@ export function Table({
|
|
|
268
235
|
{row.map((cell, j) => {
|
|
269
236
|
const contentWidth = colWidths[j] - PADDING * 2
|
|
270
237
|
const cellWidth = Bun.stringWidth(cell)
|
|
271
|
-
const displayCell =
|
|
272
|
-
|
|
273
|
-
|
|
238
|
+
const displayCell =
|
|
239
|
+
alignments[j] && cellWidth <= contentWidth
|
|
240
|
+
? " ".repeat(contentWidth - cellWidth) + cell
|
|
241
|
+
: cell
|
|
274
242
|
return (
|
|
275
243
|
<text
|
|
276
244
|
key={j}
|
|
@@ -293,9 +261,5 @@ export function Table({
|
|
|
293
261
|
}
|
|
294
262
|
|
|
295
263
|
// Exported for testing and MarkdownView
|
|
296
|
-
export {
|
|
297
|
-
computeColumnWidths,
|
|
298
|
-
isNumeric,
|
|
299
|
-
sampleRows,
|
|
300
|
-
}
|
|
264
|
+
export { computeColumnWidths, isNumeric, sampleRows }
|
|
301
265
|
export type { ColumnWidthOptions }
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
export { MarkdownView } from "./MarkdownView.js"
|
|
1
|
+
export { MarkdownView, flattenTokens } from "./MarkdownView.js"
|
|
2
|
+
export type { FlatBlock } from "./MarkdownView.js"
|
|
2
3
|
export { CodeView } from "./CodeView.js"
|
|
3
4
|
export { ImageView } from "./ImageView.js"
|
|
4
|
-
export {
|
|
5
|
-
Table,
|
|
6
|
-
computeColumnWidths,
|
|
7
|
-
isNumeric,
|
|
8
|
-
sampleRows,
|
|
9
|
-
} from "./Table.js"
|
|
5
|
+
export { Table, computeColumnWidths, isNumeric, sampleRows } from "./Table.js"
|
|
10
6
|
export type { TableProps, ColumnWidthOptions } from "./Table.js"
|
|
11
7
|
export type { ColumnDef, TableRow } from "./table-types.js"
|
|
12
8
|
export { CommandPalette } from "./CommandPalette.js"
|
|
@@ -14,8 +10,10 @@ export type { CommandPaletteEntry } from "./CommandPalette.js"
|
|
|
14
10
|
export { parseCSV, parseTSV, parseJSON, parseAuto, detectFormat } from "./parsers.js"
|
|
15
11
|
export type { Format, ParsedTable } from "./parsers.js"
|
|
16
12
|
export { RowDocumentRenderable } from "./RowDocumentRenderable.js"
|
|
13
|
+
export { computeRowDocumentGutterWidth } from "./RowDocumentRenderable.js"
|
|
17
14
|
export type {
|
|
18
15
|
RowDocumentOptions,
|
|
19
16
|
RowDocumentPalette,
|
|
20
|
-
RowDocumentDecorations,
|
|
21
17
|
} from "./RowDocumentRenderable.js"
|
|
18
|
+
export type { DecorationLayer, RowDecoration } from "./DecorationLayer.js"
|
|
19
|
+
export { useGutterPalette } from "./useGutterPalette.js"
|
package/src/parsers.ts
CHANGED
|
@@ -56,7 +56,10 @@ export function parseTSV(input: string): { columns: ColumnDef[]; rows: TableRow[
|
|
|
56
56
|
const lines = splitLines(input)
|
|
57
57
|
if (lines.length === 0) return { columns: [], rows: [] }
|
|
58
58
|
const columns = createColumnDefs(lines[0].split("\t"))
|
|
59
|
-
const rows = buildRows(
|
|
59
|
+
const rows = buildRows(
|
|
60
|
+
columns,
|
|
61
|
+
lines.slice(1).map((line) => line.split("\t")),
|
|
62
|
+
)
|
|
60
63
|
return { columns, rows }
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useMemo } from "react"
|
|
2
|
+
import { useTheme } from "@tooee/themes"
|
|
3
|
+
import type { RowDocumentPalette } from "./RowDocumentRenderable.js"
|
|
4
|
+
|
|
5
|
+
export function useGutterPalette(): RowDocumentPalette {
|
|
6
|
+
const { theme } = useTheme()
|
|
7
|
+
|
|
8
|
+
return useMemo(
|
|
9
|
+
() => ({
|
|
10
|
+
gutterFg: theme.textMuted,
|
|
11
|
+
gutterBg: theme.backgroundElement,
|
|
12
|
+
}),
|
|
13
|
+
[theme.textMuted, theme.backgroundElement],
|
|
14
|
+
)
|
|
15
|
+
}
|