toolcraft 0.0.232 → 0.0.233
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/composition.json +3 -3
- package/dist/applied-default.d.ts +3 -0
- package/dist/applied-default.js +16 -0
- package/dist/cli.js +508 -204
- package/dist/composition.json +3 -3
- package/dist/discriminator.d.ts +9 -0
- package/dist/discriminator.js +32 -0
- package/dist/error-report.d.ts +2 -18
- package/dist/error-report.js +12 -14
- package/dist/http.js +8 -1
- package/dist/human-in-loop/gate.js +6 -0
- package/dist/index.compile-check.js +22 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.js +50 -0
- package/dist/json-schema-converter.d.ts +14 -6
- package/dist/json-schema-converter.js +150 -20
- package/dist/mcp-proxy-metadata.d.ts +2 -0
- package/dist/mcp-proxy-metadata.js +13 -0
- package/dist/mcp-proxy.d.ts +1 -0
- package/dist/mcp-proxy.js +65 -47
- package/dist/mcp-result.d.ts +3 -0
- package/dist/mcp-result.js +10 -0
- package/dist/mcp.js +211 -153
- package/dist/redaction.js +26 -17
- package/dist/renderer.js +40 -29
- package/dist/schema-member-names.d.ts +2 -0
- package/dist/schema-member-names.js +39 -0
- package/dist/schema-scope.d.ts +1 -0
- package/dist/schema-scope.js +29 -16
- package/dist/schema.d.ts +1 -1
- package/dist/sdk.d.ts +2 -1
- package/dist/sdk.js +164 -50
- package/dist/stream.js +24 -23
- package/dist/testing/diagnostic-value.d.ts +1 -0
- package/dist/testing/diagnostic-value.js +57 -0
- package/dist/testing/fakes.js +2 -1
- package/dist/testing/harness.js +1 -0
- package/dist/testing/memory-fs.js +42 -1
- package/dist/testing/parity.js +50 -6
- package/dist/union-validation.d.ts +5 -0
- package/dist/union-validation.js +36 -0
- package/node_modules/@poe-code/process-runner/dist/types.d.ts +2 -0
- package/node_modules/mcp-oauth-server/dist/index.js +47 -6
- package/node_modules/tiny-http-mcp-server/dist/cli.js +14 -2
- package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-http-mcp-server/dist/http-server.d.ts +7 -5
- package/node_modules/tiny-http-mcp-server/dist/http-server.js +3 -11
- package/node_modules/tiny-http-mcp-server/dist/http-transport.d.ts +6 -1
- package/node_modules/tiny-http-mcp-server/dist/http-transport.js +196 -15
- package/node_modules/tiny-http-mcp-server/dist/index.d.ts +2 -2
- package/node_modules/tiny-http-mcp-server/dist/modern-headers.d.ts +3 -0
- package/node_modules/tiny-http-mcp-server/dist/modern-headers.js +35 -0
- package/node_modules/tiny-http-mcp-server/dist/parse-body.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/dist/parse-body.js +9 -5
- package/node_modules/tiny-http-mcp-server/dist/test-support.js +17 -0
- package/node_modules/tiny-http-mcp-server/dist/testing.d.ts +1 -1
- package/node_modules/tiny-http-mcp-server/dist/testing.js +3 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +21 -4
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +14 -6
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.js +8 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.js +50 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +21 -3
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/README.md +1 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +178 -101
- package/node_modules/tiny-mcp-client/dist/index.js +4696 -291
- package/node_modules/tiny-mcp-client/package.json +2 -1
- package/node_modules/tiny-stdio-mcp-server/dist/base64.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/base64.js +5 -0
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/content/audio.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/convert.d.ts +5 -1
- package/node_modules/tiny-stdio-mcp-server/dist/content/convert.js +41 -6
- package/node_modules/tiny-stdio-mcp-server/dist/content/file.d.ts +5 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/image.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/headers.d.ts +10 -0
- package/node_modules/tiny-stdio-mcp-server/dist/headers.js +180 -0
- package/node_modules/tiny-stdio-mcp-server/dist/index.d.ts +5 -5
- package/node_modules/tiny-stdio-mcp-server/dist/index.js +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.d.ts +220 -0
- package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.js +1995 -0
- package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.js +38 -25
- package/node_modules/tiny-stdio-mcp-server/dist/metadata.d.ts +2 -0
- package/node_modules/tiny-stdio-mcp-server/dist/metadata.js +37 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.d.ts +4 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.js +53 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol.d.ts +17 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol.js +228 -0
- package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.js +18 -0
- package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.d.ts +18 -0
- package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.js +34 -0
- package/node_modules/tiny-stdio-mcp-server/dist/schema.d.ts +4 -1
- package/node_modules/tiny-stdio-mcp-server/dist/server.d.ts +9 -4
- package/node_modules/tiny-stdio-mcp-server/dist/server.js +334 -135
- package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.js +50 -28
- package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.d.ts +11 -0
- package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.js +128 -0
- package/node_modules/tiny-stdio-mcp-server/dist/types.d.ts +58 -18
- package/node_modules/tiny-stdio-mcp-server/dist/types.js +2 -1
- package/node_modules/tiny-stdio-mcp-server/dist/uri.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/uri.js +41 -0
- package/node_modules/tiny-stdio-mcp-server/package.json +8 -0
- package/node_modules/toolcraft-design/dist/command-registry.d.ts +11 -0
- package/node_modules/toolcraft-design/dist/command-registry.js +24 -0
- package/node_modules/toolcraft-design/dist/dashboard/ansi.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/ansi.js +81 -27
- package/node_modules/toolcraft-design/dist/dashboard/buffer.js +39 -25
- package/node_modules/toolcraft-design/dist/dashboard/components/border.js +6 -5
- package/node_modules/toolcraft-design/dist/dashboard/components/footer.js +26 -21
- package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.js +12 -6
- package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.d.ts +2 -1
- package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.js +51 -10
- package/node_modules/toolcraft-design/dist/dashboard/dashboard.d.ts +4 -0
- package/node_modules/toolcraft-design/dist/dashboard/dashboard.js +77 -4
- package/node_modules/toolcraft-design/dist/dashboard/index.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/index.js +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/keymap.js +26 -6
- package/node_modules/toolcraft-design/dist/dashboard/layout.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/dashboard/layout.js +19 -3
- package/node_modules/toolcraft-design/dist/dashboard/line-buffer.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/dashboard/line-buffer.js +63 -0
- package/node_modules/toolcraft-design/dist/dashboard/output-preview.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/dashboard/output-preview.js +70 -0
- package/node_modules/toolcraft-design/dist/dashboard/snapshot.js +3 -1
- package/node_modules/toolcraft-design/dist/dashboard/store.js +9 -3
- package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.js +108 -0
- package/node_modules/toolcraft-design/dist/dashboard/terminal-width.js +3 -1
- package/node_modules/toolcraft-design/dist/dashboard/terminal.js +11 -1
- package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.js +126 -0
- package/node_modules/toolcraft-design/dist/dashboard/types.d.ts +3 -1
- package/node_modules/toolcraft-design/dist/event-groups.d.ts +19 -0
- package/node_modules/toolcraft-design/dist/event-groups.js +49 -0
- package/node_modules/toolcraft-design/dist/index.d.ts +6 -1
- package/node_modules/toolcraft-design/dist/index.js +5 -1
- package/node_modules/toolcraft-design/dist/inline-notice.d.ts +14 -0
- package/node_modules/toolcraft-design/dist/inline-notice.js +28 -0
- package/node_modules/toolcraft-design/dist/metric.d.ts +8 -0
- package/node_modules/toolcraft-design/dist/metric.js +21 -0
- package/node_modules/toolcraft-design/dist/overlay-manager.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/overlay-manager.js +10 -0
- package/node_modules/toolcraft-design/dist/primitives.d.ts +12 -0
- package/node_modules/toolcraft-design/dist/primitives.js +7 -0
- package/node_modules/toolcraft-design/dist/progress-group.d.ts +7 -0
- package/node_modules/toolcraft-design/dist/progress-group.js +10 -0
- package/node_modules/toolcraft-design/dist/render-performance.d.ts +33 -0
- package/node_modules/toolcraft-design/dist/render-performance.js +71 -0
- package/node_modules/toolcraft-design/dist/task-tree.d.ts +22 -0
- package/node_modules/toolcraft-design/dist/task-tree.js +79 -0
- package/node_modules/toolcraft-design/dist/viewport.d.ts +18 -0
- package/node_modules/toolcraft-design/dist/viewport.js +56 -0
- package/node_modules/toolcraft-schema/dist/clone-default.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/clone-default.js +26 -0
- package/node_modules/toolcraft-schema/dist/index.compile-check.js +1 -0
- package/node_modules/toolcraft-schema/dist/index.d.ts +18 -9
- package/node_modules/toolcraft-schema/dist/index.js +46 -8
- package/node_modules/toolcraft-schema/dist/json-schema/compiler.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json-schema/compiler.js +6 -0
- package/node_modules/toolcraft-schema/dist/json-schema/evaluate.js +6 -3
- package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.js +69 -0
- package/node_modules/toolcraft-schema/dist/json-schema/types.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json.d.ts +4 -1
- package/node_modules/toolcraft-schema/dist/json.js +57 -1
- package/node_modules/toolcraft-schema/dist/native-json-schema.d.ts +10 -0
- package/node_modules/toolcraft-schema/dist/native-json-schema.js +11 -0
- package/node_modules/toolcraft-schema/dist/validate.d.ts +5 -1
- package/node_modules/toolcraft-schema/dist/validate.js +61 -47
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +6 -3
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { createRenderPerformanceMonitor, formatRenderPerformance } from "../render-performance.js";
|
|
1
2
|
import { createLogger } from "../components/logger.js";
|
|
2
3
|
import { resolveOutputFormat } from "../internal/output-format.js";
|
|
3
4
|
import { ScreenBuffer, diff } from "./buffer.js";
|
|
4
5
|
import { renderBorder } from "./components/border.js";
|
|
5
6
|
import { defaultHints, renderFooter } from "./components/footer.js";
|
|
6
7
|
import { renderOutputPane } from "./components/output-pane.js";
|
|
7
|
-
import { renderStatsPane } from "./components/stats-pane.js";
|
|
8
|
+
import { renderCompactStatsPane, renderStatsPane } from "./components/stats-pane.js";
|
|
8
9
|
import { createKeymap } from "./keymap.js";
|
|
9
10
|
import { computeDashboardLayout } from "./layout.js";
|
|
10
11
|
import { createStore } from "./store.js";
|
|
@@ -32,6 +33,11 @@ export function createDashboard(opts = {}) {
|
|
|
32
33
|
let unsubscribeResize;
|
|
33
34
|
let started = false;
|
|
34
35
|
let destroyed = false;
|
|
36
|
+
const performanceMonitor = createRenderPerformanceMonitor();
|
|
37
|
+
let showPerformance = false;
|
|
38
|
+
let scrollOffset = 0;
|
|
39
|
+
let heldOutput;
|
|
40
|
+
let renderTimer;
|
|
35
41
|
function appendOutput(item) {
|
|
36
42
|
if (destroyed) {
|
|
37
43
|
return;
|
|
@@ -61,21 +67,74 @@ export function createDashboard(opts = {}) {
|
|
|
61
67
|
driver.hideCursor();
|
|
62
68
|
render();
|
|
63
69
|
const activeStore = getStore();
|
|
70
|
+
let previousStats = activeStore.getState().stats;
|
|
71
|
+
let lastStatsPaint = -Infinity;
|
|
64
72
|
unsubscribeStore = activeStore.onChange(() => {
|
|
65
|
-
|
|
73
|
+
performanceMonitor.request("update");
|
|
74
|
+
const stats = activeStore.getState().stats;
|
|
75
|
+
if (stats !== previousStats) {
|
|
76
|
+
const statusChanged = stats.status !== previousStats.status;
|
|
77
|
+
previousStats = stats;
|
|
78
|
+
if (statusChanged || Date.now() - lastStatsPaint >= 16) {
|
|
79
|
+
lastStatsPaint = Date.now();
|
|
80
|
+
render();
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
renderTimer ??= setTimeout(render, 16);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (heldOutput === undefined && renderTimer === undefined) {
|
|
87
|
+
renderTimer = setTimeout(render, 16);
|
|
88
|
+
}
|
|
66
89
|
});
|
|
67
90
|
unsubscribeKeypress = driver.onKeypress((event) => {
|
|
68
91
|
const command = resolveCommand(event);
|
|
69
92
|
if (command === undefined) {
|
|
70
93
|
return;
|
|
71
94
|
}
|
|
95
|
+
performanceMonitor.request("input");
|
|
96
|
+
if (command === "render-stats") {
|
|
97
|
+
showPerformance = !showPerformance;
|
|
98
|
+
render();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (command === "follow") {
|
|
102
|
+
heldOutput = undefined;
|
|
103
|
+
scrollOffset = 0;
|
|
104
|
+
render();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (command === "scroll-up" ||
|
|
108
|
+
command === "scroll-down" ||
|
|
109
|
+
command === "page-up" ||
|
|
110
|
+
command === "page-down") {
|
|
111
|
+
const page = Math.max(1, computeDashboardLayout({
|
|
112
|
+
totalWidth: driver.getSize().cols,
|
|
113
|
+
totalHeight: driver.getSize().rows,
|
|
114
|
+
rightPaneWidth
|
|
115
|
+
}).leftPane.height);
|
|
116
|
+
const amount = command === "page-up" || command === "page-down" ? page : 1;
|
|
117
|
+
const direction = command === "scroll-up" || command === "page-up" ? 1 : -1;
|
|
118
|
+
if (direction > 0)
|
|
119
|
+
heldOutput ??= getStore().getState().output;
|
|
120
|
+
scrollOffset = Math.max(0, scrollOffset + amount * direction);
|
|
121
|
+
if (scrollOffset === 0)
|
|
122
|
+
heldOutput = undefined;
|
|
123
|
+
renderTimer ??= setTimeout(render, 16);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
72
126
|
emitCommand(command);
|
|
73
127
|
});
|
|
74
128
|
unsubscribeResize = driver.onResize(() => {
|
|
129
|
+
performanceMonitor.request("resize");
|
|
130
|
+
previousBuffer = new ScreenBuffer(0, 0);
|
|
131
|
+
driver?.write("\u001b[2J");
|
|
75
132
|
render();
|
|
76
133
|
});
|
|
77
134
|
}
|
|
78
135
|
function stop() {
|
|
136
|
+
clearTimeout(renderTimer);
|
|
137
|
+
renderTimer = undefined;
|
|
79
138
|
unsubscribeStore?.();
|
|
80
139
|
unsubscribeKeypress?.();
|
|
81
140
|
unsubscribeResize?.();
|
|
@@ -111,9 +170,12 @@ export function createDashboard(opts = {}) {
|
|
|
111
170
|
return store;
|
|
112
171
|
}
|
|
113
172
|
function render() {
|
|
173
|
+
clearTimeout(renderTimer);
|
|
174
|
+
renderTimer = undefined;
|
|
114
175
|
if (driver === undefined) {
|
|
115
176
|
return;
|
|
116
177
|
}
|
|
178
|
+
const startedAt = performanceMonitor.begin();
|
|
117
179
|
const { cols, rows } = driver.getSize();
|
|
118
180
|
const layout = computeDashboardLayout({
|
|
119
181
|
totalWidth: cols,
|
|
@@ -127,10 +189,20 @@ export function createDashboard(opts = {}) {
|
|
|
127
189
|
rightTitle: statsTitle,
|
|
128
190
|
style: { dim: true }
|
|
129
191
|
});
|
|
130
|
-
renderOutputPane(nextBuffer, layout.leftPane, state.output);
|
|
192
|
+
scrollOffset = renderOutputPane(nextBuffer, { ...layout.leftPane, height: Math.max(0, layout.leftPane.height - (showPerformance ? 1 : 0)) }, heldOutput ?? state.output, scrollOffset);
|
|
193
|
+
if (scrollOffset === 0)
|
|
194
|
+
heldOutput = undefined;
|
|
131
195
|
renderStatsPane(nextBuffer, layout.rightPane, state.stats);
|
|
196
|
+
if (layout.summary)
|
|
197
|
+
renderCompactStatsPane(nextBuffer, layout.summary, state.stats);
|
|
132
198
|
renderFooter(nextBuffer, layout.footer, footerHints);
|
|
133
|
-
|
|
199
|
+
if (showPerformance && layout.leftPane.height > 0) {
|
|
200
|
+
nextBuffer.putInRect(layout.leftPane, layout.leftPane.height - 1, formatRenderPerformance(performanceMonitor.snapshot(), layout.leftPane.width), { dim: true });
|
|
201
|
+
}
|
|
202
|
+
const changes = diff(previousBuffer, nextBuffer);
|
|
203
|
+
driver.flush(changes);
|
|
204
|
+
performanceMonitor.end(startedAt, { changedCells: changes.length });
|
|
205
|
+
opts.onPerformance?.(performanceMonitor.snapshot());
|
|
134
206
|
previousBuffer = nextBuffer;
|
|
135
207
|
}
|
|
136
208
|
function emitCommand(command) {
|
|
@@ -159,6 +231,7 @@ export function createDashboard(opts = {}) {
|
|
|
159
231
|
appendOutput,
|
|
160
232
|
updateStats,
|
|
161
233
|
onCommand,
|
|
234
|
+
getPerformance: performanceMonitor.snapshot,
|
|
162
235
|
destroy
|
|
163
236
|
};
|
|
164
237
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { createDashboard } from "./dashboard.js";
|
|
2
|
+
export { createDashboardLineBuffer, createStreamingDashboardLineBuffer } from "./line-buffer.js";
|
|
3
|
+
export { limitOutputPreview, createOutputPreviewBuffer } from "./output-preview.js";
|
|
2
4
|
export { shouldUseInteractiveDashboard } from "./should-use-dashboard.js";
|
|
3
5
|
export type { Dashboard, DashboardOptions } from "./dashboard.js";
|
|
4
6
|
export { renderDashboardSnapshot } from "./snapshot.js";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { createDashboard } from "./dashboard.js";
|
|
2
|
+
export { createDashboardLineBuffer, createStreamingDashboardLineBuffer } from "./line-buffer.js";
|
|
3
|
+
export { limitOutputPreview, createOutputPreviewBuffer } from "./output-preview.js";
|
|
2
4
|
export { shouldUseInteractiveDashboard } from "./should-use-dashboard.js";
|
|
3
5
|
export { renderDashboardSnapshot } from "./snapshot.js";
|
|
4
6
|
export { defaultHints } from "./components/footer.js";
|
|
@@ -1,11 +1,30 @@
|
|
|
1
|
-
const commands = [
|
|
1
|
+
const commands = [
|
|
2
|
+
"forceQuit",
|
|
3
|
+
"quit",
|
|
4
|
+
"edit",
|
|
5
|
+
"pause",
|
|
6
|
+
"retry",
|
|
7
|
+
"view-log",
|
|
8
|
+
"scroll-up",
|
|
9
|
+
"scroll-down",
|
|
10
|
+
"page-up",
|
|
11
|
+
"page-down",
|
|
12
|
+
"follow",
|
|
13
|
+
"render-stats"
|
|
14
|
+
];
|
|
2
15
|
const defaultBindings = {
|
|
3
16
|
forceQuit: ["Ctrl+C"],
|
|
4
17
|
quit: ["q"],
|
|
5
18
|
edit: ["e"],
|
|
6
19
|
pause: ["p"],
|
|
7
20
|
retry: ["r"],
|
|
8
|
-
"view-log": ["l"]
|
|
21
|
+
"view-log": ["l"],
|
|
22
|
+
"scroll-up": ["up"],
|
|
23
|
+
"scroll-down": ["down"],
|
|
24
|
+
"page-up": ["pageup"],
|
|
25
|
+
"page-down": ["pagedown"],
|
|
26
|
+
follow: ["f", "F", "end"],
|
|
27
|
+
"render-stats": ["Ctrl+g"]
|
|
9
28
|
};
|
|
10
29
|
export function createKeymap(overrides, options) {
|
|
11
30
|
const resolvedCommands = options?.commands ?? commands;
|
|
@@ -85,7 +104,10 @@ function parseBinding(binding) {
|
|
|
85
104
|
if (value.length === 0) {
|
|
86
105
|
return undefined;
|
|
87
106
|
}
|
|
88
|
-
const parts = value
|
|
107
|
+
const parts = value
|
|
108
|
+
.split("+")
|
|
109
|
+
.map((part) => part.trim())
|
|
110
|
+
.filter(Boolean);
|
|
89
111
|
if (parts.length === 0) {
|
|
90
112
|
return undefined;
|
|
91
113
|
}
|
|
@@ -146,9 +168,7 @@ function matchesSingleKey(binding, event) {
|
|
|
146
168
|
if (binding.sequence !== undefined) {
|
|
147
169
|
return false;
|
|
148
170
|
}
|
|
149
|
-
if (binding.ctrl !== event.ctrl ||
|
|
150
|
-
binding.meta !== event.meta ||
|
|
151
|
-
binding.shift !== event.shift) {
|
|
171
|
+
if (binding.ctrl !== event.ctrl || binding.meta !== event.meta || binding.shift !== event.shift) {
|
|
152
172
|
return false;
|
|
153
173
|
}
|
|
154
174
|
if (binding.ch !== undefined) {
|
|
@@ -2,6 +2,7 @@ const DEFAULT_RIGHT_PANE_WIDTH = 25;
|
|
|
2
2
|
const DEFAULT_FOOTER_HEIGHT = 1;
|
|
3
3
|
const DEFAULT_BORDER_WIDTH = 1;
|
|
4
4
|
const MIN_LEFT_PANE_WIDTH = 20;
|
|
5
|
+
const MIN_READABLE_OUTPUT_WIDTH = 40;
|
|
5
6
|
export function computeDashboardLayout(opts) {
|
|
6
7
|
const totalWidth = normalizeSize(opts.totalWidth);
|
|
7
8
|
const totalHeight = normalizeSize(opts.totalHeight);
|
|
@@ -11,8 +12,8 @@ export function computeDashboardLayout(opts) {
|
|
|
11
12
|
const maxX = Math.max(0, totalWidth - 1);
|
|
12
13
|
const maxY = Math.max(0, totalHeight - 1);
|
|
13
14
|
const outerBorder = { x: 0, y: 0, width: totalWidth, height: totalHeight };
|
|
14
|
-
const innerWidth = Math.max(0, totalWidth -
|
|
15
|
-
const innerHeight = Math.max(0, totalHeight -
|
|
15
|
+
const innerWidth = Math.max(0, totalWidth - borderWidth * 2);
|
|
16
|
+
const innerHeight = Math.max(0, totalHeight - borderWidth * 2);
|
|
16
17
|
const innerX = clampCoordinate(borderWidth, maxX);
|
|
17
18
|
const innerY = clampCoordinate(borderWidth, maxY);
|
|
18
19
|
const dividerWidth = innerWidth > 0 ? 1 : 0;
|
|
@@ -41,7 +42,7 @@ export function computeDashboardLayout(opts) {
|
|
|
41
42
|
const footerY = clampCoordinate(footerDividerY + footerDividerHeight, maxY);
|
|
42
43
|
const footerDividerLeft = innerX;
|
|
43
44
|
const footerDividerRight = clampCoordinate(Math.max(innerX, totalWidth - borderWidth - 1), maxX);
|
|
44
|
-
|
|
45
|
+
const layout = {
|
|
45
46
|
outerBorder,
|
|
46
47
|
leftPane,
|
|
47
48
|
rightPane,
|
|
@@ -62,6 +63,21 @@ export function computeDashboardLayout(opts) {
|
|
|
62
63
|
right: footerDividerRight
|
|
63
64
|
}
|
|
64
65
|
};
|
|
66
|
+
if (innerWidth > 0 &&
|
|
67
|
+
requestedRightPaneWidth > 0 &&
|
|
68
|
+
availablePaneWidth < MIN_READABLE_OUTPUT_WIDTH + requestedRightPaneWidth) {
|
|
69
|
+
const summaryHeight = Math.min(2, contentHeight);
|
|
70
|
+
layout.summary = { x: innerX, y: innerY, width: innerWidth, height: summaryHeight };
|
|
71
|
+
layout.leftPane = {
|
|
72
|
+
x: innerX,
|
|
73
|
+
y: innerY + summaryHeight,
|
|
74
|
+
width: innerWidth,
|
|
75
|
+
height: Math.max(0, contentHeight - summaryHeight)
|
|
76
|
+
};
|
|
77
|
+
layout.rightPane = { x: maxX, y: innerY, width: 0, height: 0 };
|
|
78
|
+
layout.divider = { x: maxX, top: innerY, bottom: innerY };
|
|
79
|
+
}
|
|
80
|
+
return layout;
|
|
65
81
|
}
|
|
66
82
|
function computeLeftPaneWidth(availablePaneWidth, requestedRightPaneWidth) {
|
|
67
83
|
if (availablePaneWidth <= 0) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function createDashboardLineBuffer(emit: (line: string) => void): {
|
|
2
|
+
push(chunk: string): void;
|
|
3
|
+
flush(): void;
|
|
4
|
+
preview(): string;
|
|
5
|
+
};
|
|
6
|
+
export declare function createStreamingDashboardLineBuffer(emit: (line: string, id: string) => void): {
|
|
7
|
+
push(chunk: string): void;
|
|
8
|
+
flush(): void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { limitOutputPreview, MAX_OUTPUT_PREVIEW_CHARS, OUTPUT_TRUNCATION_NOTICE, retainOutputTail } from "./output-preview.js";
|
|
3
|
+
import { createTerminalStringFilter } from "./terminal-strings.js";
|
|
4
|
+
export function createDashboardLineBuffer(emit) {
|
|
5
|
+
let pending = "";
|
|
6
|
+
let omitted = false;
|
|
7
|
+
const strings = createTerminalStringFilter();
|
|
8
|
+
return {
|
|
9
|
+
preview() {
|
|
10
|
+
const line = pending.endsWith("\r") ? pending.slice(0, -1) : pending;
|
|
11
|
+
return (omitted ? OUTPUT_TRUNCATION_NOTICE : "") + line;
|
|
12
|
+
},
|
|
13
|
+
push(chunk) {
|
|
14
|
+
const text = pending + strings.push(chunk);
|
|
15
|
+
let start = 0;
|
|
16
|
+
let newline = text.indexOf("\n");
|
|
17
|
+
while (newline !== -1) {
|
|
18
|
+
const raw = text.slice(start, newline);
|
|
19
|
+
const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
|
|
20
|
+
emit(limitOutputPreview((omitted ? OUTPUT_TRUNCATION_NOTICE : "") + line));
|
|
21
|
+
omitted = false;
|
|
22
|
+
start = newline + 1;
|
|
23
|
+
newline = text.indexOf("\n", start);
|
|
24
|
+
}
|
|
25
|
+
const remaining = text.slice(start);
|
|
26
|
+
if (remaining.length > MAX_OUTPUT_PREVIEW_CHARS)
|
|
27
|
+
omitted = true;
|
|
28
|
+
const budget = MAX_OUTPUT_PREVIEW_CHARS - (omitted ? OUTPUT_TRUNCATION_NOTICE.length : 0);
|
|
29
|
+
pending = retainOutputTail(remaining, budget);
|
|
30
|
+
},
|
|
31
|
+
flush() {
|
|
32
|
+
if (pending.length === 0)
|
|
33
|
+
return;
|
|
34
|
+
const line = pending.endsWith("\r") ? pending.slice(0, -1) : pending;
|
|
35
|
+
emit((omitted ? OUTPUT_TRUNCATION_NOTICE : "") + line);
|
|
36
|
+
pending = "";
|
|
37
|
+
omitted = false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function createStreamingDashboardLineBuffer(emit) {
|
|
42
|
+
let id = randomUUID();
|
|
43
|
+
let lastPreview = "";
|
|
44
|
+
const lines = createDashboardLineBuffer(line => {
|
|
45
|
+
if (line.length === 0 || line !== lastPreview)
|
|
46
|
+
emit(line, id);
|
|
47
|
+
id = randomUUID();
|
|
48
|
+
lastPreview = "";
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
push(chunk) {
|
|
52
|
+
if (chunk.length === 0)
|
|
53
|
+
return;
|
|
54
|
+
lines.push(chunk);
|
|
55
|
+
const preview = lines.preview();
|
|
56
|
+
if (preview.length > 0 && preview !== lastPreview) {
|
|
57
|
+
lastPreview = preview;
|
|
58
|
+
emit(preview, id);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
flush: lines.flush
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const MAX_OUTPUT_PREVIEW_CHARS = 16384;
|
|
2
|
+
export declare const OUTPUT_TRUNCATION_NOTICE = "[Output truncated: showing latest text]\n";
|
|
3
|
+
export declare function retainOutputTail(text: string, maxChars: number): string;
|
|
4
|
+
export declare function limitOutputPreview(text: string): string;
|
|
5
|
+
/** Keep live deltas bounded without rebuilding the complete preview for every delta. */
|
|
6
|
+
export declare function createOutputPreviewBuffer(): {
|
|
7
|
+
push(text: string): void;
|
|
8
|
+
text(): string;
|
|
9
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { createTerminalStringFilter, terminalControlTailStart } from "./terminal-strings.js";
|
|
3
|
+
export const MAX_OUTPUT_PREVIEW_CHARS = 16_384;
|
|
4
|
+
export const OUTPUT_TRUNCATION_NOTICE = "[Output truncated: showing latest text]\n";
|
|
5
|
+
export function retainOutputTail(text, maxChars) {
|
|
6
|
+
let start = Math.max(0, text.length - maxChars);
|
|
7
|
+
if (start > 0) {
|
|
8
|
+
const newline = text.indexOf("\n", start);
|
|
9
|
+
if (newline !== -1 && newline < text.length - 1)
|
|
10
|
+
start = newline + 1;
|
|
11
|
+
start = terminalControlTailStart(text, start);
|
|
12
|
+
const firstCodeUnit = text.charCodeAt(start);
|
|
13
|
+
if (firstCodeUnit >= 0xdc00 && firstCodeUnit <= 0xdfff)
|
|
14
|
+
start += 1;
|
|
15
|
+
}
|
|
16
|
+
// Materialize the bounded tail rather than retaining a large substring backing store.
|
|
17
|
+
// UTF-16 preserves lone surrogate code units as well as complete Unicode text.
|
|
18
|
+
return Buffer.from(text.slice(start), "utf16le").toString("utf16le");
|
|
19
|
+
}
|
|
20
|
+
export function limitOutputPreview(text) {
|
|
21
|
+
text = createTerminalStringFilter().push(text);
|
|
22
|
+
if (text.length <= MAX_OUTPUT_PREVIEW_CHARS)
|
|
23
|
+
return text;
|
|
24
|
+
return (OUTPUT_TRUNCATION_NOTICE +
|
|
25
|
+
retainOutputTail(text, MAX_OUTPUT_PREVIEW_CHARS - OUTPUT_TRUNCATION_NOTICE.length));
|
|
26
|
+
}
|
|
27
|
+
/** Keep live deltas bounded without rebuilding the complete preview for every delta. */
|
|
28
|
+
export function createOutputPreviewBuffer() {
|
|
29
|
+
const strings = createTerminalStringFilter();
|
|
30
|
+
const chunks = [];
|
|
31
|
+
let chars = 0;
|
|
32
|
+
let omitted = false;
|
|
33
|
+
const tailBudget = MAX_OUTPUT_PREVIEW_CHARS - OUTPUT_TRUNCATION_NOTICE.length + 1;
|
|
34
|
+
return {
|
|
35
|
+
push(text) {
|
|
36
|
+
text = strings.push(text);
|
|
37
|
+
if (text.length === 0)
|
|
38
|
+
return;
|
|
39
|
+
if (text.length > MAX_OUTPUT_PREVIEW_CHARS) {
|
|
40
|
+
chunks.length = 0;
|
|
41
|
+
chars = 0;
|
|
42
|
+
omitted = true;
|
|
43
|
+
text = retainOutputTail(text, tailBudget);
|
|
44
|
+
}
|
|
45
|
+
chunks.push(text);
|
|
46
|
+
chars += text.length;
|
|
47
|
+
if (chars > MAX_OUTPUT_PREVIEW_CHARS)
|
|
48
|
+
omitted = true;
|
|
49
|
+
if (!omitted)
|
|
50
|
+
return;
|
|
51
|
+
while (chars > tailBudget) {
|
|
52
|
+
const first = chunks[0];
|
|
53
|
+
const excess = chars - tailBudget;
|
|
54
|
+
if (first.length <= excess) {
|
|
55
|
+
chars -= first.length;
|
|
56
|
+
chunks.shift();
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const start = terminalControlTailStart(first, excess);
|
|
60
|
+
chunks[0] = first.slice(start);
|
|
61
|
+
chars -= start;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
text() {
|
|
66
|
+
const text = chunks.join("");
|
|
67
|
+
return limitOutputPreview((omitted ? OUTPUT_TRUNCATION_NOTICE : "") + text);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -2,7 +2,7 @@ import { ScreenBuffer, cellToAnsi } from "./buffer.js";
|
|
|
2
2
|
import { renderBorder } from "./components/border.js";
|
|
3
3
|
import { defaultHints, renderFooter } from "./components/footer.js";
|
|
4
4
|
import { renderOutputPane } from "./components/output-pane.js";
|
|
5
|
-
import { renderStatsPane } from "./components/stats-pane.js";
|
|
5
|
+
import { renderCompactStatsPane, renderStatsPane } from "./components/stats-pane.js";
|
|
6
6
|
import { computeDashboardLayout } from "./layout.js";
|
|
7
7
|
const SNAPSHOT_WIDTH = 80;
|
|
8
8
|
const SNAPSHOT_HEIGHT = 20;
|
|
@@ -27,6 +27,8 @@ export function renderDashboardSnapshot(opts = {}) {
|
|
|
27
27
|
});
|
|
28
28
|
renderOutputPane(buffer, layout.leftPane, items);
|
|
29
29
|
renderStatsPane(buffer, layout.rightPane, stats);
|
|
30
|
+
if (layout.summary)
|
|
31
|
+
renderCompactStatsPane(buffer, layout.summary, stats);
|
|
30
32
|
renderFooter(buffer, layout.footer, defaultHints());
|
|
31
33
|
return bufferToAnsi(buffer);
|
|
32
34
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { limitOutputPreview } from "./output-preview.js";
|
|
1
2
|
const MAX_RETAINED_OUTPUT = 256;
|
|
2
3
|
export function createStore() {
|
|
3
4
|
let state = {
|
|
@@ -20,9 +21,14 @@ export function createStore() {
|
|
|
20
21
|
return state;
|
|
21
22
|
}
|
|
22
23
|
function appendOutput(item) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const preview = limitOutputPreview(item.text);
|
|
25
|
+
const retainedItem = preview === item.text ? item : { ...item, text: preview };
|
|
26
|
+
const existing = item.id === undefined ? -1 : state.output.findIndex((entry) => entry.id === item.id);
|
|
27
|
+
const next = existing !== -1
|
|
28
|
+
? state.output.map((entry, index) => index === existing ? retainedItem : entry)
|
|
29
|
+
: state.output.length >= MAX_RETAINED_OUTPUT
|
|
30
|
+
? [...state.output.slice(state.output.length - MAX_RETAINED_OUTPUT + 1), retainedItem]
|
|
31
|
+
: [...state.output, retainedItem];
|
|
26
32
|
state = { ...state, output: next };
|
|
27
33
|
notify();
|
|
28
34
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Remove nonprinting strings and retain complete CSI controls before presentation trims them. */
|
|
2
|
+
export declare function createTerminalStringFilter(): {
|
|
3
|
+
push(text: string): string;
|
|
4
|
+
};
|
|
5
|
+
/** Advance a tail boundary past a control if it would retain only its parameters. */
|
|
6
|
+
export declare function terminalControlTailStart(text: string, start: number): number;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Normal SGR/cursor sequences are short; malformed controls must not grow retained state.
|
|
2
|
+
const MAX_CONTROL_SEQUENCE_CHARS = 1_024;
|
|
3
|
+
/** Remove nonprinting strings and retain complete CSI controls before presentation trims them. */
|
|
4
|
+
export function createTerminalStringFilter() {
|
|
5
|
+
let hidden = false;
|
|
6
|
+
let allowBell = false;
|
|
7
|
+
let escape = false;
|
|
8
|
+
let csi;
|
|
9
|
+
let oversizedCsi = false;
|
|
10
|
+
return {
|
|
11
|
+
push(text) {
|
|
12
|
+
if (!hidden && !escape && csi === undefined && !text.includes("\u001b") && !text.includes("\u009b") && !text.includes("\u0090") &&
|
|
13
|
+
!text.includes("\u0098") && !text.includes("\u009d") && !text.includes("\u009e") && !text.includes("\u009f"))
|
|
14
|
+
return text;
|
|
15
|
+
let output = "";
|
|
16
|
+
for (const ch of text) {
|
|
17
|
+
if ((ch === "\u0018" || ch === "\u001a") && (hidden || escape || csi !== undefined)) {
|
|
18
|
+
hidden = false;
|
|
19
|
+
escape = false;
|
|
20
|
+
csi = undefined;
|
|
21
|
+
oversizedCsi = false;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (csi !== undefined) {
|
|
25
|
+
if (ch === "\u001b") {
|
|
26
|
+
csi = undefined;
|
|
27
|
+
oversizedCsi = false;
|
|
28
|
+
escape = true;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (!oversizedCsi) {
|
|
32
|
+
csi += ch;
|
|
33
|
+
if (csi.length > MAX_CONTROL_SEQUENCE_CHARS) {
|
|
34
|
+
oversizedCsi = true;
|
|
35
|
+
csi = "";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const code = ch.charCodeAt(0);
|
|
39
|
+
if (code >= 0x40 && code <= 0x7e) {
|
|
40
|
+
if (!oversizedCsi)
|
|
41
|
+
output += csi;
|
|
42
|
+
csi = undefined;
|
|
43
|
+
oversizedCsi = false;
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (hidden) {
|
|
48
|
+
if (ch === "\u009c" || (allowBell && ch === "\u0007") || (escape && ch === "\\")) {
|
|
49
|
+
hidden = false;
|
|
50
|
+
escape = false;
|
|
51
|
+
}
|
|
52
|
+
else
|
|
53
|
+
escape = ch === "\u001b";
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (escape) {
|
|
57
|
+
escape = false;
|
|
58
|
+
if (ch === "[") {
|
|
59
|
+
csi = "\u001b[";
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (ch === "]" || ch === "P" || ch === "X" || ch === "^" || ch === "_") {
|
|
63
|
+
hidden = true;
|
|
64
|
+
allowBell = ch === "]";
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
output += "\u001b";
|
|
68
|
+
}
|
|
69
|
+
if (ch === "\u001b")
|
|
70
|
+
escape = true;
|
|
71
|
+
else if (ch === "\u009b")
|
|
72
|
+
csi = ch;
|
|
73
|
+
else if (ch === "\u0090" || ch === "\u0098" || ch === "\u009d" || ch === "\u009e" || ch === "\u009f") {
|
|
74
|
+
hidden = true;
|
|
75
|
+
allowBell = ch === "\u009d";
|
|
76
|
+
}
|
|
77
|
+
else
|
|
78
|
+
output += ch;
|
|
79
|
+
}
|
|
80
|
+
return output;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Advance a tail boundary past a control if it would retain only its parameters. */
|
|
85
|
+
export function terminalControlTailStart(text, start) {
|
|
86
|
+
if (!text.includes("\u001b") && !text.includes("\u009b"))
|
|
87
|
+
return start;
|
|
88
|
+
for (let index = 0; index < start; index++) {
|
|
89
|
+
const ch = text[index];
|
|
90
|
+
if (ch !== "\u001b" && ch !== "\u009b")
|
|
91
|
+
continue;
|
|
92
|
+
let end = index + 1;
|
|
93
|
+
if (ch === "\u009b" || text[end] === "[") {
|
|
94
|
+
if (ch === "\u001b")
|
|
95
|
+
end++;
|
|
96
|
+
while (end < text.length) {
|
|
97
|
+
const code = text.charCodeAt(end);
|
|
98
|
+
if (code >= 0x40 && code <= 0x7e)
|
|
99
|
+
break;
|
|
100
|
+
end++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (start <= end)
|
|
104
|
+
return Math.min(end + 1, text.length);
|
|
105
|
+
index = end;
|
|
106
|
+
}
|
|
107
|
+
return start;
|
|
108
|
+
}
|
|
@@ -2,7 +2,7 @@ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme
|
|
|
2
2
|
export function graphemes(value) {
|
|
3
3
|
for (let index = 0; index < value.length; index += 1) {
|
|
4
4
|
const code = value.charCodeAt(index);
|
|
5
|
-
if (code < 0x20 || code > 0x7e) {
|
|
5
|
+
if ((code < 0x20 || code > 0x7e) && (code < 0x4e00 || code > 0x9fff)) {
|
|
6
6
|
return Array.from(graphemeSegmenter.segment(value), ({ segment }) => segment);
|
|
7
7
|
}
|
|
8
8
|
}
|
|
@@ -20,6 +20,8 @@ export function displayWidth(value, startColumn = 0) {
|
|
|
20
20
|
return column - startColumn;
|
|
21
21
|
}
|
|
22
22
|
export function expandTabs(value, startColumn = 0) {
|
|
23
|
+
if (!value.includes("\t"))
|
|
24
|
+
return value;
|
|
23
25
|
let column = startColumn;
|
|
24
26
|
let expanded = "";
|
|
25
27
|
for (const segment of graphemes(value)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import readline from "node:readline";
|
|
2
2
|
import { PassThrough } from "node:stream";
|
|
3
3
|
import { cellToAnsi } from "./buffer.js";
|
|
4
|
+
import { graphemeWidth } from "./terminal-width.js";
|
|
4
5
|
export function createTerminalDriver(opts) {
|
|
5
6
|
const stdin = (opts?.stdin ?? process.stdin);
|
|
6
7
|
const stdout = (opts?.stdout ?? process.stdout);
|
|
@@ -87,8 +88,17 @@ export function createTerminalDriver(opts) {
|
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
89
90
|
let output = "";
|
|
91
|
+
let cursorX;
|
|
92
|
+
let cursorY;
|
|
90
93
|
for (const change of changes) {
|
|
91
|
-
|
|
94
|
+
if (change.cell.ch.length === 0)
|
|
95
|
+
continue;
|
|
96
|
+
if (change.x !== cursorX || change.y !== cursorY) {
|
|
97
|
+
output += cursorPositionAnsi(change.x, change.y);
|
|
98
|
+
}
|
|
99
|
+
output += cellToAnsi(change.cell);
|
|
100
|
+
cursorX = change.x + graphemeWidth(change.cell.ch);
|
|
101
|
+
cursorY = change.y;
|
|
92
102
|
}
|
|
93
103
|
write(output);
|
|
94
104
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|