rowpipe 2.1.0 → 2.2.1
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/README.md +183 -5
- package/dist/analytics/profiler.d.ts +56 -0
- package/dist/analytics/profiler.d.ts.map +1 -0
- package/dist/analytics/profiler.js +277 -0
- package/dist/analytics/profiler.js.map +1 -0
- package/dist/analytics/schema-inference.d.ts.map +1 -1
- package/dist/analytics/schema-inference.js +14 -0
- package/dist/analytics/schema-inference.js.map +1 -1
- package/dist/cli/commands/clean.d.ts +15 -0
- package/dist/cli/commands/clean.d.ts.map +1 -0
- package/dist/cli/commands/clean.js +108 -0
- package/dist/cli/commands/clean.js.map +1 -0
- package/dist/cli/commands/convert.d.ts +6 -0
- package/dist/cli/commands/convert.d.ts.map +1 -1
- package/dist/cli/commands/convert.js +6 -6
- package/dist/cli/commands/convert.js.map +1 -1
- package/dist/cli/commands/db.d.ts +35 -0
- package/dist/cli/commands/db.d.ts.map +1 -0
- package/dist/cli/commands/db.js +173 -0
- package/dist/cli/commands/db.js.map +1 -0
- package/dist/cli/commands/explain.d.ts +8 -1
- package/dist/cli/commands/explain.d.ts.map +1 -1
- package/dist/cli/commands/explain.js +89 -2
- package/dist/cli/commands/explain.js.map +1 -1
- package/dist/cli/commands/join.d.ts +23 -0
- package/dist/cli/commands/join.d.ts.map +1 -0
- package/dist/cli/commands/join.js +97 -0
- package/dist/cli/commands/join.js.map +1 -0
- package/dist/cli/commands/pipeline.d.ts +20 -0
- package/dist/cli/commands/pipeline.d.ts.map +1 -1
- package/dist/cli/commands/pipeline.js +53 -12
- package/dist/cli/commands/pipeline.js.map +1 -1
- package/dist/cli/commands/profile.d.ts +22 -0
- package/dist/cli/commands/profile.d.ts.map +1 -0
- package/dist/cli/commands/profile.js +53 -0
- package/dist/cli/commands/profile.js.map +1 -0
- package/dist/cli/commands/view.d.ts +12 -0
- package/dist/cli/commands/view.d.ts.map +1 -0
- package/dist/cli/commands/view.js +52 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/commands/window.d.ts +10 -0
- package/dist/cli/commands/window.d.ts.map +1 -0
- package/dist/cli/commands/window.js +87 -0
- package/dist/cli/commands/window.js.map +1 -0
- package/dist/cli/index.js +201 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/core/batch.d.ts +1 -1
- package/dist/core/batch.d.ts.map +1 -1
- package/dist/core/batch.js.map +1 -1
- package/dist/core/pipeline.d.ts +2 -2
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/pipeline.js +5 -2
- package/dist/core/pipeline.js.map +1 -1
- package/dist/core/types.d.ts +21 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/db/adapters/index.d.ts +6 -0
- package/dist/db/adapters/index.d.ts.map +1 -0
- package/dist/db/adapters/index.js +21 -0
- package/dist/db/adapters/index.js.map +1 -0
- package/dist/db/adapters/mysql.d.ts +16 -0
- package/dist/db/adapters/mysql.d.ts.map +1 -0
- package/dist/db/adapters/mysql.js +230 -0
- package/dist/db/adapters/mysql.js.map +1 -0
- package/dist/db/adapters/postgres.d.ts +17 -0
- package/dist/db/adapters/postgres.d.ts.map +1 -0
- package/dist/db/adapters/postgres.js +239 -0
- package/dist/db/adapters/postgres.js.map +1 -0
- package/dist/db/adapters/sqlite.d.ts +19 -0
- package/dist/db/adapters/sqlite.d.ts.map +1 -0
- package/dist/db/adapters/sqlite.js +241 -0
- package/dist/db/adapters/sqlite.js.map +1 -0
- package/dist/db/index.d.ts +11 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +11 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/mapping.d.ts +45 -0
- package/dist/db/mapping.d.ts.map +1 -0
- package/dist/db/mapping.js +222 -0
- package/dist/db/mapping.js.map +1 -0
- package/dist/db/pushdown.d.ts +25 -0
- package/dist/db/pushdown.d.ts.map +1 -0
- package/dist/db/pushdown.js +165 -0
- package/dist/db/pushdown.js.map +1 -0
- package/dist/db/sink.d.ts +15 -0
- package/dist/db/sink.d.ts.map +1 -0
- package/dist/db/sink.js +47 -0
- package/dist/db/sink.js.map +1 -0
- package/dist/db/source.d.ts +14 -0
- package/dist/db/source.d.ts.map +1 -0
- package/dist/db/source.js +69 -0
- package/dist/db/source.js.map +1 -0
- package/dist/db/types.d.ts +80 -0
- package/dist/db/types.d.ts.map +1 -0
- package/dist/db/types.js +2 -0
- package/dist/db/types.js.map +1 -0
- package/dist/db/url.d.ts +15 -0
- package/dist/db/url.d.ts.map +1 -0
- package/dist/db/url.js +145 -0
- package/dist/db/url.js.map +1 -0
- package/dist/diff/hash.d.ts.map +1 -1
- package/dist/diff/hash.js +4 -3
- package/dist/diff/hash.js.map +1 -1
- package/dist/diff/key.d.ts.map +1 -1
- package/dist/diff/key.js +8 -1
- package/dist/diff/key.js.map +1 -1
- package/dist/diff/reporter.d.ts.map +1 -1
- package/dist/diff/reporter.js +5 -3
- package/dist/diff/reporter.js.map +1 -1
- package/dist/diff/storage/disk-index.d.ts.map +1 -1
- package/dist/diff/storage/disk-index.js +2 -1
- package/dist/diff/storage/disk-index.js.map +1 -1
- package/dist/diff/storage/memory-index.d.ts.map +1 -1
- package/dist/diff/storage/memory-index.js +3 -2
- package/dist/diff/storage/memory-index.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/planner/executor.d.ts.map +1 -1
- package/dist/planner/executor.js +7 -0
- package/dist/planner/executor.js.map +1 -1
- package/dist/planner/types.d.ts +4 -0
- package/dist/planner/types.d.ts.map +1 -1
- package/dist/readers/csv.d.ts.map +1 -1
- package/dist/readers/csv.js +1 -2
- package/dist/readers/csv.js.map +1 -1
- package/dist/readers/index.d.ts +5 -1
- package/dist/readers/index.d.ts.map +1 -1
- package/dist/readers/index.js +43 -0
- package/dist/readers/index.js.map +1 -1
- package/dist/readers/json.d.ts.map +1 -1
- package/dist/readers/json.js +1 -2
- package/dist/readers/json.js.map +1 -1
- package/dist/readers/jsonl.d.ts.map +1 -1
- package/dist/readers/jsonl.js +1 -2
- package/dist/readers/jsonl.js.map +1 -1
- package/dist/readers/multi-file.d.ts +29 -0
- package/dist/readers/multi-file.d.ts.map +1 -0
- package/dist/readers/multi-file.js +169 -0
- package/dist/readers/multi-file.js.map +1 -0
- package/dist/transforms/clean.d.ts +18 -0
- package/dist/transforms/clean.d.ts.map +1 -0
- package/dist/transforms/clean.js +118 -0
- package/dist/transforms/clean.js.map +1 -0
- package/dist/transforms/expression.d.ts.map +1 -1
- package/dist/transforms/expression.js +12 -0
- package/dist/transforms/expression.js.map +1 -1
- package/dist/transforms/jit-compiler.d.ts +50 -0
- package/dist/transforms/jit-compiler.d.ts.map +1 -0
- package/dist/transforms/jit-compiler.js +275 -0
- package/dist/transforms/jit-compiler.js.map +1 -0
- package/dist/transforms/join/index-storage.d.ts +36 -0
- package/dist/transforms/join/index-storage.d.ts.map +1 -0
- package/dist/transforms/join/index-storage.js +176 -0
- package/dist/transforms/join/index-storage.js.map +1 -0
- package/dist/transforms/join/index.d.ts +4 -0
- package/dist/transforms/join/index.d.ts.map +1 -0
- package/dist/transforms/join/index.js +4 -0
- package/dist/transforms/join/index.js.map +1 -0
- package/dist/transforms/join/join.d.ts +15 -0
- package/dist/transforms/join/join.d.ts.map +1 -0
- package/dist/transforms/join/join.js +216 -0
- package/dist/transforms/join/join.js.map +1 -0
- package/dist/transforms/join/types.d.ts +29 -0
- package/dist/transforms/join/types.d.ts.map +1 -0
- package/dist/transforms/join/types.js +2 -0
- package/dist/transforms/join/types.js.map +1 -0
- package/dist/transforms/window.d.ts +24 -0
- package/dist/transforms/window.d.ts.map +1 -0
- package/dist/transforms/window.js +317 -0
- package/dist/transforms/window.js.map +1 -0
- package/dist/ui/colors.d.ts +57 -0
- package/dist/ui/colors.d.ts.map +1 -0
- package/dist/ui/colors.js +94 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/viewer.d.ts +67 -0
- package/dist/ui/viewer.d.ts.map +1 -0
- package/dist/ui/viewer.js +673 -0
- package/dist/ui/viewer.js.map +1 -0
- package/dist/utils/compression.d.ts +68 -19
- package/dist/utils/compression.d.ts.map +1 -1
- package/dist/utils/compression.js +222 -34
- package/dist/utils/compression.js.map +1 -1
- package/dist/utils/formatting.d.ts +5 -0
- package/dist/utils/formatting.d.ts.map +1 -1
- package/dist/utils/formatting.js +15 -0
- package/dist/utils/formatting.js.map +1 -1
- package/dist/writers/csv.d.ts.map +1 -1
- package/dist/writers/csv.js +13 -14
- package/dist/writers/csv.js.map +1 -1
- package/dist/writers/index.d.ts +1 -0
- package/dist/writers/index.d.ts.map +1 -1
- package/dist/writers/index.js +35 -0
- package/dist/writers/index.js.map +1 -1
- package/dist/writers/json.d.ts.map +1 -1
- package/dist/writers/json.js +6 -5
- package/dist/writers/json.js.map +1 -1
- package/dist/writers/jsonl.d.ts.map +1 -1
- package/dist/writers/jsonl.js +6 -5
- package/dist/writers/jsonl.js.map +1 -1
- package/dist/writers/markdown.d.ts.map +1 -1
- package/dist/writers/markdown.js +5 -4
- package/dist/writers/markdown.js.map +1 -1
- package/dist/writers/parquet.d.ts +1 -1
- package/dist/writers/parquet.d.ts.map +1 -1
- package/package.json +10 -2
- package/skills/rowpipe/SKILL.md +8 -4
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
import { ANSI, bgBlue, bgGray, bold, cyan, dim, green, inverse, magenta, stripAnsi, truncateVisible, yellow, } from "./colors.js";
|
|
2
|
+
import { formatNumber } from "../utils/formatting.js";
|
|
3
|
+
/**
|
|
4
|
+
* Interactive Terminal Data Viewer for streaming tabular datasets.
|
|
5
|
+
* Features bounded memory sliding windows, keyboard navigation, column browsing,
|
|
6
|
+
* live in-memory searching, sorting, and seamless terminal restoration.
|
|
7
|
+
*/
|
|
8
|
+
export class TerminalViewer {
|
|
9
|
+
streamIterator;
|
|
10
|
+
isStreamExhausted = false;
|
|
11
|
+
loadedRows = [];
|
|
12
|
+
columns = [];
|
|
13
|
+
options;
|
|
14
|
+
// Navigation state
|
|
15
|
+
cursorRow = 0;
|
|
16
|
+
scrollRow = 0;
|
|
17
|
+
cursorCol = 0;
|
|
18
|
+
scrollCol = 0;
|
|
19
|
+
// Interactive modes
|
|
20
|
+
mode = "normal";
|
|
21
|
+
searchQuery = "";
|
|
22
|
+
searchInput = "";
|
|
23
|
+
jumpInput = "";
|
|
24
|
+
matchingRowIndices = null;
|
|
25
|
+
activeMatchIndex = -1;
|
|
26
|
+
// Sorting
|
|
27
|
+
sortColumn = null;
|
|
28
|
+
sortAsc = true;
|
|
29
|
+
sortedRowIndices = null;
|
|
30
|
+
// Display configuration
|
|
31
|
+
autoFitCols = true;
|
|
32
|
+
termRows = 24;
|
|
33
|
+
termCols = 80;
|
|
34
|
+
isRunning = false;
|
|
35
|
+
stdinRawSet = false;
|
|
36
|
+
constructor(source, options = {}) {
|
|
37
|
+
this.options = {
|
|
38
|
+
maxBufferRows: 25000,
|
|
39
|
+
initialRows: 500,
|
|
40
|
+
...options,
|
|
41
|
+
};
|
|
42
|
+
const stream = "read" in source && typeof source.read === "function"
|
|
43
|
+
? source.read()
|
|
44
|
+
: source;
|
|
45
|
+
this.streamIterator = stream[Symbol.asyncIterator]();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Loads at least `targetCount` rows into memory buffer from stream.
|
|
49
|
+
*/
|
|
50
|
+
async loadRows(targetCount) {
|
|
51
|
+
while (!this.isStreamExhausted && this.loadedRows.length < targetCount) {
|
|
52
|
+
const result = await this.streamIterator.next();
|
|
53
|
+
if (result.done) {
|
|
54
|
+
this.isStreamExhausted = true;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
const batch = result.value;
|
|
58
|
+
if (batch && Array.isArray(batch.rows)) {
|
|
59
|
+
for (const row of batch.rows) {
|
|
60
|
+
this.loadedRows.push(row);
|
|
61
|
+
// Discover new column names dynamically
|
|
62
|
+
for (const key of Object.keys(row)) {
|
|
63
|
+
if (!this.columns.includes(key)) {
|
|
64
|
+
this.columns.push(key);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Starts the interactive terminal viewer loop.
|
|
73
|
+
*/
|
|
74
|
+
async run() {
|
|
75
|
+
// Initial data load
|
|
76
|
+
await this.loadRows(this.options.initialRows || 200);
|
|
77
|
+
// Non-interactive fallback (e.g. CI / piped output)
|
|
78
|
+
if (!process.stdin.isTTY || this.options.interactive === false) {
|
|
79
|
+
this.renderNonInteractive();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.isRunning = true;
|
|
83
|
+
this.updateDimensions();
|
|
84
|
+
// Setup terminal
|
|
85
|
+
process.stdout.write(ANSI.enterAltScreen);
|
|
86
|
+
process.stdout.write(ANSI.hideCursor);
|
|
87
|
+
if (process.stdin.isTTY) {
|
|
88
|
+
process.stdin.setRawMode(true);
|
|
89
|
+
this.stdinRawSet = true;
|
|
90
|
+
process.stdin.resume();
|
|
91
|
+
process.stdin.setEncoding("utf8");
|
|
92
|
+
}
|
|
93
|
+
const onResize = () => {
|
|
94
|
+
this.updateDimensions();
|
|
95
|
+
this.render();
|
|
96
|
+
};
|
|
97
|
+
process.stdout.on("resize", onResize);
|
|
98
|
+
const onData = async (data) => {
|
|
99
|
+
if (!this.isRunning)
|
|
100
|
+
return;
|
|
101
|
+
await this.handleInput(data);
|
|
102
|
+
this.render();
|
|
103
|
+
};
|
|
104
|
+
process.stdin.on("data", onData);
|
|
105
|
+
// Initial render
|
|
106
|
+
this.render();
|
|
107
|
+
// Keep running until exit requested
|
|
108
|
+
return new Promise((resolve) => {
|
|
109
|
+
const cleanup = () => {
|
|
110
|
+
this.isRunning = false;
|
|
111
|
+
process.stdout.off("resize", onResize);
|
|
112
|
+
process.stdin.off("data", onData);
|
|
113
|
+
if (this.stdinRawSet && process.stdin.isTTY) {
|
|
114
|
+
process.stdin.setRawMode(false);
|
|
115
|
+
process.stdin.pause();
|
|
116
|
+
}
|
|
117
|
+
process.stdout.write(ANSI.showCursor);
|
|
118
|
+
process.stdout.write(ANSI.exitAltScreen);
|
|
119
|
+
resolve();
|
|
120
|
+
};
|
|
121
|
+
this.exitCleanup = cleanup;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
exitCleanup;
|
|
125
|
+
exit() {
|
|
126
|
+
if (this.exitCleanup) {
|
|
127
|
+
this.exitCleanup();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
updateDimensions() {
|
|
131
|
+
this.termRows = process.stdout.rows || 24;
|
|
132
|
+
this.termCols = process.stdout.columns || 80;
|
|
133
|
+
}
|
|
134
|
+
getEffectiveRowIndices() {
|
|
135
|
+
if (this.matchingRowIndices !== null) {
|
|
136
|
+
return this.matchingRowIndices;
|
|
137
|
+
}
|
|
138
|
+
if (this.sortedRowIndices !== null) {
|
|
139
|
+
return this.sortedRowIndices;
|
|
140
|
+
}
|
|
141
|
+
const indices = [];
|
|
142
|
+
for (let i = 0; i < this.loadedRows.length; i++) {
|
|
143
|
+
indices.push(i);
|
|
144
|
+
}
|
|
145
|
+
return indices;
|
|
146
|
+
}
|
|
147
|
+
applySearch(query) {
|
|
148
|
+
this.searchQuery = query.trim();
|
|
149
|
+
if (!this.searchQuery) {
|
|
150
|
+
this.matchingRowIndices = null;
|
|
151
|
+
this.activeMatchIndex = -1;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const lowerQuery = this.searchQuery.toLowerCase();
|
|
155
|
+
const matches = [];
|
|
156
|
+
const sourceIndices = this.sortedRowIndices || Array.from({ length: this.loadedRows.length }, (_, i) => i);
|
|
157
|
+
for (const idx of sourceIndices) {
|
|
158
|
+
const row = this.loadedRows[idx];
|
|
159
|
+
if (!row)
|
|
160
|
+
continue;
|
|
161
|
+
const rowText = Object.values(row).map(String).join(" ").toLowerCase();
|
|
162
|
+
if (rowText.includes(lowerQuery)) {
|
|
163
|
+
matches.push(idx);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
this.matchingRowIndices = matches;
|
|
167
|
+
this.activeMatchIndex = matches.length > 0 ? 0 : -1;
|
|
168
|
+
this.cursorRow = 0;
|
|
169
|
+
this.scrollRow = 0;
|
|
170
|
+
}
|
|
171
|
+
applySort(column) {
|
|
172
|
+
if (this.sortColumn === column) {
|
|
173
|
+
if (this.sortAsc) {
|
|
174
|
+
this.sortAsc = false;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// Clear sort
|
|
178
|
+
this.sortColumn = null;
|
|
179
|
+
this.sortedRowIndices = null;
|
|
180
|
+
if (this.searchQuery) {
|
|
181
|
+
this.applySearch(this.searchQuery);
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
this.sortColumn = column;
|
|
188
|
+
this.sortAsc = true;
|
|
189
|
+
}
|
|
190
|
+
const indices = Array.from({ length: this.loadedRows.length }, (_, i) => i);
|
|
191
|
+
const asc = this.sortAsc;
|
|
192
|
+
indices.sort((a, b) => {
|
|
193
|
+
const valA = this.loadedRows[a]?.[column];
|
|
194
|
+
const valB = this.loadedRows[b]?.[column];
|
|
195
|
+
if (valA === valB)
|
|
196
|
+
return 0;
|
|
197
|
+
if (valA === null || valA === undefined)
|
|
198
|
+
return 1;
|
|
199
|
+
if (valB === null || valB === undefined)
|
|
200
|
+
return -1;
|
|
201
|
+
if (typeof valA === "number" && typeof valB === "number") {
|
|
202
|
+
return asc ? valA - valB : valB - valA;
|
|
203
|
+
}
|
|
204
|
+
const strA = String(valA);
|
|
205
|
+
const strB = String(valB);
|
|
206
|
+
return asc ? strA.localeCompare(strB) : strB.localeCompare(strA);
|
|
207
|
+
});
|
|
208
|
+
this.sortedRowIndices = indices;
|
|
209
|
+
if (this.searchQuery) {
|
|
210
|
+
this.applySearch(this.searchQuery);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async handleInput(key) {
|
|
214
|
+
// -------------------------------------------------------------
|
|
215
|
+
// 1. Search Mode Input
|
|
216
|
+
// -------------------------------------------------------------
|
|
217
|
+
if (this.mode === "search") {
|
|
218
|
+
if (key === "\r" || key === "\n") {
|
|
219
|
+
// Submit search
|
|
220
|
+
this.applySearch(this.searchInput);
|
|
221
|
+
this.mode = "normal";
|
|
222
|
+
}
|
|
223
|
+
else if (key === "\x1b" || key === "\x03") {
|
|
224
|
+
// Escape / Cancel
|
|
225
|
+
this.mode = "normal";
|
|
226
|
+
this.searchInput = this.searchQuery;
|
|
227
|
+
}
|
|
228
|
+
else if (key === "\x7f" || key === "\b" || key === "\x08") {
|
|
229
|
+
// Backspace
|
|
230
|
+
this.searchInput = this.searchInput.slice(0, -1);
|
|
231
|
+
}
|
|
232
|
+
else if (key.length === 1 && key >= " ") {
|
|
233
|
+
this.searchInput += key;
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// -------------------------------------------------------------
|
|
238
|
+
// 2. Jump to Row Mode Input
|
|
239
|
+
// -------------------------------------------------------------
|
|
240
|
+
if (this.mode === "jump") {
|
|
241
|
+
if (key === "\r" || key === "\n") {
|
|
242
|
+
const targetRow = Number.parseInt(this.jumpInput, 10);
|
|
243
|
+
if (!Number.isNaN(targetRow) && targetRow >= 1) {
|
|
244
|
+
const zeroIdx = targetRow - 1;
|
|
245
|
+
await this.loadRows(targetRow + 50);
|
|
246
|
+
const effective = this.getEffectiveRowIndices();
|
|
247
|
+
this.cursorRow = Math.min(zeroIdx, Math.max(0, effective.length - 1));
|
|
248
|
+
this.scrollRow = Math.max(0, this.cursorRow - Math.floor(this.getVisibleDataRowCount() / 2));
|
|
249
|
+
}
|
|
250
|
+
this.mode = "normal";
|
|
251
|
+
this.jumpInput = "";
|
|
252
|
+
}
|
|
253
|
+
else if (key === "\x1b" || key === "\x03") {
|
|
254
|
+
this.mode = "normal";
|
|
255
|
+
this.jumpInput = "";
|
|
256
|
+
}
|
|
257
|
+
else if (key === "\x7f" || key === "\b") {
|
|
258
|
+
this.jumpInput = this.jumpInput.slice(0, -1);
|
|
259
|
+
}
|
|
260
|
+
else if (/^\d$/.test(key)) {
|
|
261
|
+
this.jumpInput += key;
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
// -------------------------------------------------------------
|
|
266
|
+
// 3. Help Mode
|
|
267
|
+
// -------------------------------------------------------------
|
|
268
|
+
if (this.mode === "help") {
|
|
269
|
+
if (key === "q" || key === "\x1b" || key === "?" || key === "\r" || key === " ") {
|
|
270
|
+
this.mode = "normal";
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
// -------------------------------------------------------------
|
|
275
|
+
// 4. Normal Navigation Mode
|
|
276
|
+
// -------------------------------------------------------------
|
|
277
|
+
const effectiveIndices = this.getEffectiveRowIndices();
|
|
278
|
+
const totalEffectiveRows = effectiveIndices.length;
|
|
279
|
+
const pageRows = this.getVisibleDataRowCount();
|
|
280
|
+
// Quit
|
|
281
|
+
if (key === "q" || key === "Q" || key === "\x03") {
|
|
282
|
+
this.exit();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
// Help
|
|
286
|
+
if (key === "?") {
|
|
287
|
+
this.mode = "help";
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
// Enter Search
|
|
291
|
+
if (key === "/") {
|
|
292
|
+
this.mode = "search";
|
|
293
|
+
this.searchInput = "";
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
// Jump to row
|
|
297
|
+
if (key === ":" || key === "J") {
|
|
298
|
+
this.mode = "jump";
|
|
299
|
+
this.jumpInput = "";
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
// Clear search
|
|
303
|
+
if (key === "\x1b") {
|
|
304
|
+
if (this.searchQuery) {
|
|
305
|
+
this.searchQuery = "";
|
|
306
|
+
this.matchingRowIndices = null;
|
|
307
|
+
}
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
// Next match (n) / Prev match (N)
|
|
311
|
+
if (key === "n" && this.matchingRowIndices && this.matchingRowIndices.length > 0) {
|
|
312
|
+
this.cursorRow = (this.cursorRow + 1) % this.matchingRowIndices.length;
|
|
313
|
+
this.adjustScroll();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (key === "N" && this.matchingRowIndices && this.matchingRowIndices.length > 0) {
|
|
317
|
+
this.cursorRow = (this.cursorRow - 1 + this.matchingRowIndices.length) % this.matchingRowIndices.length;
|
|
318
|
+
this.adjustScroll();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
// Sort by selected column (s)
|
|
322
|
+
if (key === "s" || key === "S") {
|
|
323
|
+
const activeCol = this.columns[this.cursorCol];
|
|
324
|
+
if (activeCol) {
|
|
325
|
+
this.applySort(activeCol);
|
|
326
|
+
}
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// Toggle column auto-fit
|
|
330
|
+
if (key === "c" || key === "C") {
|
|
331
|
+
this.autoFitCols = !this.autoFitCols;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// Up / k / w
|
|
335
|
+
if (key === "\x1b[A" || key === "k" || key === "w") {
|
|
336
|
+
if (this.cursorRow > 0) {
|
|
337
|
+
this.cursorRow--;
|
|
338
|
+
this.adjustScroll();
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
// Down / j
|
|
343
|
+
if (key === "\x1b[B" || key === "j") {
|
|
344
|
+
if (this.cursorRow < totalEffectiveRows - 1) {
|
|
345
|
+
this.cursorRow++;
|
|
346
|
+
this.adjustScroll();
|
|
347
|
+
}
|
|
348
|
+
else if (!this.isStreamExhausted) {
|
|
349
|
+
await this.loadRows(this.loadedRows.length + 200);
|
|
350
|
+
const updated = this.getEffectiveRowIndices();
|
|
351
|
+
if (this.cursorRow < updated.length - 1) {
|
|
352
|
+
this.cursorRow++;
|
|
353
|
+
this.adjustScroll();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
// Page Down / Space / f / Ctrl+F
|
|
359
|
+
if (key === "\x1b[6~" || key === " " || key === "f" || key === "\x06") {
|
|
360
|
+
const nextRow = Math.min(totalEffectiveRows - 1, this.cursorRow + pageRows);
|
|
361
|
+
if (nextRow >= totalEffectiveRows - 5 && !this.isStreamExhausted) {
|
|
362
|
+
await this.loadRows(this.loadedRows.length + pageRows * 2);
|
|
363
|
+
}
|
|
364
|
+
this.cursorRow = Math.min(this.getEffectiveRowIndices().length - 1, this.cursorRow + pageRows);
|
|
365
|
+
this.adjustScroll();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
// Page Up / b / Ctrl+B
|
|
369
|
+
if (key === "\x1b[5~" || key === "b" || key === "\x02") {
|
|
370
|
+
this.cursorRow = Math.max(0, this.cursorRow - pageRows);
|
|
371
|
+
this.adjustScroll();
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
// Top / g / Home
|
|
375
|
+
if (key === "\x1b[H" || key === "\x1b[1~" || key === "g") {
|
|
376
|
+
this.cursorRow = 0;
|
|
377
|
+
this.scrollRow = 0;
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
// End / G
|
|
381
|
+
if (key === "\x1b[F" || key === "\x1b[4~" || key === "G") {
|
|
382
|
+
// Load all or up to maxBufferRows
|
|
383
|
+
while (!this.isStreamExhausted && this.loadedRows.length < (this.options.maxBufferRows || 25000)) {
|
|
384
|
+
await this.loadRows(this.loadedRows.length + 1000);
|
|
385
|
+
}
|
|
386
|
+
const updated = this.getEffectiveRowIndices();
|
|
387
|
+
this.cursorRow = Math.max(0, updated.length - 1);
|
|
388
|
+
this.adjustScroll();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
// Left / h / a
|
|
392
|
+
if (key === "\x1b[D" || key === "h" || key === "a" || key === "\x1b[Z") {
|
|
393
|
+
if (this.cursorCol > 0) {
|
|
394
|
+
this.cursorCol--;
|
|
395
|
+
if (this.cursorCol < this.scrollCol) {
|
|
396
|
+
this.scrollCol = this.cursorCol;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
// Right / l / d / Tab
|
|
402
|
+
if (key === "\x1b[C" || key === "l" || key === "d" || key === "\t") {
|
|
403
|
+
if (this.cursorCol < this.columns.length - 1) {
|
|
404
|
+
this.cursorCol++;
|
|
405
|
+
// Adjust scrollCol if needed in render
|
|
406
|
+
}
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
adjustScroll() {
|
|
411
|
+
const visibleDataRows = this.getVisibleDataRowCount();
|
|
412
|
+
if (this.cursorRow < this.scrollRow) {
|
|
413
|
+
this.scrollRow = this.cursorRow;
|
|
414
|
+
}
|
|
415
|
+
else if (this.cursorRow >= this.scrollRow + visibleDataRows) {
|
|
416
|
+
this.scrollRow = this.cursorRow - visibleDataRows + 1;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
getVisibleDataRowCount() {
|
|
420
|
+
return Math.max(1, this.termRows - 4); // 1 title, 1 header, 1 divider, 1 status
|
|
421
|
+
}
|
|
422
|
+
calculateColumnWidths() {
|
|
423
|
+
const widths = {};
|
|
424
|
+
const defaultWidth = 14;
|
|
425
|
+
for (const col of this.columns) {
|
|
426
|
+
widths[col] = Math.max(col.length, 8);
|
|
427
|
+
}
|
|
428
|
+
if (this.autoFitCols) {
|
|
429
|
+
// Sample visible rows
|
|
430
|
+
const indices = this.getEffectiveRowIndices();
|
|
431
|
+
const sampleSlice = indices.slice(this.scrollRow, this.scrollRow + 50);
|
|
432
|
+
for (const idx of sampleSlice) {
|
|
433
|
+
const row = this.loadedRows[idx];
|
|
434
|
+
if (!row)
|
|
435
|
+
continue;
|
|
436
|
+
for (const col of this.columns) {
|
|
437
|
+
const val = row[col];
|
|
438
|
+
const str = val === null || val === undefined ? "null" : String(val);
|
|
439
|
+
widths[col] = Math.min(32, Math.max(widths[col] || 8, str.length));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
for (const col of this.columns) {
|
|
445
|
+
widths[col] = defaultWidth;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return widths;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Main rendering routine that constructs and prints a single terminal frame.
|
|
452
|
+
*/
|
|
453
|
+
render() {
|
|
454
|
+
if (!this.isRunning)
|
|
455
|
+
return;
|
|
456
|
+
const visibleRowsCount = this.getVisibleDataRowCount();
|
|
457
|
+
const effectiveIndices = this.getEffectiveRowIndices();
|
|
458
|
+
const totalEffectiveRows = effectiveIndices.length;
|
|
459
|
+
const colWidths = this.calculateColumnWidths();
|
|
460
|
+
const rowNumWidth = Math.max(5, String(totalEffectiveRows).length + 2);
|
|
461
|
+
let availableCols = this.termCols - rowNumWidth - 3; // for borders & padding
|
|
462
|
+
// Determine visible columns based on scrollCol and available width
|
|
463
|
+
const visibleCols = [];
|
|
464
|
+
let usedColsWidth = 0;
|
|
465
|
+
for (let c = this.scrollCol; c < this.columns.length; c++) {
|
|
466
|
+
const colName = this.columns[c];
|
|
467
|
+
const width = (colWidths[colName] || 12) + 2; // +2 for padding
|
|
468
|
+
if (visibleCols.length > 0 && usedColsWidth + width > availableCols) {
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
visibleCols.push(colName);
|
|
472
|
+
usedColsWidth += width;
|
|
473
|
+
}
|
|
474
|
+
// Adjust scrollCol if cursorCol is beyond visible columns
|
|
475
|
+
if (this.cursorCol < this.scrollCol) {
|
|
476
|
+
this.scrollCol = this.cursorCol;
|
|
477
|
+
}
|
|
478
|
+
else if (this.cursorCol >= this.scrollCol + visibleCols.length) {
|
|
479
|
+
this.scrollCol = Math.max(0, this.cursorCol - visibleCols.length + 1);
|
|
480
|
+
}
|
|
481
|
+
const lines = [];
|
|
482
|
+
// -------------------------------------------------------------
|
|
483
|
+
// Line 1: Title & Summary Bar
|
|
484
|
+
// -------------------------------------------------------------
|
|
485
|
+
const titleText = this.options.title || this.options.filePath || "Rowpipe Interactive Viewer";
|
|
486
|
+
const eofBadge = this.isStreamExhausted ? green("● EOF") : yellow("⏳ Streaming");
|
|
487
|
+
const sortBadge = this.sortColumn
|
|
488
|
+
? magenta(` [Sort: ${this.sortColumn} ${this.sortAsc ? "▲" : "▼"}]`)
|
|
489
|
+
: "";
|
|
490
|
+
const searchBadge = this.searchQuery ? yellow(` [Filter: "${this.searchQuery}"]`) : "";
|
|
491
|
+
const titleLeft = ` ${bold(cyan("ROWPIPE"))} ${dim("│")} ${bold(titleText)}${sortBadge}${searchBadge}`;
|
|
492
|
+
const titleRight = `${eofBadge} ${dim("│")} ${formatNumber(totalEffectiveRows)} rows ${dim("│")} ${this.columns.length} cols `;
|
|
493
|
+
const titlePadding = Math.max(0, this.termCols - stripAnsi(titleLeft).length - stripAnsi(titleRight).length);
|
|
494
|
+
lines.push(bgGray(`${titleLeft}${" ".repeat(titlePadding)}${titleRight}`));
|
|
495
|
+
// -------------------------------------------------------------
|
|
496
|
+
// Line 2: Table Column Headers
|
|
497
|
+
// -------------------------------------------------------------
|
|
498
|
+
let headerLine = dim("#".padStart(rowNumWidth)) + " " + dim("│");
|
|
499
|
+
for (const col of visibleCols) {
|
|
500
|
+
const width = colWidths[col] || 12;
|
|
501
|
+
const isActiveCol = this.columns[this.cursorCol] === col;
|
|
502
|
+
const isSortedCol = this.sortColumn === col;
|
|
503
|
+
const sortArrow = isSortedCol ? (this.sortAsc ? " ▲" : " ▼") : "";
|
|
504
|
+
let colLabel = truncateVisible(col + sortArrow, width);
|
|
505
|
+
colLabel = colLabel.padEnd(width);
|
|
506
|
+
if (isActiveCol) {
|
|
507
|
+
headerLine += ` ${bold(cyan(colLabel))} ${dim("│")}`;
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
headerLine += ` ${bold(colLabel)} ${dim("│")}`;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
lines.push(headerLine);
|
|
514
|
+
// -------------------------------------------------------------
|
|
515
|
+
// Line 3: Header Separator
|
|
516
|
+
// -------------------------------------------------------------
|
|
517
|
+
let divider = dim("─".repeat(rowNumWidth)) + dim("┼");
|
|
518
|
+
for (const col of visibleCols) {
|
|
519
|
+
const width = colWidths[col] || 12;
|
|
520
|
+
divider += dim("─".repeat(width + 2)) + dim("┼");
|
|
521
|
+
}
|
|
522
|
+
lines.push(divider);
|
|
523
|
+
// -------------------------------------------------------------
|
|
524
|
+
// Lines 4+: Data Rows
|
|
525
|
+
// -------------------------------------------------------------
|
|
526
|
+
for (let i = 0; i < visibleRowsCount; i++) {
|
|
527
|
+
const rowIdxInView = this.scrollRow + i;
|
|
528
|
+
if (rowIdxInView >= totalEffectiveRows) {
|
|
529
|
+
// Empty placeholder row
|
|
530
|
+
lines.push(dim("~"));
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
const originalRowIdx = effectiveIndices[rowIdxInView];
|
|
534
|
+
const row = this.loadedRows[originalRowIdx];
|
|
535
|
+
const isCursorRow = rowIdxInView === this.cursorRow;
|
|
536
|
+
const rowNumStr = String(rowIdxInView + 1).padStart(rowNumWidth);
|
|
537
|
+
let rowLine = isCursorRow ? bold(cyan(rowNumStr)) : dim(rowNumStr);
|
|
538
|
+
rowLine += " " + dim("│");
|
|
539
|
+
for (const col of visibleCols) {
|
|
540
|
+
const width = colWidths[col] || 12;
|
|
541
|
+
const val = row?.[col];
|
|
542
|
+
let cellText = "";
|
|
543
|
+
if (val === null || val === undefined) {
|
|
544
|
+
cellText = dim("null").padEnd(width);
|
|
545
|
+
}
|
|
546
|
+
else if (typeof val === "number" || typeof val === "bigint") {
|
|
547
|
+
cellText = yellow(String(val)).padStart(width);
|
|
548
|
+
}
|
|
549
|
+
else if (typeof val === "boolean") {
|
|
550
|
+
cellText = magenta(val ? "true" : "false").padEnd(width);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
const str = String(val);
|
|
554
|
+
const truncated = truncateVisible(str, width).padEnd(width);
|
|
555
|
+
// Highlight search matches
|
|
556
|
+
if (this.searchQuery && str.toLowerCase().includes(this.searchQuery.toLowerCase())) {
|
|
557
|
+
cellText = bold(yellow(truncated));
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
cellText = truncated;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
rowLine += ` ${cellText} ${dim("│")}`;
|
|
564
|
+
}
|
|
565
|
+
if (isCursorRow) {
|
|
566
|
+
lines.push(inverse(rowLine));
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
lines.push(rowLine);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
// -------------------------------------------------------------
|
|
573
|
+
// Bottom Status / Command Bar
|
|
574
|
+
// -------------------------------------------------------------
|
|
575
|
+
let statusBar = "";
|
|
576
|
+
if (this.mode === "search") {
|
|
577
|
+
statusBar = bgBlue(` SEARCH: /${this.searchInput}█ `) + ` (Enter: apply, Esc: cancel, n/N: next/prev)`;
|
|
578
|
+
}
|
|
579
|
+
else if (this.mode === "jump") {
|
|
580
|
+
statusBar = bgBlue(` JUMP TO ROW #: ${this.jumpInput}█ `) + ` (Enter: go, Esc: cancel)`;
|
|
581
|
+
}
|
|
582
|
+
else if (this.mode === "help") {
|
|
583
|
+
statusBar = bgBlue(" HELP MODE ") + ` (Press ? or Esc to close)`;
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
const posInfo = `Row ${formatNumber(this.cursorRow + 1)}/${formatNumber(totalEffectiveRows)} │ Col ${this.cursorCol + 1}/${this.columns.length}`;
|
|
587
|
+
const shortcuts = `[?]Help [/]Search [s]Sort [c]Width [g/G]Top/End [:]Jump [q]Quit`;
|
|
588
|
+
const padding = Math.max(1, this.termCols - posInfo.length - shortcuts.length - 3);
|
|
589
|
+
statusBar = bgGray(` ${posInfo}${" ".repeat(padding)}${shortcuts} `);
|
|
590
|
+
}
|
|
591
|
+
lines.push(statusBar);
|
|
592
|
+
// -------------------------------------------------------------
|
|
593
|
+
// Modal Overlay for Help Mode
|
|
594
|
+
// -------------------------------------------------------------
|
|
595
|
+
if (this.mode === "help") {
|
|
596
|
+
this.drawHelpModal(lines);
|
|
597
|
+
}
|
|
598
|
+
// Frame output to terminal
|
|
599
|
+
process.stdout.write(ANSI.cursorHome + lines.slice(0, this.termRows).join("\n"));
|
|
600
|
+
}
|
|
601
|
+
drawHelpModal(lines) {
|
|
602
|
+
const helpBox = [
|
|
603
|
+
"┌────────────────────────────────────────────────────────┐",
|
|
604
|
+
"│ ROWPIPE INTERACTIVE VIEWER HELP │",
|
|
605
|
+
"├────────────────────────────────────────────────────────┤",
|
|
606
|
+
"│ Navigation: │",
|
|
607
|
+
"│ ↑ / k / w : Move cursor up 1 row │",
|
|
608
|
+
"│ ↓ / j / s : Move cursor down 1 row │",
|
|
609
|
+
"│ ← / h / a : Scroll left column │",
|
|
610
|
+
"│ → / l / d / Tab : Scroll right column │",
|
|
611
|
+
"│ PageUp / b : Page up │",
|
|
612
|
+
"│ PageDown / Space : Page down │",
|
|
613
|
+
"│ Home / g : Jump to top of dataset │",
|
|
614
|
+
"│ End / G : Jump to end of loaded stream │",
|
|
615
|
+
"│ │",
|
|
616
|
+
"│ Data Operations: │",
|
|
617
|
+
"│ / : Live text search & row filter │",
|
|
618
|
+
"│ n / N : Jump to next / previous match │",
|
|
619
|
+
"│ s : Cycle sort order on active col │",
|
|
620
|
+
"│ c : Toggle auto-fit column width │",
|
|
621
|
+
"│ : or J : Jump to specific row number │",
|
|
622
|
+
"│ │",
|
|
623
|
+
"│ Exit: │",
|
|
624
|
+
"│ q / Esc / Ctrl+C : Quit viewer & restore terminal │",
|
|
625
|
+
"└────────────────────────────────────────────────────────┘",
|
|
626
|
+
];
|
|
627
|
+
const startRow = Math.max(2, Math.floor((this.termRows - helpBox.length) / 2));
|
|
628
|
+
const startCol = Math.max(0, Math.floor((this.termCols - 60) / 2));
|
|
629
|
+
for (let r = 0; r < helpBox.length; r++) {
|
|
630
|
+
const lineIdx = startRow + r;
|
|
631
|
+
if (lineIdx < lines.length) {
|
|
632
|
+
const boxLine = helpBox[r];
|
|
633
|
+
const currentLine = lines[lineIdx] || "";
|
|
634
|
+
const paddedBox = " ".repeat(startCol) + bold(cyan(boxLine));
|
|
635
|
+
lines[lineIdx] = paddedBox;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Non-interactive fallback printer for scripts, CI, and non-TTY pipes.
|
|
641
|
+
*/
|
|
642
|
+
renderNonInteractive(maxRows = 50) {
|
|
643
|
+
const colWidths = this.calculateColumnWidths();
|
|
644
|
+
const rowsToPrint = this.loadedRows.slice(0, maxRows);
|
|
645
|
+
let headerLine = dim("#".padStart(5)) + " │";
|
|
646
|
+
for (const col of this.columns) {
|
|
647
|
+
const width = colWidths[col] || 12;
|
|
648
|
+
headerLine += ` ${bold(col.padEnd(width))} │`;
|
|
649
|
+
}
|
|
650
|
+
let divider = dim("─".repeat(5)) + "┼";
|
|
651
|
+
for (const col of this.columns) {
|
|
652
|
+
const width = colWidths[col] || 12;
|
|
653
|
+
divider += "─".repeat(width + 2) + "┼";
|
|
654
|
+
}
|
|
655
|
+
process.stdout.write(headerLine + "\n");
|
|
656
|
+
process.stdout.write(divider + "\n");
|
|
657
|
+
for (let i = 0; i < rowsToPrint.length; i++) {
|
|
658
|
+
const row = rowsToPrint[i];
|
|
659
|
+
let rowLine = dim(String(i + 1).padStart(5)) + " │";
|
|
660
|
+
for (const col of this.columns) {
|
|
661
|
+
const width = colWidths[col] || 12;
|
|
662
|
+
const val = row[col];
|
|
663
|
+
const cell = val === null || val === undefined ? "null" : String(val);
|
|
664
|
+
rowLine += ` ${cell.padEnd(width)} │`;
|
|
665
|
+
}
|
|
666
|
+
process.stdout.write(rowLine + "\n");
|
|
667
|
+
}
|
|
668
|
+
if (this.loadedRows.length > maxRows || !this.isStreamExhausted) {
|
|
669
|
+
process.stdout.write(dim(`\nShowing first ${rowsToPrint.length} rows (Stream has more rows). Run in interactive terminal to explore full dataset.\n`));
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
//# sourceMappingURL=viewer.js.map
|