@dungle-scrubs/tallow 0.9.4 → 0.9.6
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/cli.js +7 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/interactive-mode-patch.d.ts +24 -12
- package/dist/interactive-mode-patch.d.ts.map +1 -1
- package/dist/interactive-mode-patch.js +229 -146
- package/dist/interactive-mode-patch.js.map +1 -1
- package/dist/interactive-reset.d.ts +49 -0
- package/dist/interactive-reset.d.ts.map +1 -0
- package/dist/interactive-reset.js +40 -0
- package/dist/interactive-reset.js.map +1 -0
- package/dist/pi-tui-editor-patch.d.ts +10 -0
- package/dist/pi-tui-editor-patch.d.ts.map +1 -0
- package/dist/pi-tui-editor-patch.js +159 -0
- package/dist/pi-tui-editor-patch.js.map +1 -0
- package/dist/pi-tui-patch.d.ts +2 -0
- package/dist/pi-tui-patch.d.ts.map +1 -0
- package/dist/pi-tui-patch.js +563 -0
- package/dist/pi-tui-patch.js.map +1 -0
- package/dist/pi-tui-settings-list-patch.d.ts +11 -0
- package/dist/pi-tui-settings-list-patch.d.ts.map +1 -0
- package/dist/pi-tui-settings-list-patch.js +38 -0
- package/dist/pi-tui-settings-list-patch.js.map +1 -0
- package/dist/reset-diagnostics.d.ts +69 -0
- package/dist/reset-diagnostics.d.ts.map +1 -0
- package/dist/reset-diagnostics.js +41 -0
- package/dist/reset-diagnostics.js.map +1 -0
- package/dist/sdk.d.ts +5 -21
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +180 -149
- package/dist/sdk.js.map +1 -1
- package/dist/workspace-transition-interactive.d.ts +1 -0
- package/dist/workspace-transition-interactive.d.ts.map +1 -1
- package/dist/workspace-transition-interactive.js +7 -17
- package/dist/workspace-transition-interactive.js.map +1 -1
- package/extensions/__integration__/audit-findings.test.ts +4 -5
- package/extensions/_icons/index.ts +2 -4
- package/extensions/_shared/__tests__/image-metadata.test.ts +33 -0
- package/extensions/_shared/__tests__/terminal-links.test.ts +18 -0
- package/extensions/_shared/image-metadata.ts +99 -0
- package/extensions/_shared/inline-preview.ts +1 -1
- package/extensions/_shared/terminal-links.ts +22 -0
- package/extensions/ask-user-question-tool/index.ts +0 -3
- package/extensions/clear/__tests__/clear.test.ts +269 -2
- package/extensions/command-expansion/index.ts +1 -1
- package/extensions/context-files/index.ts +5 -1
- package/extensions/context-fork/__tests__/context-fork.test.ts +94 -1
- package/extensions/context-fork/extension.json +1 -1
- package/extensions/context-fork/index.ts +32 -0
- package/extensions/edit-tool-enhanced/index.ts +2 -1
- package/extensions/hooks/index.ts +33 -11
- package/extensions/loop/index.ts +14 -1
- package/extensions/lsp/index.ts +64 -13
- package/extensions/lsp/package.json +2 -2
- package/extensions/random-spinner/index.ts +7 -642
- package/extensions/read-tool-enhanced/index.ts +6 -8
- package/extensions/render-stabilizer/__tests__/render-stabilizer.test.ts +2 -3
- package/extensions/render-stabilizer/index.ts +6 -6
- package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +26 -0
- package/extensions/slash-command-bridge/index.ts +14 -2
- package/extensions/subagent-tool/model-resolver.ts +274 -7
- package/extensions/tasks/commands/register-tasks-extension.ts +9 -9
- package/extensions/teams-tool/tools/register-extension.ts +1 -3
- package/extensions/web-search-tool/index.ts +2 -1
- package/extensions/write-tool-enhanced/index.ts +2 -1
- package/node_modules/@mariozechner/pi-tui/README.md +56 -34
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts +18 -13
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js +182 -113
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js +3 -3
- package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts +45 -36
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.js +489 -325
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts +1 -99
- package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/image.js +17 -192
- package/node_modules/@mariozechner/pi-tui/dist/components/image.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/input.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/input.js +57 -60
- package/node_modules/@mariozechner/pi-tui/dist/components/input.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts +2 -69
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.js +5 -102
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js +111 -53
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts +19 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js +78 -67
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts +1 -25
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js +13 -50
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +8 -10
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/index.js +6 -9
- package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +108 -238
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +108 -365
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +33 -48
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.js +239 -155
- package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts +14 -94
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js +44 -186
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +13 -58
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js +78 -111
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +24 -110
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.js +188 -435
- package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts +0 -18
- package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/utils.js +251 -119
- package/node_modules/@mariozechner/pi-tui/dist/utils.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/package.json +6 -6
- package/node_modules/@mariozechner/pi-tui/src/__tests__/__snapshots__/render.test.ts.snap +3 -40
- package/node_modules/@mariozechner/pi-tui/src/__tests__/image-component.test.ts +71 -81
- package/node_modules/@mariozechner/pi-tui/src/__tests__/render.test.ts +0 -33
- package/node_modules/@mariozechner/pi-tui/src/__tests__/terminal-image.test.ts +93 -334
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +1 -1
- package/node_modules/@mariozechner/pi-tui/src/__tests__/utils.test.ts +11 -196
- package/node_modules/@mariozechner/pi-tui/src/autocomplete.ts +228 -142
- package/node_modules/@mariozechner/pi-tui/src/components/cancellable-loader.ts +3 -3
- package/node_modules/@mariozechner/pi-tui/src/components/editor.ts +624 -390
- package/node_modules/@mariozechner/pi-tui/src/components/image.ts +17 -227
- package/node_modules/@mariozechner/pi-tui/src/components/input.ts +71 -63
- package/node_modules/@mariozechner/pi-tui/src/components/loader.ts +5 -137
- package/node_modules/@mariozechner/pi-tui/src/components/markdown.ts +143 -52
- package/node_modules/@mariozechner/pi-tui/src/components/select-list.ts +136 -70
- package/node_modules/@mariozechner/pi-tui/src/components/settings-list.ts +12 -51
- package/node_modules/@mariozechner/pi-tui/src/index.ts +17 -36
- package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +148 -421
- package/node_modules/@mariozechner/pi-tui/src/keys.ts +253 -181
- package/node_modules/@mariozechner/pi-tui/src/terminal-image.ts +51 -252
- package/node_modules/@mariozechner/pi-tui/src/terminal.ts +78 -133
- package/node_modules/@mariozechner/pi-tui/src/tui.ts +202 -478
- package/node_modules/@mariozechner/pi-tui/src/utils.ts +289 -125
- package/node_modules/@mariozechner/pi-tui/tsconfig.build.json +1 -0
- package/package.json +13 -13
- package/packages/tallow-tui/node_modules/@types/mime-types/README.md +8 -2
- package/packages/tallow-tui/node_modules/@types/mime-types/index.d.ts +6 -0
- package/packages/tallow-tui/node_modules/@types/mime-types/package.json +9 -3
- package/packages/tallow-tui/node_modules/get-east-asian-width/lookup-data.js +18 -0
- package/packages/tallow-tui/node_modules/get-east-asian-width/lookup.js +116 -384
- package/packages/tallow-tui/node_modules/get-east-asian-width/package.json +5 -4
- package/packages/tallow-tui/node_modules/get-east-asian-width/utilities.js +24 -0
- package/packages/tallow-tui/node_modules/marked/README.md +5 -4
- package/packages/tallow-tui/node_modules/marked/bin/main.js +10 -8
- package/packages/tallow-tui/node_modules/marked/bin/marked.js +2 -1
- package/packages/tallow-tui/node_modules/marked/lib/marked.d.ts +156 -125
- package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js +67 -2179
- package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js.map +3 -3
- package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js +67 -2201
- package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js.map +3 -3
- package/packages/tallow-tui/node_modules/marked/man/marked.1 +4 -2
- package/packages/tallow-tui/node_modules/marked/man/marked.1.md +2 -1
- package/packages/tallow-tui/node_modules/marked/package.json +26 -34
- package/skills/tallow-expert/SKILL.md +1 -3
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts +0 -32
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.js +0 -46
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts +0 -52
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js +0 -89
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts +0 -14
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js +0 -55
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-change-listener.test.ts +0 -121
- package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-ghost-text.test.ts +0 -112
- package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +0 -134
- package/node_modules/@mariozechner/pi-tui/src/__tests__/settings-list.test.ts +0 -81
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +0 -555
- package/node_modules/@mariozechner/pi-tui/src/border-styles.ts +0 -60
- package/node_modules/@mariozechner/pi-tui/src/components/bordered-box.ts +0 -113
- package/node_modules/@mariozechner/pi-tui/src/test-utils/capability-env.ts +0 -56
- package/packages/tallow-tui/node_modules/marked/lib/marked.cjs +0 -2211
- package/packages/tallow-tui/node_modules/marked/lib/marked.cjs.map +0 -7
- package/packages/tallow-tui/node_modules/marked/lib/marked.d.cts +0 -728
- package/packages/tallow-tui/node_modules/marked/marked.min.js +0 -69
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image component for the TUI.
|
|
3
|
-
* Renders images via Kitty/iTerm2 protocols with optional border framing.
|
|
4
|
-
* Preserves aspect ratio and avoids small-image upscaling.
|
|
5
|
-
*
|
|
6
|
-
* @module
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { type BorderStyle, ROUNDED } from "../border-styles.js";
|
|
10
1
|
import {
|
|
11
2
|
getCapabilities,
|
|
12
3
|
getImageDimensions,
|
|
@@ -15,24 +6,6 @@ import {
|
|
|
15
6
|
renderImage,
|
|
16
7
|
} from "../terminal-image.js";
|
|
17
8
|
import type { Component } from "../tui.js";
|
|
18
|
-
import { hyperlink } from "../utils.js";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Pending file path for the next Image instance.
|
|
22
|
-
* Set by external code (e.g. a tool_result hook) before Image construction.
|
|
23
|
-
* Consumed once by the next Image constructor, then cleared.
|
|
24
|
-
*/
|
|
25
|
-
let pendingFilePath: string | undefined;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Set the file path for the next Image instance to be constructed.
|
|
29
|
-
* Called before Image creation so the component can render a clickable link.
|
|
30
|
-
*
|
|
31
|
-
* @param path - Absolute file path, or undefined to clear
|
|
32
|
-
*/
|
|
33
|
-
export function setNextImageFilePath(path: string | undefined): void {
|
|
34
|
-
pendingFilePath = path;
|
|
35
|
-
}
|
|
36
9
|
|
|
37
10
|
export interface ImageTheme {
|
|
38
11
|
fallbackColor: (str: string) => string;
|
|
@@ -44,54 +17,8 @@ export interface ImageOptions {
|
|
|
44
17
|
filename?: string;
|
|
45
18
|
/** Kitty image ID. If provided, reuses this ID (for animations/updates). */
|
|
46
19
|
imageId?: number;
|
|
47
|
-
/** Show a border around the image. Default: true. */
|
|
48
|
-
border?: boolean;
|
|
49
|
-
/** Border style. Default: ROUNDED. */
|
|
50
|
-
borderStyle?: BorderStyle;
|
|
51
|
-
/** Color function for border characters. */
|
|
52
|
-
borderColorFn?: (str: string) => string;
|
|
53
|
-
/** Absolute file path — enables a clickable OSC 8 file:// link below the image. */
|
|
54
|
-
filePath?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Default fraction of terminal rows available for images when no explicit maxHeightCells is set. */
|
|
58
|
-
const DEFAULT_MAX_HEIGHT_RATIO = 0.9;
|
|
59
|
-
|
|
60
|
-
/** Minimum dynamic max height when terminal row count is available. */
|
|
61
|
-
const MIN_DYNAMIC_MAX_HEIGHT_CELLS = 10;
|
|
62
|
-
|
|
63
|
-
/** Border overhead: │ + space on each side = 4 columns. */
|
|
64
|
-
const BORDER_OVERHEAD = 4;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Calculate a dynamic default max image height from terminal size.
|
|
68
|
-
*
|
|
69
|
-
* Uses a high percentage of the current terminal height to avoid unnecessary
|
|
70
|
-
* downscaling while still preventing images from consuming the full viewport.
|
|
71
|
-
* Returns undefined when terminal row count is unavailable, which disables the
|
|
72
|
-
* height cap unless explicitly provided via options.maxHeightCells.
|
|
73
|
-
*
|
|
74
|
-
* @returns Dynamic max height in terminal rows, or undefined
|
|
75
|
-
*/
|
|
76
|
-
function getDefaultMaxHeightCells(): number | undefined {
|
|
77
|
-
const terminalRows = process.stdout.rows;
|
|
78
|
-
if (!terminalRows || terminalRows <= 0) return undefined;
|
|
79
|
-
return Math.max(
|
|
80
|
-
MIN_DYNAMIC_MAX_HEIGHT_CELLS,
|
|
81
|
-
Math.floor(terminalRows * DEFAULT_MAX_HEIGHT_RATIO)
|
|
82
|
-
);
|
|
83
20
|
}
|
|
84
21
|
|
|
85
|
-
/**
|
|
86
|
-
* TUI component that renders an inline image using terminal graphics protocols.
|
|
87
|
-
* Falls back to a text placeholder when the terminal lacks image support.
|
|
88
|
-
*
|
|
89
|
-
* Features:
|
|
90
|
-
* - Dynamic height cap (~90% of terminal rows) by default (overridable via maxHeightCells)
|
|
91
|
-
* - Small images not upscaled beyond native pixel width
|
|
92
|
-
* - Rounded border frame by default (configurable or disableable)
|
|
93
|
-
* - Kitty warping fix: omits r= param so terminal auto-calculates rows
|
|
94
|
-
*/
|
|
95
22
|
export class Image implements Component {
|
|
96
23
|
private base64Data: string;
|
|
97
24
|
private mimeType: string;
|
|
@@ -117,46 +44,24 @@ export class Image implements Component {
|
|
|
117
44
|
this.dimensions = dimensions ||
|
|
118
45
|
getImageDimensions(base64Data, mimeType) || { widthPx: 800, heightPx: 600 };
|
|
119
46
|
this.imageId = options.imageId;
|
|
120
|
-
|
|
121
|
-
// Auto-consume pending file path if not explicitly provided
|
|
122
|
-
if (!this.options.filePath && pendingFilePath) {
|
|
123
|
-
this.options = { ...this.options, filePath: pendingFilePath };
|
|
124
|
-
pendingFilePath = undefined;
|
|
125
|
-
}
|
|
126
47
|
}
|
|
127
48
|
|
|
128
|
-
/**
|
|
129
|
-
* Get the Kitty image ID used by this image (if any).
|
|
130
|
-
* @returns Image ID or undefined
|
|
131
|
-
*/
|
|
49
|
+
/** Get the Kitty image ID used by this image (if any). */
|
|
132
50
|
getImageId(): number | undefined {
|
|
133
51
|
return this.imageId;
|
|
134
52
|
}
|
|
135
53
|
|
|
136
|
-
/** Clears cached render output so the next render() recomputes. */
|
|
137
54
|
invalidate(): void {
|
|
138
55
|
this.cachedLines = undefined;
|
|
139
56
|
this.cachedWidth = undefined;
|
|
140
57
|
}
|
|
141
58
|
|
|
142
|
-
/**
|
|
143
|
-
* Render the image into terminal lines.
|
|
144
|
-
*
|
|
145
|
-
* @param width - Available terminal width in columns
|
|
146
|
-
* @returns Array of strings (lines) for the TUI to output
|
|
147
|
-
*/
|
|
148
59
|
render(width: number): string[] {
|
|
149
60
|
if (this.cachedLines && this.cachedWidth === width) {
|
|
150
61
|
return this.cachedLines;
|
|
151
62
|
}
|
|
152
63
|
|
|
153
|
-
const
|
|
154
|
-
// Ignore maxWidthCells from upstream — it's hardcoded to 60 in
|
|
155
|
-
// pi-coding-agent and squashes landscape images. Use the full
|
|
156
|
-
// available terminal width instead; height capping and natural-width
|
|
157
|
-
// clamping in calculateImageLayout prevent oversized output.
|
|
158
|
-
const maxWidth = this.getSafeMaxWidthCells(width, showBorder);
|
|
159
|
-
const maxHeight = this.options.maxHeightCells ?? getDefaultMaxHeightCells();
|
|
64
|
+
const maxWidth = Math.min(width - 2, this.options.maxWidthCells ?? 60);
|
|
160
65
|
|
|
161
66
|
const caps = getCapabilities();
|
|
162
67
|
let lines: string[];
|
|
@@ -164,23 +69,32 @@ export class Image implements Component {
|
|
|
164
69
|
if (caps.images) {
|
|
165
70
|
const result = renderImage(this.base64Data, this.dimensions, {
|
|
166
71
|
maxWidthCells: maxWidth,
|
|
167
|
-
maxHeightCells: maxHeight,
|
|
168
72
|
imageId: this.imageId,
|
|
169
73
|
});
|
|
170
74
|
|
|
171
75
|
if (result) {
|
|
76
|
+
// Store the image ID for later cleanup
|
|
172
77
|
if (result.imageId) {
|
|
173
78
|
this.imageId = result.imageId;
|
|
174
79
|
}
|
|
175
80
|
|
|
176
|
-
lines
|
|
177
|
-
|
|
178
|
-
|
|
81
|
+
// Return `rows` lines so TUI accounts for image height
|
|
82
|
+
// First (rows-1) lines are empty (TUI clears them)
|
|
83
|
+
// Last line: move cursor back up, then output image sequence
|
|
84
|
+
lines = [];
|
|
85
|
+
for (let i = 0; i < result.rows - 1; i++) {
|
|
86
|
+
lines.push("");
|
|
87
|
+
}
|
|
88
|
+
// Move cursor up to first row, then output image
|
|
89
|
+
const moveUp = result.rows > 1 ? `\x1b[${result.rows - 1}A` : "";
|
|
90
|
+
lines.push(moveUp + result.sequence);
|
|
179
91
|
} else {
|
|
180
|
-
|
|
92
|
+
const fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);
|
|
93
|
+
lines = [this.theme.fallbackColor(fallback)];
|
|
181
94
|
}
|
|
182
95
|
} else {
|
|
183
|
-
|
|
96
|
+
const fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);
|
|
97
|
+
lines = [this.theme.fallbackColor(fallback)];
|
|
184
98
|
}
|
|
185
99
|
|
|
186
100
|
this.cachedLines = lines;
|
|
@@ -188,128 +102,4 @@ export class Image implements Component {
|
|
|
188
102
|
|
|
189
103
|
return lines;
|
|
190
104
|
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Resolve a safe image width budget from the current pane width.
|
|
194
|
-
*
|
|
195
|
-
* Keeps width math deterministic for ultra-narrow panes by enforcing a
|
|
196
|
-
* minimum of 1 cell after reserved spacing and optional border overhead.
|
|
197
|
-
*
|
|
198
|
-
* @param width - Current pane width in terminal columns
|
|
199
|
-
* @param showBorder - Whether bordered rendering is enabled
|
|
200
|
-
* @returns Positive max width in cells for `renderImage`
|
|
201
|
-
*/
|
|
202
|
-
private getSafeMaxWidthCells(width: number, showBorder: boolean): number {
|
|
203
|
-
const borderCols = showBorder ? BORDER_OVERHEAD : 0;
|
|
204
|
-
const availableWidth = Math.floor(width) - 2 - borderCols;
|
|
205
|
-
return Math.max(1, availableWidth);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Wraps visible text in an OSC 8 file:// hyperlink if filePath is set.
|
|
210
|
-
* Returns the text unchanged when no filePath is configured.
|
|
211
|
-
*
|
|
212
|
-
* @param text - Visible content to wrap (spaces, border chars, etc.)
|
|
213
|
-
* @returns Text optionally wrapped in OSC 8 escape sequences
|
|
214
|
-
*/
|
|
215
|
-
private wrapFileLink(text: string): string {
|
|
216
|
-
if (!this.options.filePath) return text;
|
|
217
|
-
return hyperlink(`file://${encodeURI(this.options.filePath)}`, text);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Builds image output without a border (original behavior).
|
|
222
|
-
* First N-1 lines are filled with OSC 8–wrapped spaces (when filePath
|
|
223
|
-
* is set) so the image area is a clickable link in the text layer.
|
|
224
|
-
* Last line moves cursor up and outputs the image escape sequence.
|
|
225
|
-
*
|
|
226
|
-
* @param sequence - Terminal escape sequence for the image
|
|
227
|
-
* @param rows - Number of terminal rows the image occupies
|
|
228
|
-
* @param columns - Number of terminal columns the image occupies
|
|
229
|
-
* @returns Lines array for the TUI
|
|
230
|
-
*/
|
|
231
|
-
private buildUnborderedImage(sequence: string, rows: number, columns: number): string[] {
|
|
232
|
-
const lines: string[] = [];
|
|
233
|
-
const filler = this.wrapFileLink(" ".repeat(columns));
|
|
234
|
-
for (let i = 0; i < rows - 1; i++) {
|
|
235
|
-
lines.push(filler);
|
|
236
|
-
}
|
|
237
|
-
const moveUp = rows > 1 ? `\x1b[${rows - 1}A` : "";
|
|
238
|
-
lines.push(`${this.wrapFileLink(" ".repeat(columns))}${moveUp}\x1b[${columns}D${sequence}`);
|
|
239
|
-
return lines;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Builds image output wrapped in a border.
|
|
244
|
-
* Border characters occupy the text layer; the image fills the inner
|
|
245
|
-
* cell range via the graphics layer (Kitty/iTerm2).
|
|
246
|
-
*
|
|
247
|
-
* The last content line outputs a full bordered row (text layer), then
|
|
248
|
-
* repositions the cursor back to the first content row and places the
|
|
249
|
-
* image sequence. The graphics layer draws over the inner spaces while
|
|
250
|
-
* border characters at the edges remain visible.
|
|
251
|
-
*
|
|
252
|
-
* @param sequence - Terminal escape sequence for the image
|
|
253
|
-
* @param rows - Number of terminal rows the image occupies
|
|
254
|
-
* @param columns - Number of terminal columns the image occupies
|
|
255
|
-
* @returns Lines array for the TUI
|
|
256
|
-
*/
|
|
257
|
-
private buildBorderedImage(sequence: string, rows: number, columns: number): string[] {
|
|
258
|
-
const style = this.options.borderStyle ?? ROUNDED;
|
|
259
|
-
const colorFn = this.options.borderColorFn ?? ((s: string) => s);
|
|
260
|
-
|
|
261
|
-
const innerWidth = columns;
|
|
262
|
-
const totalWidth = innerWidth + BORDER_OVERHEAD;
|
|
263
|
-
|
|
264
|
-
const top = colorFn(style.topLeft + style.horizontal.repeat(totalWidth - 2) + style.topRight);
|
|
265
|
-
const bottom = colorFn(
|
|
266
|
-
style.bottomLeft + style.horizontal.repeat(totalWidth - 2) + style.bottomRight
|
|
267
|
-
);
|
|
268
|
-
const leftBorder = `${colorFn(style.vertical)} `;
|
|
269
|
-
const rightBorder = ` ${colorFn(style.vertical)}`;
|
|
270
|
-
const emptyInner = this.wrapFileLink(" ".repeat(innerWidth));
|
|
271
|
-
const borderedLine = leftBorder + emptyInner + rightBorder;
|
|
272
|
-
|
|
273
|
-
const lines: string[] = [top];
|
|
274
|
-
|
|
275
|
-
// Bordered empty lines — image fills the inner area via the graphics layer
|
|
276
|
-
for (let i = 0; i < rows - 1; i++) {
|
|
277
|
-
lines.push(borderedLine);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Last content line: full bordered text, then reposition cursor for image placement.
|
|
281
|
-
// CUU (cursor up) + CUB (cursor backward) positions to column 2 of the first content row.
|
|
282
|
-
const moveUp = rows > 1 ? `\x1b[${rows - 1}A` : "";
|
|
283
|
-
const moveToImageStart = `\x1b[${totalWidth - 2}D`;
|
|
284
|
-
lines.push(borderedLine + moveUp + moveToImageStart + sequence);
|
|
285
|
-
|
|
286
|
-
lines.push(bottom);
|
|
287
|
-
|
|
288
|
-
return lines;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Builds fallback text when the terminal doesn't support images.
|
|
293
|
-
* Optionally wrapped in a border for visual consistency.
|
|
294
|
-
*
|
|
295
|
-
* @param showBorder - Whether to wrap the fallback in a border
|
|
296
|
-
* @returns Lines array for the TUI
|
|
297
|
-
*/
|
|
298
|
-
private buildFallback(showBorder: boolean): string[] {
|
|
299
|
-
const fallback = imageFallback(this.mimeType, this.dimensions, this.options.filename);
|
|
300
|
-
const text = this.theme.fallbackColor(fallback);
|
|
301
|
-
|
|
302
|
-
if (showBorder) {
|
|
303
|
-
const style = this.options.borderStyle ?? ROUNDED;
|
|
304
|
-
const colorFn = this.options.borderColorFn ?? ((s: string) => s);
|
|
305
|
-
const innerWidth = fallback.length + 2; // 1 space padding each side
|
|
306
|
-
const top = colorFn(style.topLeft + style.horizontal.repeat(innerWidth) + style.topRight);
|
|
307
|
-
const bottom = colorFn(
|
|
308
|
-
style.bottomLeft + style.horizontal.repeat(innerWidth) + style.bottomRight
|
|
309
|
-
);
|
|
310
|
-
return [top, `${colorFn(style.vertical)} ${text} ${colorFn(style.vertical)}`, bottom];
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return [text];
|
|
314
|
-
}
|
|
315
105
|
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getKeybindings } from "../keybindings.js";
|
|
2
|
+
import { decodeKittyPrintable } from "../keys.js";
|
|
2
3
|
import { KillRing } from "../kill-ring.js";
|
|
3
4
|
import { type Component, CURSOR_MARKER, type Focusable } from "../tui.js";
|
|
4
5
|
import { UndoStack } from "../undo-stack.js";
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getSegmenter,
|
|
8
|
+
isPunctuationChar,
|
|
9
|
+
isWhitespaceChar,
|
|
10
|
+
sliceByColumn,
|
|
11
|
+
visibleWidth,
|
|
12
|
+
} from "../utils.js";
|
|
6
13
|
|
|
7
14
|
const segmenter = getSegmenter();
|
|
8
15
|
|
|
@@ -81,69 +88,69 @@ export class Input implements Component, Focusable {
|
|
|
81
88
|
return;
|
|
82
89
|
}
|
|
83
90
|
|
|
84
|
-
const kb =
|
|
91
|
+
const kb = getKeybindings();
|
|
85
92
|
|
|
86
93
|
// Escape/Cancel
|
|
87
|
-
if (kb.matches(data, "
|
|
94
|
+
if (kb.matches(data, "tui.select.cancel")) {
|
|
88
95
|
if (this.onEscape) this.onEscape();
|
|
89
96
|
return;
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
// Undo
|
|
93
|
-
if (kb.matches(data, "undo")) {
|
|
100
|
+
if (kb.matches(data, "tui.editor.undo")) {
|
|
94
101
|
this.undo();
|
|
95
102
|
return;
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
// Submit
|
|
99
|
-
if (kb.matches(data, "submit") || data === "\n") {
|
|
106
|
+
if (kb.matches(data, "tui.input.submit") || data === "\n") {
|
|
100
107
|
if (this.onSubmit) this.onSubmit(this.value);
|
|
101
108
|
return;
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
// Deletion
|
|
105
|
-
if (kb.matches(data, "deleteCharBackward")) {
|
|
112
|
+
if (kb.matches(data, "tui.editor.deleteCharBackward")) {
|
|
106
113
|
this.handleBackspace();
|
|
107
114
|
return;
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
if (kb.matches(data, "deleteCharForward")) {
|
|
117
|
+
if (kb.matches(data, "tui.editor.deleteCharForward")) {
|
|
111
118
|
this.handleForwardDelete();
|
|
112
119
|
return;
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
if (kb.matches(data, "deleteWordBackward")) {
|
|
122
|
+
if (kb.matches(data, "tui.editor.deleteWordBackward")) {
|
|
116
123
|
this.deleteWordBackwards();
|
|
117
124
|
return;
|
|
118
125
|
}
|
|
119
126
|
|
|
120
|
-
if (kb.matches(data, "deleteWordForward")) {
|
|
127
|
+
if (kb.matches(data, "tui.editor.deleteWordForward")) {
|
|
121
128
|
this.deleteWordForward();
|
|
122
129
|
return;
|
|
123
130
|
}
|
|
124
131
|
|
|
125
|
-
if (kb.matches(data, "deleteToLineStart")) {
|
|
132
|
+
if (kb.matches(data, "tui.editor.deleteToLineStart")) {
|
|
126
133
|
this.deleteToLineStart();
|
|
127
134
|
return;
|
|
128
135
|
}
|
|
129
136
|
|
|
130
|
-
if (kb.matches(data, "deleteToLineEnd")) {
|
|
137
|
+
if (kb.matches(data, "tui.editor.deleteToLineEnd")) {
|
|
131
138
|
this.deleteToLineEnd();
|
|
132
139
|
return;
|
|
133
140
|
}
|
|
134
141
|
|
|
135
142
|
// Kill ring actions
|
|
136
|
-
if (kb.matches(data, "yank")) {
|
|
143
|
+
if (kb.matches(data, "tui.editor.yank")) {
|
|
137
144
|
this.yank();
|
|
138
145
|
return;
|
|
139
146
|
}
|
|
140
|
-
if (kb.matches(data, "yankPop")) {
|
|
147
|
+
if (kb.matches(data, "tui.editor.yankPop")) {
|
|
141
148
|
this.yankPop();
|
|
142
149
|
return;
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
// Cursor movement
|
|
146
|
-
if (kb.matches(data, "cursorLeft")) {
|
|
153
|
+
if (kb.matches(data, "tui.editor.cursorLeft")) {
|
|
147
154
|
this.lastAction = null;
|
|
148
155
|
if (this.cursor > 0) {
|
|
149
156
|
const beforeCursor = this.value.slice(0, this.cursor);
|
|
@@ -154,7 +161,7 @@ export class Input implements Component, Focusable {
|
|
|
154
161
|
return;
|
|
155
162
|
}
|
|
156
163
|
|
|
157
|
-
if (kb.matches(data, "cursorRight")) {
|
|
164
|
+
if (kb.matches(data, "tui.editor.cursorRight")) {
|
|
158
165
|
this.lastAction = null;
|
|
159
166
|
if (this.cursor < this.value.length) {
|
|
160
167
|
const afterCursor = this.value.slice(this.cursor);
|
|
@@ -165,28 +172,38 @@ export class Input implements Component, Focusable {
|
|
|
165
172
|
return;
|
|
166
173
|
}
|
|
167
174
|
|
|
168
|
-
if (kb.matches(data, "cursorLineStart")) {
|
|
175
|
+
if (kb.matches(data, "tui.editor.cursorLineStart")) {
|
|
169
176
|
this.lastAction = null;
|
|
170
177
|
this.cursor = 0;
|
|
171
178
|
return;
|
|
172
179
|
}
|
|
173
180
|
|
|
174
|
-
if (kb.matches(data, "cursorLineEnd")) {
|
|
181
|
+
if (kb.matches(data, "tui.editor.cursorLineEnd")) {
|
|
175
182
|
this.lastAction = null;
|
|
176
183
|
this.cursor = this.value.length;
|
|
177
184
|
return;
|
|
178
185
|
}
|
|
179
186
|
|
|
180
|
-
if (kb.matches(data, "cursorWordLeft")) {
|
|
187
|
+
if (kb.matches(data, "tui.editor.cursorWordLeft")) {
|
|
181
188
|
this.moveWordBackwards();
|
|
182
189
|
return;
|
|
183
190
|
}
|
|
184
191
|
|
|
185
|
-
if (kb.matches(data, "cursorWordRight")) {
|
|
192
|
+
if (kb.matches(data, "tui.editor.cursorWordRight")) {
|
|
186
193
|
this.moveWordForwards();
|
|
187
194
|
return;
|
|
188
195
|
}
|
|
189
196
|
|
|
197
|
+
// Kitty CSI-u printable character (e.g. \x1b[97u for 'a').
|
|
198
|
+
// Terminals with Kitty protocol flag 1 (disambiguate) send CSI-u for all keys,
|
|
199
|
+
// including plain printable characters. Decode before the control-char check
|
|
200
|
+
// since CSI-u sequences contain \x1b which would be rejected.
|
|
201
|
+
const kittyPrintable = decodeKittyPrintable(data);
|
|
202
|
+
if (kittyPrintable !== undefined) {
|
|
203
|
+
this.insertCharacter(kittyPrintable);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
190
207
|
// Regular character input - accept printable characters including Unicode,
|
|
191
208
|
// but reject control characters (C0: 0x00-0x1F, DEL: 0x7F, C1: 0x80-0x9F)
|
|
192
209
|
const hasControlChars = [...data].some((ch) => {
|
|
@@ -421,7 +438,11 @@ export class Input implements Component, Focusable {
|
|
|
421
438
|
this.pushUndo();
|
|
422
439
|
|
|
423
440
|
// Clean the pasted text - remove newlines and carriage returns
|
|
424
|
-
const cleanText = pastedText
|
|
441
|
+
const cleanText = pastedText
|
|
442
|
+
.replace(/\r\n/g, "")
|
|
443
|
+
.replace(/\r/g, "")
|
|
444
|
+
.replace(/\n/g, "")
|
|
445
|
+
.replace(/\t/g, " ");
|
|
425
446
|
|
|
426
447
|
// Insert at cursor position
|
|
427
448
|
this.value = this.value.slice(0, this.cursor) + cleanText + this.value.slice(this.cursor);
|
|
@@ -443,56 +464,43 @@ export class Input implements Component, Focusable {
|
|
|
443
464
|
|
|
444
465
|
let visibleText = "";
|
|
445
466
|
let cursorDisplay = this.cursor;
|
|
467
|
+
const totalWidth = visibleWidth(this.value);
|
|
446
468
|
|
|
447
|
-
if (
|
|
469
|
+
if (totalWidth < availableWidth) {
|
|
448
470
|
// Everything fits (leave room for cursor at end)
|
|
449
471
|
visibleText = this.value;
|
|
450
472
|
} else {
|
|
451
473
|
// Need horizontal scrolling
|
|
452
|
-
// Reserve one
|
|
474
|
+
// Reserve one column for cursor if it's at the end
|
|
453
475
|
const scrollWidth = this.cursor === this.value.length ? availableWidth - 1 : availableWidth;
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
476
|
+
const cursorCol = visibleWidth(this.value.slice(0, this.cursor));
|
|
477
|
+
|
|
478
|
+
if (scrollWidth > 0) {
|
|
479
|
+
const halfWidth = Math.floor(scrollWidth / 2);
|
|
480
|
+
let startCol = 0;
|
|
481
|
+
|
|
482
|
+
if (cursorCol < halfWidth) {
|
|
483
|
+
// Cursor near start
|
|
484
|
+
startCol = 0;
|
|
485
|
+
} else if (cursorCol > totalWidth - halfWidth) {
|
|
486
|
+
// Cursor near end
|
|
487
|
+
startCol = Math.max(0, totalWidth - scrollWidth);
|
|
488
|
+
} else {
|
|
489
|
+
// Cursor in middle
|
|
490
|
+
startCol = Math.max(0, cursorCol - halfWidth);
|
|
465
491
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
break;
|
|
478
|
-
}
|
|
479
|
-
return end;
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
if (this.cursor < halfWidth) {
|
|
483
|
-
// Cursor near start
|
|
484
|
-
visibleText = this.value.slice(0, findValidEnd(scrollWidth));
|
|
485
|
-
cursorDisplay = this.cursor;
|
|
486
|
-
} else if (this.cursor > this.value.length - halfWidth) {
|
|
487
|
-
// Cursor near end
|
|
488
|
-
const start = findValidStart(this.value.length - scrollWidth);
|
|
489
|
-
visibleText = this.value.slice(start);
|
|
490
|
-
cursorDisplay = this.cursor - start;
|
|
492
|
+
|
|
493
|
+
visibleText = sliceByColumn(this.value, startCol, scrollWidth, true);
|
|
494
|
+
const beforeCursor = sliceByColumn(
|
|
495
|
+
this.value,
|
|
496
|
+
startCol,
|
|
497
|
+
Math.max(0, cursorCol - startCol),
|
|
498
|
+
true
|
|
499
|
+
);
|
|
500
|
+
cursorDisplay = beforeCursor.length;
|
|
491
501
|
} else {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
visibleText = this.value.slice(start, findValidEnd(start + scrollWidth));
|
|
495
|
-
cursorDisplay = halfWidth;
|
|
502
|
+
visibleText = "";
|
|
503
|
+
cursorDisplay = 0;
|
|
496
504
|
}
|
|
497
505
|
}
|
|
498
506
|
|