parquetlens 0.1.0
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/chunk-573AA4JN.js +112 -0
- package/dist/chunk-573AA4JN.js.map +1 -0
- package/dist/chunk-E6TEBKS4.js +166 -0
- package/dist/chunk-E6TEBKS4.js.map +1 -0
- package/dist/chunk-JOHKCQYH.js +99 -0
- package/dist/chunk-JOHKCQYH.js.map +1 -0
- package/dist/chunk-LHMHT2IQ.js +99 -0
- package/dist/chunk-LHMHT2IQ.js.map +1 -0
- package/dist/chunk-UUCD5YU4.js +92 -0
- package/dist/chunk-UUCD5YU4.js.map +1 -0
- package/dist/chunk-VFBGUOAH.js +92 -0
- package/dist/chunk-VFBGUOAH.js.map +1 -0
- package/dist/main.js +317 -0
- package/dist/main.js.map +1 -0
- package/dist/tui.js +576 -0
- package/dist/tui.js.map +1 -0
- package/package.json +43 -0
package/dist/tui.js
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname } from 'path';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
import {
|
|
8
|
+
openParquetBufferFromPath
|
|
9
|
+
} from "./chunk-573AA4JN.js";
|
|
10
|
+
|
|
11
|
+
// src/tui.tsx
|
|
12
|
+
import { createCliRenderer } from "@opentui/core";
|
|
13
|
+
import { createRoot, useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
14
|
+
import { useEffect, useMemo, useState } from "react";
|
|
15
|
+
import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
16
|
+
var TOP_BAR_LINES = 3;
|
|
17
|
+
var FOOTER_LINES = 4;
|
|
18
|
+
var TABLE_BORDER_LINES = 2;
|
|
19
|
+
var TABLE_HEADER_LINES = 3;
|
|
20
|
+
var RESERVED_LINES = TOP_BAR_LINES + FOOTER_LINES + TABLE_BORDER_LINES + TABLE_HEADER_LINES;
|
|
21
|
+
var DEFAULT_COLUMN_WIDTH = 6;
|
|
22
|
+
var MAX_COLUMN_WIDTH = 40;
|
|
23
|
+
var SCROLL_STEP = 3;
|
|
24
|
+
var SIDEBAR_WIDTH_RATIO = 0.35;
|
|
25
|
+
var SIDEBAR_MIN_WIDTH = 24;
|
|
26
|
+
var CONTENT_BORDER_WIDTH = 2;
|
|
27
|
+
var PANEL_GAP = 1;
|
|
28
|
+
var THEME = {
|
|
29
|
+
background: "#1e1f29",
|
|
30
|
+
panel: "#22232e",
|
|
31
|
+
header: "#2d2f3d",
|
|
32
|
+
border: "#44475a",
|
|
33
|
+
accent: "#bd93f9",
|
|
34
|
+
badge: "#50fa7b",
|
|
35
|
+
badgeText: "#1e1f29",
|
|
36
|
+
text: "#c5cee0",
|
|
37
|
+
muted: "#6272a4",
|
|
38
|
+
stripe: "#252733"
|
|
39
|
+
};
|
|
40
|
+
async function runTui(filePath, options) {
|
|
41
|
+
const source = await openParquetBufferFromPath(filePath);
|
|
42
|
+
const renderer = await createCliRenderer({
|
|
43
|
+
exitOnCtrlC: true,
|
|
44
|
+
useAlternateScreen: true,
|
|
45
|
+
useConsole: false,
|
|
46
|
+
useMouse: true,
|
|
47
|
+
enableMouseMovement: true
|
|
48
|
+
});
|
|
49
|
+
renderer.setTerminalTitle("parquetlens");
|
|
50
|
+
const root = createRoot(renderer);
|
|
51
|
+
const handleExit = () => {
|
|
52
|
+
root.unmount();
|
|
53
|
+
renderer.destroy();
|
|
54
|
+
};
|
|
55
|
+
root.render(/* @__PURE__ */ jsx(App, { source, filePath, options, onExit: handleExit }));
|
|
56
|
+
}
|
|
57
|
+
function App({ source, filePath, options, onExit }) {
|
|
58
|
+
const { width, height } = useTerminalDimensions();
|
|
59
|
+
const pageSize = Math.max(1, height - RESERVED_LINES);
|
|
60
|
+
const maxOffset = options.maxRows === void 0 ? void 0 : Math.max(0, options.maxRows - pageSize);
|
|
61
|
+
const [offset, setOffset] = useState(0);
|
|
62
|
+
const [xOffset, setXOffset] = useState(0);
|
|
63
|
+
const [grid, setGrid] = useState({ columns: [], rows: [] });
|
|
64
|
+
const [selection, setSelection] = useState(null);
|
|
65
|
+
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
66
|
+
const [loading, setLoading] = useState(true);
|
|
67
|
+
const [error, setError] = useState(null);
|
|
68
|
+
const [metadata, setMetadata] = useState(null);
|
|
69
|
+
const sidebarWidth = sidebarOpen ? Math.min(width, Math.max(SIDEBAR_MIN_WIDTH, Math.floor(width * SIDEBAR_WIDTH_RATIO))) : 0;
|
|
70
|
+
const tableWidth = Math.max(0, width - (sidebarOpen ? sidebarWidth + PANEL_GAP : 0));
|
|
71
|
+
const tableContentWidth = Math.max(0, tableWidth - CONTENT_BORDER_WIDTH);
|
|
72
|
+
const columnsToRead = options.columns;
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
let canceled = false;
|
|
75
|
+
const loadWindow = async () => {
|
|
76
|
+
setLoading(true);
|
|
77
|
+
setError(null);
|
|
78
|
+
try {
|
|
79
|
+
const limit = options.maxRows ? Math.max(0, Math.min(pageSize, options.maxRows - offset)) : pageSize;
|
|
80
|
+
const readOptions = {
|
|
81
|
+
batchSize: options.batchSize ?? 1024,
|
|
82
|
+
columns: columnsToRead.length > 0 ? columnsToRead : void 0,
|
|
83
|
+
limit,
|
|
84
|
+
offset
|
|
85
|
+
};
|
|
86
|
+
const table = await source.readTable(readOptions);
|
|
87
|
+
const columns = table.schema.fields.map((field) => ({
|
|
88
|
+
name: field.name,
|
|
89
|
+
type: formatArrowType(field.type)
|
|
90
|
+
}));
|
|
91
|
+
const rows = tableToRows(
|
|
92
|
+
table,
|
|
93
|
+
columns.map((c) => c.name)
|
|
94
|
+
);
|
|
95
|
+
if (!canceled) {
|
|
96
|
+
setGrid({ columns, rows });
|
|
97
|
+
}
|
|
98
|
+
} catch (caught) {
|
|
99
|
+
const message = caught instanceof Error ? caught.message : String(caught);
|
|
100
|
+
if (!canceled) {
|
|
101
|
+
setError(message);
|
|
102
|
+
}
|
|
103
|
+
} finally {
|
|
104
|
+
if (!canceled) {
|
|
105
|
+
setLoading(false);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
loadWindow();
|
|
110
|
+
return () => {
|
|
111
|
+
canceled = true;
|
|
112
|
+
};
|
|
113
|
+
}, [columnsToRead, offset, options.batchSize, options.maxRows, pageSize, source]);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
let canceled = false;
|
|
116
|
+
source.readMetadata().then((meta) => {
|
|
117
|
+
if (!canceled) {
|
|
118
|
+
setMetadata(meta);
|
|
119
|
+
}
|
|
120
|
+
}).catch(() => {
|
|
121
|
+
if (!canceled) {
|
|
122
|
+
setMetadata(null);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
return () => {
|
|
126
|
+
canceled = true;
|
|
127
|
+
};
|
|
128
|
+
}, [source]);
|
|
129
|
+
const gridLines = useMemo(
|
|
130
|
+
() => buildGridLines(grid, offset, tableContentWidth),
|
|
131
|
+
[grid, offset, tableContentWidth]
|
|
132
|
+
);
|
|
133
|
+
const maxScrollX = Math.max(0, gridLines.maxLineLength - tableContentWidth);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
setXOffset((current) => Math.min(current, maxScrollX));
|
|
136
|
+
}, [gridLines.maxLineLength, maxScrollX, tableContentWidth]);
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (grid.rows.length === 0 || grid.columns.length === 0) {
|
|
139
|
+
setSelection(null);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
setSelection((current) => {
|
|
143
|
+
const nextRow = current ? Math.min(current.row, grid.rows.length - 1) : 0;
|
|
144
|
+
const nextCol = current ? Math.min(current.col, grid.columns.length - 1) : 0;
|
|
145
|
+
return { row: nextRow, col: nextCol };
|
|
146
|
+
});
|
|
147
|
+
}, [grid.columns.length, grid.rows.length]);
|
|
148
|
+
useKeyboard((key) => {
|
|
149
|
+
if (key.ctrl && key.name === "c" || key.name === "escape" || key.name === "q") {
|
|
150
|
+
if (sidebarOpen && key.name === "escape") {
|
|
151
|
+
setSidebarOpen(false);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
onExit();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const clampOffset = (value) => {
|
|
158
|
+
const clamped = Math.max(0, value);
|
|
159
|
+
return maxOffset === void 0 ? clamped : Math.min(clamped, maxOffset);
|
|
160
|
+
};
|
|
161
|
+
if (key.name === "down" || key.name === "j") {
|
|
162
|
+
setOffset((current) => clampOffset(current + 1));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (key.name === "up" || key.name === "k") {
|
|
166
|
+
setOffset((current) => clampOffset(current - 1));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (key.name === "pagedown" || key.name === "space") {
|
|
170
|
+
setOffset((current) => clampOffset(current + pageSize));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (key.name === "pageup") {
|
|
174
|
+
setOffset((current) => clampOffset(current - pageSize));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (key.name === "home" || key.name === "g" && !key.shift) {
|
|
178
|
+
setOffset(0);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if ((key.name === "end" || key.name === "g" && key.shift) && maxOffset !== void 0) {
|
|
182
|
+
setOffset(maxOffset);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (key.name === "return" || key.name === "enter") {
|
|
186
|
+
setSidebarOpen((current) => !current);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (key.name === "x") {
|
|
190
|
+
setSidebarOpen(false);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (key.name === "s") {
|
|
194
|
+
setSidebarOpen((current) => !current);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (key.name === "left" || key.name === "h") {
|
|
198
|
+
setXOffset(
|
|
199
|
+
(current) => clampScroll(findScrollStop(current, gridLines.scrollStops, -1), maxScrollX)
|
|
200
|
+
);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (key.name === "right" || key.name === "l") {
|
|
204
|
+
setXOffset(
|
|
205
|
+
(current) => clampScroll(findScrollStop(current, gridLines.scrollStops, 1), maxScrollX)
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
const visibleLines = applyHorizontalScroll(gridLines, tableContentWidth, xOffset, pageSize);
|
|
210
|
+
const detail = buildDetail(selection, grid, offset);
|
|
211
|
+
const metaFlags = getMetadataFlags(metadata);
|
|
212
|
+
return /* @__PURE__ */ jsxs("box", { flexDirection: "column", width: "100%", height: "100%", backgroundColor: THEME.background, children: [
|
|
213
|
+
/* @__PURE__ */ jsx("box", { backgroundColor: THEME.header, border: true, borderColor: THEME.border, children: renderHeader({
|
|
214
|
+
filePath,
|
|
215
|
+
offset,
|
|
216
|
+
rows: grid.rows.length,
|
|
217
|
+
columns: grid.columns.length,
|
|
218
|
+
loading,
|
|
219
|
+
error,
|
|
220
|
+
maxRows: options.maxRows,
|
|
221
|
+
optimized: metaFlags.optimized,
|
|
222
|
+
createdBy: metaFlags.createdBy
|
|
223
|
+
}) }),
|
|
224
|
+
/* @__PURE__ */ jsxs("box", { flexGrow: 1, flexDirection: "row", gap: PANEL_GAP, children: [
|
|
225
|
+
/* @__PURE__ */ jsxs(
|
|
226
|
+
"box",
|
|
227
|
+
{
|
|
228
|
+
backgroundColor: THEME.panel,
|
|
229
|
+
border: true,
|
|
230
|
+
borderColor: THEME.border,
|
|
231
|
+
flexGrow: 1,
|
|
232
|
+
width: sidebarOpen ? tableWidth : "100%",
|
|
233
|
+
onMouseScroll: (event) => {
|
|
234
|
+
if (!event.scroll) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const delta = Math.max(1, event.scroll.delta);
|
|
238
|
+
const step = delta * SCROLL_STEP;
|
|
239
|
+
if (event.scroll.direction === "up") {
|
|
240
|
+
setOffset((current) => Math.max(0, current - step));
|
|
241
|
+
} else if (event.scroll.direction === "down") {
|
|
242
|
+
setOffset((current) => {
|
|
243
|
+
const next = current + step;
|
|
244
|
+
return maxOffset === void 0 ? next : Math.min(next, maxOffset);
|
|
245
|
+
});
|
|
246
|
+
} else if (event.scroll.direction === "left") {
|
|
247
|
+
setXOffset((current) => clampScroll(current - step, maxScrollX));
|
|
248
|
+
} else if (event.scroll.direction === "right") {
|
|
249
|
+
setXOffset((current) => clampScroll(current + step, maxScrollX));
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
children: [
|
|
253
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", truncate: true, fg: THEME.accent, children: visibleLines.headerName }),
|
|
254
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", truncate: true, fg: THEME.muted, children: visibleLines.headerType }),
|
|
255
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", truncate: true, fg: THEME.border, children: visibleLines.separator }),
|
|
256
|
+
visibleLines.rows.map((line, index) => {
|
|
257
|
+
const isSelected = selection?.row === index;
|
|
258
|
+
return /* @__PURE__ */ jsx(
|
|
259
|
+
"text",
|
|
260
|
+
{
|
|
261
|
+
wrapMode: "none",
|
|
262
|
+
truncate: true,
|
|
263
|
+
fg: THEME.text,
|
|
264
|
+
bg: isSelected ? THEME.header : index % 2 === 0 ? THEME.background : THEME.stripe,
|
|
265
|
+
onMouseDown: (event) => {
|
|
266
|
+
const target = event.target;
|
|
267
|
+
if (!target) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const localX = Math.max(0, event.x - target.x);
|
|
271
|
+
const absoluteX = localX + xOffset;
|
|
272
|
+
const colIndex = findColumnIndex(absoluteX, gridLines.columnRanges);
|
|
273
|
+
if (colIndex >= 0) {
|
|
274
|
+
setSelection({ row: index, col: colIndex });
|
|
275
|
+
setSidebarOpen(true);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
children: line
|
|
279
|
+
},
|
|
280
|
+
`row-${index}`
|
|
281
|
+
);
|
|
282
|
+
})
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
),
|
|
286
|
+
sidebarOpen ? /* @__PURE__ */ jsxs(
|
|
287
|
+
"box",
|
|
288
|
+
{
|
|
289
|
+
width: "35%",
|
|
290
|
+
minWidth: 24,
|
|
291
|
+
backgroundColor: THEME.panel,
|
|
292
|
+
border: true,
|
|
293
|
+
borderColor: THEME.border,
|
|
294
|
+
title: "cell detail",
|
|
295
|
+
titleAlignment: "left",
|
|
296
|
+
children: [
|
|
297
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", truncate: true, fg: THEME.muted, children: "press esc/x to close" }),
|
|
298
|
+
/* @__PURE__ */ jsx("scrollbox", { scrollY: true, flexGrow: 1, backgroundColor: THEME.panel, children: /* @__PURE__ */ jsx(
|
|
299
|
+
"text",
|
|
300
|
+
{
|
|
301
|
+
wrapMode: "word",
|
|
302
|
+
fg: THEME.text,
|
|
303
|
+
selectable: true,
|
|
304
|
+
selectionBg: THEME.accent,
|
|
305
|
+
selectionFg: THEME.background,
|
|
306
|
+
children: detail
|
|
307
|
+
}
|
|
308
|
+
) }),
|
|
309
|
+
/* @__PURE__ */ jsx(
|
|
310
|
+
"text",
|
|
311
|
+
{
|
|
312
|
+
wrapMode: "none",
|
|
313
|
+
truncate: true,
|
|
314
|
+
fg: THEME.accent,
|
|
315
|
+
onMouseDown: () => {
|
|
316
|
+
setSidebarOpen(false);
|
|
317
|
+
},
|
|
318
|
+
children: "[ close ]"
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
) : null
|
|
324
|
+
] }),
|
|
325
|
+
/* @__PURE__ */ jsx("box", { backgroundColor: THEME.header, border: true, borderColor: THEME.border, children: renderFooter() })
|
|
326
|
+
] });
|
|
327
|
+
}
|
|
328
|
+
function renderHeader(props) {
|
|
329
|
+
const { filePath, offset, rows, columns, loading, error, maxRows, createdBy, optimized } = props;
|
|
330
|
+
const start = rows > 0 ? offset + 1 : offset;
|
|
331
|
+
const end = rows > 0 ? offset + rows : offset;
|
|
332
|
+
const totalText = maxRows !== void 0 ? `of ${maxRows.toLocaleString()}` : "";
|
|
333
|
+
const fileName = filePath.split("/").pop() ?? filePath;
|
|
334
|
+
if (error) {
|
|
335
|
+
return /* @__PURE__ */ jsxs("box", { flexDirection: "row", alignItems: "center", gap: 2, width: "100%", children: [
|
|
336
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.accent, children: "\u25C8 parquetlens" }),
|
|
337
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: "#ef4444", children: "error: " + error })
|
|
338
|
+
] });
|
|
339
|
+
}
|
|
340
|
+
return /* @__PURE__ */ jsxs("box", { flexDirection: "row", alignItems: "center", gap: 2, width: "100%", children: [
|
|
341
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.accent, children: "\u25C8 parquetlens" }),
|
|
342
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.muted, children: "\u2502" }),
|
|
343
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.text, children: fileName }),
|
|
344
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.muted, children: "\u2502" }),
|
|
345
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.muted, children: "rows " }),
|
|
346
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.text, children: `${start.toLocaleString()}-${end.toLocaleString()} ${totalText}` }),
|
|
347
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.muted, children: "\u2502" }),
|
|
348
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.muted, children: "cols " }),
|
|
349
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.text, children: columns.toString() }),
|
|
350
|
+
createdBy ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
351
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.muted, children: "\u2502" }),
|
|
352
|
+
/* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.muted, truncate: true, children: createdBy })
|
|
353
|
+
] }) : null,
|
|
354
|
+
/* @__PURE__ */ jsx("box", { flexGrow: 1 }),
|
|
355
|
+
loading ? /* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.badge, children: "\u25CF loading" }) : null,
|
|
356
|
+
optimized ? /* @__PURE__ */ jsx("text", { wrapMode: "none", fg: THEME.badgeText, bg: THEME.badge, children: " \u2713 OPTIMIZED " }) : null
|
|
357
|
+
] });
|
|
358
|
+
}
|
|
359
|
+
function renderFooterLine() {
|
|
360
|
+
return "q exit | arrows/jk scroll | pgup/pgdn page | h/l col jump | mouse wheel scroll | click cell for detail | s/enter toggle panel";
|
|
361
|
+
}
|
|
362
|
+
function renderFooter() {
|
|
363
|
+
const controls = renderFooterLine();
|
|
364
|
+
return /* @__PURE__ */ jsx("box", { flexDirection: "column", width: "100%", children: /* @__PURE__ */ jsx("text", { wrapMode: "none", truncate: true, fg: THEME.muted, children: controls }) });
|
|
365
|
+
}
|
|
366
|
+
function buildGridLines(grid, offset, targetWidth) {
|
|
367
|
+
const columns = grid.columns.length > 0 ? grid.columns : [{ name: "(loading)", type: "" }];
|
|
368
|
+
const rows = grid.rows;
|
|
369
|
+
const rowNumberWidth = Math.max(String(offset + rows.length).length, 3);
|
|
370
|
+
const columnWidths = columns.map((col, index) => {
|
|
371
|
+
const longestCell = rows.reduce(
|
|
372
|
+
(max, row) => {
|
|
373
|
+
const value = row[index] ?? "";
|
|
374
|
+
return Math.max(max, value.length);
|
|
375
|
+
},
|
|
376
|
+
Math.max(col.name.length, col.type.length)
|
|
377
|
+
);
|
|
378
|
+
return Math.min(Math.max(longestCell, DEFAULT_COLUMN_WIDTH), MAX_COLUMN_WIDTH);
|
|
379
|
+
});
|
|
380
|
+
const headerNames = ["#", ...columns.map((c) => c.name)];
|
|
381
|
+
const headerTypes = ["", ...columns.map((c) => c.type)];
|
|
382
|
+
const headerWidths = [rowNumberWidth, ...columnWidths];
|
|
383
|
+
const separatorWidth = headerWidths.length > 1 ? (headerWidths.length - 1) * 2 : 0;
|
|
384
|
+
const baseLength = headerWidths.reduce((total, width) => total + width, 0) + separatorWidth;
|
|
385
|
+
if (targetWidth > baseLength && headerWidths.length > 1) {
|
|
386
|
+
headerWidths[headerWidths.length - 1] += targetWidth - baseLength;
|
|
387
|
+
}
|
|
388
|
+
const headerNameLine = buildLine(headerNames, headerWidths);
|
|
389
|
+
const headerTypeLine = buildLine(headerTypes, headerWidths);
|
|
390
|
+
const separatorLine = buildSeparator(headerWidths);
|
|
391
|
+
const rowLines = rows.map((row, index) => {
|
|
392
|
+
const rowIndex = String(offset + index + 1);
|
|
393
|
+
const values = [rowIndex, ...row];
|
|
394
|
+
return buildLine(values, headerWidths);
|
|
395
|
+
});
|
|
396
|
+
const { columnRanges, scrollStops } = buildColumnRanges(headerWidths);
|
|
397
|
+
const maxLineLength = Math.max(
|
|
398
|
+
headerNameLine.length,
|
|
399
|
+
headerTypeLine.length,
|
|
400
|
+
separatorLine.length,
|
|
401
|
+
...rowLines.map((line) => line.length)
|
|
402
|
+
);
|
|
403
|
+
return {
|
|
404
|
+
headerNameLine,
|
|
405
|
+
headerTypeLine,
|
|
406
|
+
separatorLine,
|
|
407
|
+
rowLines,
|
|
408
|
+
maxLineLength,
|
|
409
|
+
columnRanges,
|
|
410
|
+
scrollStops
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function applyHorizontalScroll(lines, width, xOffset, pageSize) {
|
|
414
|
+
const sliceLine = (line) => {
|
|
415
|
+
if (width <= 0) {
|
|
416
|
+
return "";
|
|
417
|
+
}
|
|
418
|
+
const sliced = xOffset <= 0 ? line.slice(0, width) : line.slice(xOffset, xOffset + width);
|
|
419
|
+
return sliced.padEnd(width, " ");
|
|
420
|
+
};
|
|
421
|
+
const rows = lines.rowLines.slice(0, pageSize).map(sliceLine);
|
|
422
|
+
const emptyRow = width > 0 ? " ".repeat(width) : "";
|
|
423
|
+
while (rows.length < pageSize) {
|
|
424
|
+
rows.push(emptyRow);
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
headerName: sliceLine(lines.headerNameLine),
|
|
428
|
+
headerType: sliceLine(lines.headerTypeLine),
|
|
429
|
+
separator: sliceLine(lines.separatorLine),
|
|
430
|
+
rows
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function buildLine(values, widths) {
|
|
434
|
+
return values.map((value, index) => {
|
|
435
|
+
const width = widths[index] ?? DEFAULT_COLUMN_WIDTH;
|
|
436
|
+
return padCell(value, width);
|
|
437
|
+
}).join(" ");
|
|
438
|
+
}
|
|
439
|
+
function buildSeparator(widths) {
|
|
440
|
+
return "";
|
|
441
|
+
}
|
|
442
|
+
function padCell(value, width) {
|
|
443
|
+
const normalized = normalizeCell(value);
|
|
444
|
+
if (normalized.length > width) {
|
|
445
|
+
if (width <= 3) {
|
|
446
|
+
return normalized.slice(0, width);
|
|
447
|
+
}
|
|
448
|
+
return `${normalized.slice(0, width - 3)}...`;
|
|
449
|
+
}
|
|
450
|
+
return normalized.padEnd(width, " ");
|
|
451
|
+
}
|
|
452
|
+
function normalizeCell(value) {
|
|
453
|
+
return value.replace(/\r?\n/g, "\\n").replace(/\t/g, "\\t");
|
|
454
|
+
}
|
|
455
|
+
function buildColumnRanges(widths) {
|
|
456
|
+
const columnRanges = [];
|
|
457
|
+
const scrollStops = [0];
|
|
458
|
+
let cursor = 0;
|
|
459
|
+
for (let i = 0; i < widths.length; i += 1) {
|
|
460
|
+
const width = widths[i];
|
|
461
|
+
if (i > 0) {
|
|
462
|
+
const start = cursor;
|
|
463
|
+
const end = cursor + width - 1;
|
|
464
|
+
columnRanges.push({ start, end });
|
|
465
|
+
scrollStops.push(start);
|
|
466
|
+
}
|
|
467
|
+
cursor += width;
|
|
468
|
+
if (i < widths.length - 1) {
|
|
469
|
+
cursor += 2;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return { columnRanges, scrollStops };
|
|
473
|
+
}
|
|
474
|
+
function findColumnIndex(x, ranges) {
|
|
475
|
+
for (let index = 0; index < ranges.length; index += 1) {
|
|
476
|
+
const range = ranges[index];
|
|
477
|
+
if (x >= range.start && x <= range.end) {
|
|
478
|
+
return index;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return -1;
|
|
482
|
+
}
|
|
483
|
+
function findScrollStop(current, stops, direction) {
|
|
484
|
+
if (direction > 0) {
|
|
485
|
+
for (const stop of stops) {
|
|
486
|
+
if (stop > current) {
|
|
487
|
+
return stop;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return current;
|
|
491
|
+
}
|
|
492
|
+
for (let index = stops.length - 1; index >= 0; index -= 1) {
|
|
493
|
+
const stop = stops[index];
|
|
494
|
+
if (stop < current) {
|
|
495
|
+
return stop;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return 0;
|
|
499
|
+
}
|
|
500
|
+
function clampScroll(value, max) {
|
|
501
|
+
if (value < 0) {
|
|
502
|
+
return 0;
|
|
503
|
+
}
|
|
504
|
+
if (value > max) {
|
|
505
|
+
return max;
|
|
506
|
+
}
|
|
507
|
+
return value;
|
|
508
|
+
}
|
|
509
|
+
function buildDetail(selection, grid, offset) {
|
|
510
|
+
if (!selection || grid.columns.length === 0 || grid.rows.length === 0) {
|
|
511
|
+
return "click a cell to see full details";
|
|
512
|
+
}
|
|
513
|
+
const rowIndex = Math.min(selection.row, grid.rows.length - 1);
|
|
514
|
+
const colIndex = Math.min(selection.col, grid.columns.length - 1);
|
|
515
|
+
const col = grid.columns[colIndex];
|
|
516
|
+
const columnName = col?.name ?? "(unknown)";
|
|
517
|
+
const columnType = col?.type ?? "";
|
|
518
|
+
const value = grid.rows[rowIndex]?.[colIndex] ?? "";
|
|
519
|
+
const absoluteRow = offset + rowIndex + 1;
|
|
520
|
+
return `row ${absoluteRow} \u2022 ${columnName}
|
|
521
|
+
${columnType}
|
|
522
|
+
|
|
523
|
+
${value}`;
|
|
524
|
+
}
|
|
525
|
+
function getMetadataFlags(metadata) {
|
|
526
|
+
if (!metadata) {
|
|
527
|
+
return { optimized: false };
|
|
528
|
+
}
|
|
529
|
+
const raw = metadata.keyValueMetadata["content_defined_chunking"];
|
|
530
|
+
const normalized = raw?.toLowerCase?.() ?? "";
|
|
531
|
+
const optimized = raw !== void 0 && raw !== null && normalized !== "false" && normalized !== "0";
|
|
532
|
+
return {
|
|
533
|
+
optimized,
|
|
534
|
+
createdBy: metadata.createdBy
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function tableToRows(table, columns) {
|
|
538
|
+
const rows = [];
|
|
539
|
+
for (const batch of table.batches) {
|
|
540
|
+
const vectors = columns.map((_, index) => batch.getChildAt(index));
|
|
541
|
+
for (let rowIndex = 0; rowIndex < batch.numRows; rowIndex += 1) {
|
|
542
|
+
const row = vectors.map((vector) => formatCell(vector?.get(rowIndex)));
|
|
543
|
+
rows.push(row);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return rows;
|
|
547
|
+
}
|
|
548
|
+
function formatCell(value) {
|
|
549
|
+
if (value === null || value === void 0) {
|
|
550
|
+
return "";
|
|
551
|
+
}
|
|
552
|
+
if (typeof value === "bigint") {
|
|
553
|
+
return value.toString();
|
|
554
|
+
}
|
|
555
|
+
if (value instanceof Date) {
|
|
556
|
+
return value.toISOString();
|
|
557
|
+
}
|
|
558
|
+
if (value instanceof Uint8Array) {
|
|
559
|
+
return `Uint8Array(${value.length})`;
|
|
560
|
+
}
|
|
561
|
+
if (typeof value === "object") {
|
|
562
|
+
try {
|
|
563
|
+
return JSON.stringify(value);
|
|
564
|
+
} catch {
|
|
565
|
+
return String(value);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return String(value);
|
|
569
|
+
}
|
|
570
|
+
function formatArrowType(type) {
|
|
571
|
+
return type.toString();
|
|
572
|
+
}
|
|
573
|
+
export {
|
|
574
|
+
runTui
|
|
575
|
+
};
|
|
576
|
+
//# sourceMappingURL=tui.js.map
|
package/dist/tui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tui.tsx"],"sourcesContent":["import { createCliRenderer } from \"@opentui/core\";\nimport { createRoot, useKeyboard, useTerminalDimensions } from \"@opentui/react\";\nimport React, { useEffect, useMemo, useState } from \"react\";\n\nimport type {\n ParquetBufferSource,\n ParquetFileMetadata,\n ParquetReadOptions,\n} from \"@parquetlens/parquet-reader\";\nimport { openParquetBufferFromPath } from \"@parquetlens/parquet-reader\";\n\ntype TuiOptions = {\n columns: string[];\n maxRows?: number;\n batchSize?: number;\n};\n\ntype ColumnInfo = {\n name: string;\n type: string;\n};\n\ntype GridState = {\n columns: ColumnInfo[];\n rows: string[][];\n};\n\ntype GridLines = {\n headerNameLine: string;\n headerTypeLine: string;\n separatorLine: string;\n rowLines: string[];\n maxLineLength: number;\n columnRanges: Array<{ start: number; end: number }>;\n scrollStops: number[];\n};\n\nconst TOP_BAR_LINES = 3;\nconst FOOTER_LINES = 4;\nconst TABLE_BORDER_LINES = 2;\nconst TABLE_HEADER_LINES = 3;\nconst RESERVED_LINES = TOP_BAR_LINES + FOOTER_LINES + TABLE_BORDER_LINES + TABLE_HEADER_LINES;\nconst DEFAULT_COLUMN_WIDTH = 6;\nconst MAX_COLUMN_WIDTH = 40;\nconst SCROLL_STEP = 3;\nconst SIDEBAR_WIDTH_RATIO = 0.35;\nconst SIDEBAR_MIN_WIDTH = 24;\nconst CONTENT_BORDER_WIDTH = 2;\nconst PANEL_GAP = 1;\n\nconst THEME = {\n background: \"#1e1f29\",\n panel: \"#22232e\",\n header: \"#2d2f3d\",\n border: \"#44475a\",\n accent: \"#bd93f9\",\n badge: \"#50fa7b\",\n badgeText: \"#1e1f29\",\n text: \"#c5cee0\",\n muted: \"#6272a4\",\n stripe: \"#252733\",\n};\n\nexport async function runTui(filePath: string, options: TuiOptions): Promise<void> {\n const source = await openParquetBufferFromPath(filePath);\n const renderer = await createCliRenderer({\n exitOnCtrlC: true,\n useAlternateScreen: true,\n useConsole: false,\n useMouse: true,\n enableMouseMovement: true,\n });\n renderer.setTerminalTitle(\"parquetlens\");\n const root = createRoot(renderer);\n\n const handleExit = () => {\n root.unmount();\n renderer.destroy();\n };\n\n root.render(<App source={source} filePath={filePath} options={options} onExit={handleExit} />);\n}\n\ntype AppProps = {\n source: ParquetBufferSource;\n filePath: string;\n options: TuiOptions;\n onExit: () => void;\n};\n\nfunction App({ source, filePath, options, onExit }: AppProps) {\n const { width, height } = useTerminalDimensions();\n const pageSize = Math.max(1, height - RESERVED_LINES);\n const maxOffset =\n options.maxRows === undefined ? undefined : Math.max(0, options.maxRows - pageSize);\n\n const [offset, setOffset] = useState(0);\n const [xOffset, setXOffset] = useState(0);\n const [grid, setGrid] = useState<GridState>({ columns: [], rows: [] } as GridState);\n const [selection, setSelection] = useState<{ row: number; col: number } | null>(null);\n const [sidebarOpen, setSidebarOpen] = useState(false);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [metadata, setMetadata] = useState<ParquetFileMetadata | null>(null);\n const sidebarWidth = sidebarOpen\n ? Math.min(width, Math.max(SIDEBAR_MIN_WIDTH, Math.floor(width * SIDEBAR_WIDTH_RATIO)))\n : 0;\n const tableWidth = Math.max(0, width - (sidebarOpen ? sidebarWidth + PANEL_GAP : 0));\n const tableContentWidth = Math.max(0, tableWidth - CONTENT_BORDER_WIDTH);\n\n const columnsToRead = options.columns;\n\n useEffect(() => {\n let canceled = false;\n\n const loadWindow = async () => {\n setLoading(true);\n setError(null);\n try {\n const limit = options.maxRows\n ? Math.max(0, Math.min(pageSize, options.maxRows - offset))\n : pageSize;\n const readOptions: ParquetReadOptions = {\n batchSize: options.batchSize ?? 1024,\n columns: columnsToRead.length > 0 ? columnsToRead : undefined,\n limit,\n offset,\n };\n const table = await source.readTable(readOptions);\n const columns: ColumnInfo[] = table.schema.fields.map((field) => ({\n name: field.name,\n type: formatArrowType(field.type),\n }));\n const rows = tableToRows(\n table,\n columns.map((c) => c.name),\n );\n\n if (!canceled) {\n setGrid({ columns, rows });\n }\n } catch (caught) {\n const message = caught instanceof Error ? caught.message : String(caught);\n if (!canceled) {\n setError(message);\n }\n } finally {\n if (!canceled) {\n setLoading(false);\n }\n }\n };\n\n loadWindow();\n\n return () => {\n canceled = true;\n };\n }, [columnsToRead, offset, options.batchSize, options.maxRows, pageSize, source]);\n\n useEffect(() => {\n let canceled = false;\n source\n .readMetadata()\n .then((meta) => {\n if (!canceled) {\n setMetadata(meta);\n }\n })\n .catch(() => {\n if (!canceled) {\n setMetadata(null);\n }\n });\n\n return () => {\n canceled = true;\n };\n }, [source]);\n\n const gridLines = useMemo(\n () => buildGridLines(grid, offset, tableContentWidth),\n [grid, offset, tableContentWidth],\n );\n const maxScrollX = Math.max(0, gridLines.maxLineLength - tableContentWidth);\n\n useEffect(() => {\n setXOffset((current) => Math.min(current, maxScrollX));\n }, [gridLines.maxLineLength, maxScrollX, tableContentWidth]);\n\n useEffect(() => {\n if (grid.rows.length === 0 || grid.columns.length === 0) {\n setSelection(null);\n return;\n }\n\n setSelection((current) => {\n const nextRow = current ? Math.min(current.row, grid.rows.length - 1) : 0;\n const nextCol = current ? Math.min(current.col, grid.columns.length - 1) : 0;\n return { row: nextRow, col: nextCol };\n });\n }, [grid.columns.length, grid.rows.length]);\n\n useKeyboard((key) => {\n if ((key.ctrl && key.name === \"c\") || key.name === \"escape\" || key.name === \"q\") {\n if (sidebarOpen && key.name === \"escape\") {\n setSidebarOpen(false);\n return;\n }\n onExit();\n return;\n }\n\n const clampOffset = (value: number) => {\n const clamped = Math.max(0, value);\n return maxOffset === undefined ? clamped : Math.min(clamped, maxOffset);\n };\n\n if (key.name === \"down\" || key.name === \"j\") {\n setOffset((current) => clampOffset(current + 1));\n return;\n }\n\n if (key.name === \"up\" || key.name === \"k\") {\n setOffset((current) => clampOffset(current - 1));\n return;\n }\n\n if (key.name === \"pagedown\" || key.name === \"space\") {\n setOffset((current) => clampOffset(current + pageSize));\n return;\n }\n\n if (key.name === \"pageup\") {\n setOffset((current) => clampOffset(current - pageSize));\n return;\n }\n\n if (key.name === \"home\" || (key.name === \"g\" && !key.shift)) {\n setOffset(0);\n return;\n }\n\n if ((key.name === \"end\" || (key.name === \"g\" && key.shift)) && maxOffset !== undefined) {\n setOffset(maxOffset);\n return;\n }\n\n if (key.name === \"return\" || key.name === \"enter\") {\n setSidebarOpen((current) => !current);\n return;\n }\n\n if (key.name === \"x\") {\n setSidebarOpen(false);\n return;\n }\n\n if (key.name === \"s\") {\n setSidebarOpen((current) => !current);\n return;\n }\n\n if (key.name === \"left\" || key.name === \"h\") {\n setXOffset((current) =>\n clampScroll(findScrollStop(current, gridLines.scrollStops, -1), maxScrollX),\n );\n return;\n }\n\n if (key.name === \"right\" || key.name === \"l\") {\n setXOffset((current) =>\n clampScroll(findScrollStop(current, gridLines.scrollStops, 1), maxScrollX),\n );\n }\n });\n\n const visibleLines = applyHorizontalScroll(gridLines, tableContentWidth, xOffset, pageSize);\n const detail = buildDetail(selection, grid, offset);\n const metaFlags = getMetadataFlags(metadata);\n\n return (\n <box flexDirection=\"column\" width=\"100%\" height=\"100%\" backgroundColor={THEME.background}>\n <box backgroundColor={THEME.header} border borderColor={THEME.border}>\n {renderHeader({\n filePath,\n offset,\n rows: grid.rows.length,\n columns: grid.columns.length,\n loading,\n error,\n maxRows: options.maxRows,\n optimized: metaFlags.optimized,\n createdBy: metaFlags.createdBy,\n })}\n </box>\n <box flexGrow={1} flexDirection=\"row\" gap={PANEL_GAP}>\n <box\n backgroundColor={THEME.panel}\n border\n borderColor={THEME.border}\n flexGrow={1}\n width={sidebarOpen ? tableWidth : \"100%\"}\n onMouseScroll={(event) => {\n if (!event.scroll) {\n return;\n }\n\n const delta = Math.max(1, event.scroll.delta);\n const step = delta * SCROLL_STEP;\n\n if (event.scroll.direction === \"up\") {\n setOffset((current) => Math.max(0, current - step));\n } else if (event.scroll.direction === \"down\") {\n setOffset((current) => {\n const next = current + step;\n return maxOffset === undefined ? next : Math.min(next, maxOffset);\n });\n } else if (event.scroll.direction === \"left\") {\n setXOffset((current) => clampScroll(current - step, maxScrollX));\n } else if (event.scroll.direction === \"right\") {\n setXOffset((current) => clampScroll(current + step, maxScrollX));\n }\n }}\n >\n <text wrapMode=\"none\" truncate fg={THEME.accent}>\n {visibleLines.headerName}\n </text>\n <text wrapMode=\"none\" truncate fg={THEME.muted}>\n {visibleLines.headerType}\n </text>\n <text wrapMode=\"none\" truncate fg={THEME.border}>\n {visibleLines.separator}\n </text>\n {visibleLines.rows.map((line, index) => {\n const isSelected = selection?.row === index;\n return (\n <text\n key={`row-${index}`}\n wrapMode=\"none\"\n truncate\n fg={THEME.text}\n bg={isSelected ? THEME.header : index % 2 === 0 ? THEME.background : THEME.stripe}\n onMouseDown={(event) => {\n const target = event.target;\n if (!target) {\n return;\n }\n const localX = Math.max(0, event.x - target.x);\n const absoluteX = localX + xOffset;\n const colIndex = findColumnIndex(absoluteX, gridLines.columnRanges);\n if (colIndex >= 0) {\n setSelection({ row: index, col: colIndex });\n setSidebarOpen(true);\n }\n }}\n >\n {line}\n </text>\n );\n })}\n </box>\n {sidebarOpen ? (\n <box\n width=\"35%\"\n minWidth={24}\n backgroundColor={THEME.panel}\n border\n borderColor={THEME.border}\n title=\"cell detail\"\n titleAlignment=\"left\"\n >\n <text wrapMode=\"none\" truncate fg={THEME.muted}>\n press esc/x to close\n </text>\n <scrollbox scrollY flexGrow={1} backgroundColor={THEME.panel}>\n <text\n wrapMode=\"word\"\n fg={THEME.text}\n selectable\n selectionBg={THEME.accent}\n selectionFg={THEME.background}\n >\n {detail}\n </text>\n </scrollbox>\n <text\n wrapMode=\"none\"\n truncate\n fg={THEME.accent}\n onMouseDown={() => {\n setSidebarOpen(false);\n }}\n >\n [ close ]\n </text>\n </box>\n ) : null}\n </box>\n <box backgroundColor={THEME.header} border borderColor={THEME.border}>\n {renderFooter()}\n </box>\n </box>\n );\n}\n\ntype HeaderProps = {\n filePath: string;\n offset: number;\n rows: number;\n columns: number;\n loading: boolean;\n error: string | null;\n maxRows?: number;\n createdBy?: string;\n optimized: boolean;\n};\n\nfunction renderHeader(props: HeaderProps) {\n const { filePath, offset, rows, columns, loading, error, maxRows, createdBy, optimized } = props;\n\n const start = rows > 0 ? offset + 1 : offset;\n const end = rows > 0 ? offset + rows : offset;\n const totalText = maxRows !== undefined ? `of ${maxRows.toLocaleString()}` : \"\";\n const fileName = filePath.split(\"/\").pop() ?? filePath;\n\n if (error) {\n return (\n <box flexDirection=\"row\" alignItems=\"center\" gap={2} width=\"100%\">\n <text wrapMode=\"none\" fg={THEME.accent}>\n {\"◈ parquetlens\"}\n </text>\n <text wrapMode=\"none\" fg=\"#ef4444\">\n {\"error: \" + error}\n </text>\n </box>\n );\n }\n\n return (\n <box flexDirection=\"row\" alignItems=\"center\" gap={2} width=\"100%\">\n <text wrapMode=\"none\" fg={THEME.accent}>\n {\"◈ parquetlens\"}\n </text>\n <text wrapMode=\"none\" fg={THEME.muted}>\n {\"│\"}\n </text>\n <text wrapMode=\"none\" fg={THEME.text}>\n {fileName}\n </text>\n <text wrapMode=\"none\" fg={THEME.muted}>\n {\"│\"}\n </text>\n <text wrapMode=\"none\" fg={THEME.muted}>\n {\"rows \"}\n </text>\n <text wrapMode=\"none\" fg={THEME.text}>\n {`${start.toLocaleString()}-${end.toLocaleString()} ${totalText}`}\n </text>\n <text wrapMode=\"none\" fg={THEME.muted}>\n {\"│\"}\n </text>\n <text wrapMode=\"none\" fg={THEME.muted}>\n {\"cols \"}\n </text>\n <text wrapMode=\"none\" fg={THEME.text}>\n {columns.toString()}\n </text>\n {createdBy ? (\n <>\n <text wrapMode=\"none\" fg={THEME.muted}>\n {\"│\"}\n </text>\n <text wrapMode=\"none\" fg={THEME.muted} truncate>\n {createdBy}\n </text>\n </>\n ) : null}\n <box flexGrow={1} />\n {loading ? (\n <text wrapMode=\"none\" fg={THEME.badge}>\n {\"● loading\"}\n </text>\n ) : null}\n {optimized ? (\n <text wrapMode=\"none\" fg={THEME.badgeText} bg={THEME.badge}>\n {\" ✓ OPTIMIZED \"}\n </text>\n ) : null}\n </box>\n );\n}\n\nfunction renderFooterLine(): string {\n return \"q exit | arrows/jk scroll | pgup/pgdn page | h/l col jump | mouse wheel scroll | click cell for detail | s/enter toggle panel\";\n}\n\nfunction renderFooter() {\n const controls = renderFooterLine();\n\n return (\n <box flexDirection=\"column\" width=\"100%\">\n <text wrapMode=\"none\" truncate fg={THEME.muted}>\n {controls}\n </text>\n </box>\n );\n}\n\nfunction buildGridLines(grid: GridState, offset: number, targetWidth: number): GridLines {\n const columns: ColumnInfo[] =\n grid.columns.length > 0 ? grid.columns : [{ name: \"(loading)\", type: \"\" }];\n const rows = grid.rows;\n\n const rowNumberWidth = Math.max(String(offset + rows.length).length, 3);\n const columnWidths = columns.map((col, index) => {\n const longestCell = rows.reduce(\n (max, row) => {\n const value = row[index] ?? \"\";\n return Math.max(max, value.length);\n },\n Math.max(col.name.length, col.type.length),\n );\n return Math.min(Math.max(longestCell, DEFAULT_COLUMN_WIDTH), MAX_COLUMN_WIDTH);\n });\n\n const headerNames = [\"#\", ...columns.map((c) => c.name)];\n const headerTypes = [\"\", ...columns.map((c) => c.type)];\n const headerWidths = [rowNumberWidth, ...columnWidths];\n const separatorWidth = headerWidths.length > 1 ? (headerWidths.length - 1) * 2 : 0;\n const baseLength = headerWidths.reduce((total, width) => total + width, 0) + separatorWidth;\n\n if (targetWidth > baseLength && headerWidths.length > 1) {\n headerWidths[headerWidths.length - 1] += targetWidth - baseLength;\n }\n const headerNameLine = buildLine(headerNames, headerWidths);\n const headerTypeLine = buildLine(headerTypes, headerWidths);\n const separatorLine = buildSeparator(headerWidths);\n const rowLines = rows.map((row, index) => {\n const rowIndex = String(offset + index + 1);\n const values = [rowIndex, ...row];\n return buildLine(values, headerWidths);\n });\n\n const { columnRanges, scrollStops } = buildColumnRanges(headerWidths);\n\n const maxLineLength = Math.max(\n headerNameLine.length,\n headerTypeLine.length,\n separatorLine.length,\n ...rowLines.map((line) => line.length),\n );\n\n return {\n headerNameLine,\n headerTypeLine,\n separatorLine,\n rowLines,\n maxLineLength,\n columnRanges,\n scrollStops,\n };\n}\n\nfunction applyHorizontalScroll(\n lines: GridLines,\n width: number,\n xOffset: number,\n pageSize: number,\n): { headerName: string; headerType: string; separator: string; rows: string[] } {\n const sliceLine = (line: string) => {\n if (width <= 0) {\n return \"\";\n }\n const sliced = xOffset <= 0 ? line.slice(0, width) : line.slice(xOffset, xOffset + width);\n return sliced.padEnd(width, \" \");\n };\n\n const rows = lines.rowLines.slice(0, pageSize).map(sliceLine);\n const emptyRow = width > 0 ? \" \".repeat(width) : \"\";\n while (rows.length < pageSize) {\n rows.push(emptyRow);\n }\n\n return {\n headerName: sliceLine(lines.headerNameLine),\n headerType: sliceLine(lines.headerTypeLine),\n separator: sliceLine(lines.separatorLine),\n rows,\n };\n}\n\nfunction buildLine(values: string[], widths: number[]): string {\n return values\n .map((value, index) => {\n const width = widths[index] ?? DEFAULT_COLUMN_WIDTH;\n return padCell(value, width);\n })\n .join(\" \");\n}\n\nfunction buildSeparator(widths: number[]): string {\n return \"\";\n}\n\nfunction padCell(value: string, width: number): string {\n const normalized = normalizeCell(value);\n if (normalized.length > width) {\n if (width <= 3) {\n return normalized.slice(0, width);\n }\n return `${normalized.slice(0, width - 3)}...`;\n }\n return normalized.padEnd(width, \" \");\n}\n\nfunction normalizeCell(value: string): string {\n return value.replace(/\\r?\\n/g, \"\\\\n\").replace(/\\t/g, \"\\\\t\");\n}\n\nfunction buildColumnRanges(widths: number[]): {\n columnRanges: Array<{ start: number; end: number }>;\n scrollStops: number[];\n} {\n const columnRanges: Array<{ start: number; end: number }> = [];\n const scrollStops: number[] = [0];\n let cursor = 0;\n\n for (let i = 0; i < widths.length; i += 1) {\n const width = widths[i];\n if (i > 0) {\n const start = cursor;\n const end = cursor + width - 1;\n columnRanges.push({ start, end });\n scrollStops.push(start);\n }\n cursor += width;\n if (i < widths.length - 1) {\n cursor += 2;\n }\n }\n\n return { columnRanges, scrollStops };\n}\n\nfunction findColumnIndex(x: number, ranges: Array<{ start: number; end: number }>): number {\n for (let index = 0; index < ranges.length; index += 1) {\n const range = ranges[index];\n if (x >= range.start && x <= range.end) {\n return index;\n }\n }\n return -1;\n}\n\nfunction findScrollStop(current: number, stops: number[], direction: 1 | -1): number {\n if (direction > 0) {\n for (const stop of stops) {\n if (stop > current) {\n return stop;\n }\n }\n return current;\n }\n\n for (let index = stops.length - 1; index >= 0; index -= 1) {\n const stop = stops[index];\n if (stop < current) {\n return stop;\n }\n }\n\n return 0;\n}\n\nfunction clampScroll(value: number, max: number): number {\n if (value < 0) {\n return 0;\n }\n if (value > max) {\n return max;\n }\n return value;\n}\n\nfunction buildDetail(\n selection: { row: number; col: number } | null,\n grid: GridState,\n offset: number,\n): string {\n if (!selection || grid.columns.length === 0 || grid.rows.length === 0) {\n return \"click a cell to see full details\";\n }\n\n const rowIndex = Math.min(selection.row, grid.rows.length - 1);\n const colIndex = Math.min(selection.col, grid.columns.length - 1);\n const col = grid.columns[colIndex];\n const columnName = col?.name ?? \"(unknown)\";\n const columnType = col?.type ?? \"\";\n const value = grid.rows[rowIndex]?.[colIndex] ?? \"\";\n const absoluteRow = offset + rowIndex + 1;\n\n return `row ${absoluteRow} • ${columnName}\\n${columnType}\\n\\n${value}`;\n}\n\nfunction getMetadataFlags(metadata: ParquetFileMetadata | null): {\n optimized: boolean;\n createdBy?: string;\n} {\n if (!metadata) {\n return { optimized: false };\n }\n\n const raw = metadata.keyValueMetadata[\"content_defined_chunking\"];\n const normalized = raw?.toLowerCase?.() ?? \"\";\n const optimized =\n raw !== undefined && raw !== null && normalized !== \"false\" && normalized !== \"0\";\n\n return {\n optimized,\n createdBy: metadata.createdBy,\n };\n}\n\nfunction tableToRows(table: import(\"apache-arrow\").Table, columns: string[]): string[][] {\n const rows: string[][] = [];\n\n for (const batch of table.batches) {\n const vectors = columns.map((_, index) => batch.getChildAt(index));\n\n for (let rowIndex = 0; rowIndex < batch.numRows; rowIndex += 1) {\n const row = vectors.map((vector) => formatCell(vector?.get(rowIndex)));\n rows.push(row);\n }\n }\n\n return rows;\n}\n\nfunction formatCell(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n\n if (typeof value === \"bigint\") {\n return value.toString();\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n if (value instanceof Uint8Array) {\n return `Uint8Array(${value.length})`;\n }\n\n if (typeof value === \"object\") {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n\n return String(value);\n}\n\nfunction formatArrowType(type: import(\"apache-arrow\").DataType): string {\n return type.toString();\n}\n"],"mappings":"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","names":[]}
|