@code-yeongyu/senpi-tui 2026.8.12 → 2026.8.13
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/autocomplete.d.ts.map +1 -1
- package/dist/autocomplete.js.map +1 -1
- package/dist/components/alt-screen-flash.d.ts.map +1 -1
- package/dist/components/box.d.ts.map +1 -1
- package/dist/components/cancellable-loader.d.ts.map +1 -1
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +11 -0
- package/dist/components/editor.js.map +1 -1
- package/dist/components/h-stack.d.ts.map +1 -1
- package/dist/components/h-stack.js.map +1 -1
- package/dist/components/image.d.ts.map +1 -1
- package/dist/components/image.js.map +1 -1
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/latex.d.ts.map +1 -1
- package/dist/components/loader.d.ts.map +1 -1
- package/dist/components/loader.js.map +1 -1
- package/dist/components/markdown.d.ts +2 -0
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +5 -3
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/scroll-view.d.ts.map +1 -1
- package/dist/components/scroll-view.js.map +1 -1
- package/dist/components/select-list.d.ts.map +1 -1
- package/dist/components/select-list.js.map +1 -1
- package/dist/components/settings-list.d.ts.map +1 -1
- package/dist/components/settings-list.js.map +1 -1
- package/dist/components/spacer.d.ts.map +1 -1
- package/dist/components/spacer.js.map +1 -1
- package/dist/components/stack.d.ts.map +1 -1
- package/dist/components/stack.js.map +1 -1
- package/dist/components/text.d.ts.map +1 -1
- package/dist/components/text.js.map +1 -1
- package/dist/components/truncated-text.d.ts.map +1 -1
- package/dist/components/truncated-text.js.map +1 -1
- package/dist/components/v-stack.d.ts.map +1 -1
- package/dist/components/v-stack.js +7 -2
- package/dist/components/v-stack.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/keybindings.d.ts +24 -4
- package/dist/keybindings.d.ts.map +1 -1
- package/dist/keybindings.js +21 -4
- package/dist/keybindings.js.map +1 -1
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js.map +1 -1
- package/dist/kill-ring.d.ts.map +1 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +6 -7
- package/dist/layout.js.map +1 -1
- package/dist/native-modifiers.d.ts.map +1 -1
- package/dist/native-modifiers.js +10 -3
- package/dist/native-modifiers.js.map +1 -1
- package/dist/paste-markers.d.ts.map +1 -1
- package/dist/stdin-buffer.d.ts.map +1 -1
- package/dist/stdin-buffer.js.map +1 -1
- package/dist/terminal-capabilities.d.ts +1 -1
- package/dist/terminal-capabilities.d.ts.map +1 -1
- package/dist/terminal-capabilities.js +7 -1
- package/dist/terminal-capabilities.js.map +1 -1
- package/dist/terminal-colors.js +1 -1
- package/dist/terminal-colors.js.map +1 -1
- package/dist/terminal-image.d.ts +12 -0
- package/dist/terminal-image.d.ts.map +1 -1
- package/dist/terminal-image.js +80 -3
- package/dist/terminal-image.js.map +1 -1
- package/dist/terminal.d.ts +1 -0
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +10 -7
- package/dist/terminal.js.map +1 -1
- package/dist/tmux-image-probe.js.map +1 -1
- package/dist/{TuiAltScreen.d.ts → tui-alt-screen.d.ts} +19 -5
- package/dist/tui-alt-screen.d.ts.map +1 -0
- package/dist/{TuiAltScreen.js → tui-alt-screen.js} +234 -36
- package/dist/tui-alt-screen.js.map +1 -0
- package/dist/tui-main-screen.d.ts +17 -0
- package/dist/tui-main-screen.d.ts.map +1 -0
- package/dist/tui-main-screen.js +31 -0
- package/dist/tui-main-screen.js.map +1 -0
- package/dist/tui.d.ts +31 -12
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +55 -26
- package/dist/tui.js.map +1 -1
- package/dist/undo-stack.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -2
- package/dist/utils.js.map +1 -1
- package/native/darwin/README.md +20 -0
- package/native/darwin/build.sh +62 -0
- package/native/darwin/src/darwin-modifiers.c +70 -0
- package/native/win32/README.md +22 -0
- package/native/win32/build.mjs +229 -0
- package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/native/win32/src/win32-console-mode.c +119 -0
- package/package.json +9 -1
- package/dist/TuiAltScreen.d.ts.map +0 -1
- package/dist/TuiAltScreen.js.map +0 -1
- package/dist/TuiMainScreen.d.ts +0 -5
- package/dist/TuiMainScreen.d.ts.map +0 -1
- package/dist/TuiMainScreen.js +0 -5
- package/dist/TuiMainScreen.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tmux-image-probe.js","sourceRoot":"","sources":["../src/tmux-image-probe.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFileSync } from \"node:child_process\";\n\ninterface CellDimensions {\n\twidthPx: number;\n\theightPx: number;\n}\n\nexport type TmuxSupportTier = \"unsupported\" | \"on-only\" | \"on-and-all\";\nexport type TmuxAllowPassthrough = \"off\" | \"on\" | \"all\";\n\nexport type TmuxExecFile = (file: string, args: readonly string[]) => string;\n\ninterface TmuxBaseState {\n\treadonly supportTier: TmuxSupportTier;\n\treadonly allowPassthrough: TmuxAllowPassthrough;\n\treadonly focusEvents: boolean;\n\treadonly paneActive: boolean;\n\treadonly windowActive: boolean;\n\treadonly visible: boolean;\n\treadonly clientCount: number;\n\treadonly clientTermname: string;\n\treadonly nested: boolean;\n\treadonly hyperlinks: boolean;\n\treadonly cellDimensions?: CellDimensions;\n}\n\nexport interface TmuxOutsideState extends TmuxBaseState {\n\treadonly kind: \"outside\";\n}\n\nexport interface TmuxUnavailableState extends TmuxBaseState {\n\treadonly kind: \"unavailable\";\n\treadonly reason: \"probe-failed\" | \"malformed-output\";\n}\n\nexport interface TmuxDetectedState extends TmuxBaseState {\n\treadonly kind: \"tmux\";\n\treadonly version: string;\n}\n\nexport type TmuxImageState = TmuxOutsideState | TmuxUnavailableState | TmuxDetectedState;\n\nexport const TMUX_IMAGE_FORMAT =\n\t\"#{version}|#{allow-passthrough}|#{focus-events}|#{pane_active}|#{window_active}|#{session_attached}|#{client_termname}|#{client_cell_width}|#{client_cell_height}|#{client_termfeatures}\";\n\nconst DISABLED_STATE = {\n\tsupportTier: \"unsupported\",\n\tallowPassthrough: \"off\",\n\tfocusEvents: false,\n\tpaneActive: false,\n\twindowActive: false,\n\tvisible: false,\n\tclientCount: 0,\n\tclientTermname: \"\",\n\tnested: false,\n\thyperlinks: false,\n} as const;\n\nfunction defaultExecFile(file: string, args: readonly string[]): string {\n\treturn execFileSync(file, [...args], {\n\t\tencoding: \"utf8\",\n\t\ttimeout: 250,\n\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t});\n}\n\nfunction parseTmuxVersion(version: string): readonly [major: number, minor: number] | null {\n\tconst match = /^(?:next-)?(\\d+)\\.(\\d+)/.exec(version.trim());\n\tif (!match) return null;\n\tconst major = Number.parseInt(match[1] ?? \"\", 10);\n\tconst minor = Number.parseInt(match[2] ?? \"\", 10);\n\treturn Number.isFinite(major) && Number.isFinite(minor) ? [major, minor] : null;\n}\n\nexport function tmuxSupportTier(version: string): TmuxSupportTier {\n\tconst parsed = parseTmuxVersion(version);\n\tif (!parsed) return \"unsupported\";\n\tconst [major, minor] = parsed;\n\tif (major > 3 || (major === 3 && minor >= 4)) return \"on-and-all\";\n\tif (major === 3 && minor === 3) return \"on-only\";\n\treturn \"unsupported\";\n}\n\nexport function normalizeTmuxAllowPassthrough(raw: string, supportTier: TmuxSupportTier): TmuxAllowPassthrough {\n\tif (supportTier === \"unsupported\") return \"off\";\n\tconst value = raw.trim().toLowerCase();\n\tif (value === \"1\" || value === \"on\") return \"on\";\n\tif (value === \"all\" && supportTier === \"on-and-all\") return \"all\";\n\treturn \"off\";\n}\n\nfunction parseBoolean(raw: string): boolean {\n\tconst value = raw.trim().toLowerCase();\n\treturn value === \"1\" || value === \"on\";\n}\n\nfunction parsePositiveInteger(raw: string): number | undefined {\n\tif (!/^\\d+$/.test(raw)) return undefined;\n\tconst value = Number.parseInt(raw, 10);\n\treturn value > 0 ? value : undefined;\n}\n\nfunction parseClientCount(raw: string): number {\n\tif (!/^\\d+$/.test(raw)) return 0;\n\treturn Number.parseInt(raw, 10);\n}\n\nfunction isNestedClient(clientTermname: string): boolean {\n\tconst term = clientTermname.trim().toLowerCase();\n\treturn term.startsWith(\"tmux\") || term.startsWith(\"screen\");\n}\n\nexport function parseTmuxImageState(output: string): TmuxImageState {\n\tconst fields = output.trim().split(\"|\");\n\tif (fields.length !== 10) {\n\t\treturn { kind: \"unavailable\", reason: \"malformed-output\", ...DISABLED_STATE };\n\t}\n\n\tconst [\n\t\tversion = \"\",\n\t\trawAllow = \"\",\n\t\trawFocusEvents = \"\",\n\t\trawPaneActive = \"\",\n\t\trawWindowActive = \"\",\n\t\trawClientCount = \"\",\n\t\trawClientTermname = \"\",\n\t\trawCellWidth = \"\",\n\t\trawCellHeight = \"\",\n\t\trawTermfeatures = \"\",\n\t] = fields;\n\tconst supportTier = tmuxSupportTier(version);\n\tconst clientCount = parseClientCount(rawClientCount);\n\tconst paneActive = parseBoolean(rawPaneActive);\n\tconst windowActive = parseBoolean(rawWindowActive);\n\tconst clientTermname = rawClientTermname.trim();\n\tconst widthPx = parsePositiveInteger(rawCellWidth);\n\tconst heightPx = parsePositiveInteger(rawCellHeight);\n\tconst cellDimensions = widthPx && heightPx ? { widthPx, heightPx } : undefined;\n\n\treturn {\n\t\tkind: \"tmux\",\n\t\tversion: version.trim(),\n\t\tsupportTier,\n\t\tallowPassthrough: normalizeTmuxAllowPassthrough(rawAllow, supportTier),\n\t\tfocusEvents: parseBoolean(rawFocusEvents),\n\t\tpaneActive,\n\t\twindowActive,\n\t\tvisible: paneActive && windowActive && clientCount === 1,\n\t\tclientCount,\n\t\tclientTermname,\n\t\tnested: isNestedClient(clientTermname),\n\t\thyperlinks: rawTermfeatures\n\t\t\t.split(\",\")\n\t\t\t.map((feature) => feature.trim())\n\t\t\t.includes(\"hyperlinks\"),\n\t\t...(cellDimensions ? { cellDimensions } : {}),\n\t};\n}\n\nexport function probeTmuxImageState(\n\tenv: Readonly<Record<string, string | undefined>> = process.env,\n\texecFile: TmuxExecFile = defaultExecFile,\n): TmuxImageState {\n\tconst term = env.TERM?.toLowerCase() ?? \"\";\n\tif (!env.TMUX && !term.startsWith(\"tmux\")) {\n\t\treturn { kind: \"outside\", ...DISABLED_STATE };\n\t}\n\n\tconst pane = env.TMUX_PANE;\n\tconst args = [\"display-message\", \"-p\"];\n\tif (pane && /^%\\d+$/.test(pane)) {\n\t\targs.push(\"-t\", pane);\n\t}\n\targs.push(TMUX_IMAGE_FORMAT);\n\n\ttry {\n\t\treturn parseTmuxImageState(execFile(\"tmux\", args));\n\t} catch {\n\t\treturn { kind: \"unavailable\", reason: \"probe-failed\", ...DISABLED_STATE };\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tmux-image-probe.js","sourceRoot":"","sources":["../src/tmux-image-probe.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFileSync } from \"node:child_process\";\n\ninterface CellDimensions {\n\twidthPx: number;\n\theightPx: number;\n}\n\nexport type TmuxSupportTier = \"unsupported\" | \"on-only\" | \"on-and-all\";\nexport type TmuxAllowPassthrough = \"off\" | \"on\" | \"all\";\n\nexport type TmuxExecFile = (file: string, args: readonly string[]) => string;\n\ninterface TmuxBaseState {\n\treadonly supportTier: TmuxSupportTier;\n\treadonly allowPassthrough: TmuxAllowPassthrough;\n\treadonly focusEvents: boolean;\n\treadonly paneActive: boolean;\n\treadonly windowActive: boolean;\n\treadonly visible: boolean;\n\treadonly clientCount: number;\n\treadonly clientTermname: string;\n\treadonly nested: boolean;\n\treadonly hyperlinks: boolean;\n\treadonly cellDimensions?: CellDimensions;\n}\n\nexport interface TmuxOutsideState extends TmuxBaseState {\n\treadonly kind: \"outside\";\n}\n\nexport interface TmuxUnavailableState extends TmuxBaseState {\n\treadonly kind: \"unavailable\";\n\treadonly reason: \"probe-failed\" | \"malformed-output\";\n}\n\nexport interface TmuxDetectedState extends TmuxBaseState {\n\treadonly kind: \"tmux\";\n\treadonly version: string;\n}\n\nexport type TmuxImageState = TmuxOutsideState | TmuxUnavailableState | TmuxDetectedState;\n\nexport const TMUX_IMAGE_FORMAT =\n\t\"#{version}|#{allow-passthrough}|#{focus-events}|#{pane_active}|#{window_active}|#{session_attached}|#{client_termname}|#{client_cell_width}|#{client_cell_height}|#{client_termfeatures}\";\n\nconst DISABLED_STATE = {\n\tsupportTier: \"unsupported\",\n\tallowPassthrough: \"off\",\n\tfocusEvents: false,\n\tpaneActive: false,\n\twindowActive: false,\n\tvisible: false,\n\tclientCount: 0,\n\tclientTermname: \"\",\n\tnested: false,\n\thyperlinks: false,\n} as const;\n\nfunction defaultExecFile(file: string, args: readonly string[]): string {\n\treturn execFileSync(file, [...args], {\n\t\tencoding: \"utf8\",\n\t\ttimeout: 250,\n\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t});\n}\n\nfunction parseTmuxVersion(version: string): readonly [major: number, minor: number] | null {\n\tconst match = /^(?:next-)?(\\d+)\\.(\\d+)/.exec(version.trim());\n\tif (!match) return null;\n\tconst major = Number.parseInt(match[1] ?? \"\", 10);\n\tconst minor = Number.parseInt(match[2] ?? \"\", 10);\n\treturn Number.isFinite(major) && Number.isFinite(minor) ? [major, minor] : null;\n}\n\nexport function tmuxSupportTier(version: string): TmuxSupportTier {\n\tconst parsed = parseTmuxVersion(version);\n\tif (!parsed) return \"unsupported\";\n\tconst [major, minor] = parsed;\n\tif (major > 3 || (major === 3 && minor >= 4)) return \"on-and-all\";\n\tif (major === 3 && minor === 3) return \"on-only\";\n\treturn \"unsupported\";\n}\n\nexport function normalizeTmuxAllowPassthrough(raw: string, supportTier: TmuxSupportTier): TmuxAllowPassthrough {\n\tif (supportTier === \"unsupported\") return \"off\";\n\tconst value = raw.trim().toLowerCase();\n\tif (value === \"1\" || value === \"on\") return \"on\";\n\tif (value === \"all\" && supportTier === \"on-and-all\") return \"all\";\n\treturn \"off\";\n}\n\nfunction parseBoolean(raw: string): boolean {\n\tconst value = raw.trim().toLowerCase();\n\treturn value === \"1\" || value === \"on\";\n}\n\nfunction parsePositiveInteger(raw: string): number | undefined {\n\tif (!/^\\d+$/.test(raw)) return undefined;\n\tconst value = Number.parseInt(raw, 10);\n\treturn value > 0 ? value : undefined;\n}\n\nfunction parseClientCount(raw: string): number {\n\tif (!/^\\d+$/.test(raw)) return 0;\n\treturn Number.parseInt(raw, 10);\n}\n\nfunction isNestedClient(clientTermname: string): boolean {\n\tconst term = clientTermname.trim().toLowerCase();\n\treturn term.startsWith(\"tmux\") || term.startsWith(\"screen\");\n}\n\nexport function parseTmuxImageState(output: string): TmuxImageState {\n\tconst fields = output.trim().split(\"|\");\n\tif (fields.length !== 10) {\n\t\treturn { kind: \"unavailable\", reason: \"malformed-output\", ...DISABLED_STATE };\n\t}\n\n\tconst [\n\t\tversion = \"\",\n\t\trawAllow = \"\",\n\t\trawFocusEvents = \"\",\n\t\trawPaneActive = \"\",\n\t\trawWindowActive = \"\",\n\t\trawClientCount = \"\",\n\t\trawClientTermname = \"\",\n\t\trawCellWidth = \"\",\n\t\trawCellHeight = \"\",\n\t\trawTermfeatures = \"\",\n\t] = fields;\n\tconst supportTier = tmuxSupportTier(version);\n\tconst clientCount = parseClientCount(rawClientCount);\n\tconst paneActive = parseBoolean(rawPaneActive);\n\tconst windowActive = parseBoolean(rawWindowActive);\n\tconst clientTermname = rawClientTermname.trim();\n\tconst widthPx = parsePositiveInteger(rawCellWidth);\n\tconst heightPx = parsePositiveInteger(rawCellHeight);\n\tconst cellDimensions = widthPx && heightPx ? { widthPx, heightPx } : undefined;\n\n\treturn {\n\t\tkind: \"tmux\",\n\t\tversion: version.trim(),\n\t\tsupportTier,\n\t\tallowPassthrough: normalizeTmuxAllowPassthrough(rawAllow, supportTier),\n\t\tfocusEvents: parseBoolean(rawFocusEvents),\n\t\tpaneActive,\n\t\twindowActive,\n\t\tvisible: paneActive && windowActive && clientCount === 1,\n\t\tclientCount,\n\t\tclientTermname,\n\t\tnested: isNestedClient(clientTermname),\n\t\thyperlinks: rawTermfeatures\n\t\t\t.split(\",\")\n\t\t\t.map((feature) => feature.trim())\n\t\t\t.includes(\"hyperlinks\"),\n\t\t...(cellDimensions ? { cellDimensions } : {}),\n\t};\n}\n\nexport function probeTmuxImageState(\n\tenv: Readonly<Record<string, string | undefined>> = process.env,\n\texecFile: TmuxExecFile = defaultExecFile,\n): TmuxImageState {\n\tconst term = env.TERM?.toLowerCase() ?? \"\";\n\tif (!env.TMUX && !term.startsWith(\"tmux\")) {\n\t\treturn { kind: \"outside\", ...DISABLED_STATE };\n\t}\n\n\tconst pane = env.TMUX_PANE;\n\tconst args = [\"display-message\", \"-p\"];\n\tif (pane && /^%\\d+$/.test(pane)) {\n\t\targs.push(\"-t\", pane);\n\t}\n\targs.push(TMUX_IMAGE_FORMAT);\n\n\ttry {\n\t\treturn parseTmuxImageState(execFile(\"tmux\", args));\n\t} catch {\n\t\treturn { kind: \"unavailable\", reason: \"probe-failed\", ...DISABLED_STATE };\n\t}\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Terminal } from "./terminal.ts";
|
|
2
|
-
import { type Component, TuiBase, VIEWPORT_TUI, type ViewportTUI } from "./tui.ts";
|
|
2
|
+
import { type Component, TuiBase, type TuiStopOptions, VIEWPORT_TUI, type ViewportTUI } from "./tui.ts";
|
|
3
3
|
export interface TuiAltScreenOptions {
|
|
4
4
|
/** Number of logical lines moved for each mouse-wheel event. */
|
|
5
5
|
wheelScrollLines?: number;
|
|
@@ -7,9 +7,12 @@ export interface TuiAltScreenOptions {
|
|
|
7
7
|
mouse?: boolean;
|
|
8
8
|
/** Open an OSC 8 hyperlink activated with a primary-button click. */
|
|
9
9
|
openUrl?: (url: string) => void;
|
|
10
|
+
/** Handle an unmodified secondary-button press for clipboard paste. Currently enabled on Windows only. */
|
|
11
|
+
onRightClickPaste?: () => void;
|
|
10
12
|
}
|
|
11
13
|
/** Alternate-screen TUI with a scrollable, application-owned viewport. */
|
|
12
14
|
export declare class TuiAltScreen extends TuiBase implements ViewportTUI {
|
|
15
|
+
readonly mode: "fullscreen";
|
|
13
16
|
readonly [VIEWPORT_TUI]: true;
|
|
14
17
|
private previousScreen;
|
|
15
18
|
private lastDocument;
|
|
@@ -23,8 +26,12 @@ export declare class TuiAltScreen extends TuiBase implements ViewportTUI {
|
|
|
23
26
|
private altScreenActive;
|
|
24
27
|
private imageProtocol;
|
|
25
28
|
private savedCapabilities?;
|
|
29
|
+
private readonly uploadedKittyImages;
|
|
26
30
|
private selectionAnchor?;
|
|
27
31
|
private selectionFocus?;
|
|
32
|
+
private selectionGranularity;
|
|
33
|
+
private selectionInitialRange?;
|
|
34
|
+
private lastClick?;
|
|
28
35
|
private selectionDragPointer?;
|
|
29
36
|
private selectionAutoScrollDirection;
|
|
30
37
|
private selectionAutoScrollTimer?;
|
|
@@ -36,18 +43,19 @@ export declare class TuiAltScreen extends TuiBase implements ViewportTUI {
|
|
|
36
43
|
private readonly wheelScrollLines;
|
|
37
44
|
private readonly mouseEnabled;
|
|
38
45
|
private readonly openUrl?;
|
|
46
|
+
private readonly onRightClickPaste?;
|
|
39
47
|
constructor(terminal: Terminal, showHardwareCursor?: boolean, logDirectory?: string, options?: TuiAltScreenOptions);
|
|
40
48
|
get viewportTop(): number;
|
|
41
49
|
get isFollowingOutput(): boolean;
|
|
42
50
|
setLayoutRoot(component: Component | undefined): void;
|
|
43
51
|
render(width: number): string[];
|
|
44
|
-
invalidate(): void;
|
|
45
52
|
protected getMountedRoots(): readonly Component[];
|
|
46
53
|
private getPrimaryScrollView;
|
|
47
54
|
protected beforeTerminalStart(): void;
|
|
48
|
-
protected beforeTerminalStop(): void;
|
|
49
|
-
protected afterTerminalStop(): void;
|
|
55
|
+
protected beforeTerminalStop(_options: TuiStopOptions): void;
|
|
56
|
+
protected afterTerminalStop(options: TuiStopOptions): void;
|
|
50
57
|
private deleteAltScreenKittyImages;
|
|
58
|
+
private prepareKittyScreen;
|
|
51
59
|
protected resetRenderState(): void;
|
|
52
60
|
scrollBy(lines: number): void;
|
|
53
61
|
scrollToTop(): void;
|
|
@@ -59,6 +67,7 @@ export declare class TuiAltScreen extends TuiBase implements ViewportTUI {
|
|
|
59
67
|
private parseWheelEvent;
|
|
60
68
|
private routeWheel;
|
|
61
69
|
private parseSgrMouseEvent;
|
|
70
|
+
private handleRightClickPaste;
|
|
62
71
|
private getScrollbarTargetAt;
|
|
63
72
|
private setScrollbarHover;
|
|
64
73
|
private updateScrollbarHover;
|
|
@@ -67,6 +76,11 @@ export declare class TuiAltScreen extends TuiBase implements ViewportTUI {
|
|
|
67
76
|
private stopScrollbarDrag;
|
|
68
77
|
private getScrollSelectionPoint;
|
|
69
78
|
private getSelectionPoint;
|
|
79
|
+
private getSelectionSourceLine;
|
|
80
|
+
private getWordSelection;
|
|
81
|
+
private getLineSelection;
|
|
82
|
+
private updateSelectionFocus;
|
|
83
|
+
private getClickCount;
|
|
70
84
|
private updateSelectionAutoScroll;
|
|
71
85
|
private autoScrollSelection;
|
|
72
86
|
private stopSelectionAutoScroll;
|
|
@@ -80,4 +94,4 @@ export declare class TuiAltScreen extends TuiBase implements ViewportTUI {
|
|
|
80
94
|
private compositeFlashes;
|
|
81
95
|
protected doRender(): void;
|
|
82
96
|
}
|
|
83
|
-
//# sourceMappingURL=
|
|
97
|
+
//# sourceMappingURL=tui-alt-screen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-alt-screen.d.ts","sourceRoot":"","sources":["../src/tui-alt-screen.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,14 +4,15 @@ import { ScrollView } from "./components/scroll-view.js";
|
|
|
4
4
|
import { getKeybindings } from "./keybindings.js";
|
|
5
5
|
import { isKeyRelease } from "./keys.js";
|
|
6
6
|
import { getScrollbarGeometry, getScrollViewBox, getScrollViewsAt, renderLayoutFrame, } from "./layout.js";
|
|
7
|
-
import { deleteAllKittyImages, getCapabilities, isImageLine, setCapabilities, } from "./terminal-image.js";
|
|
8
|
-
import { CURSOR_MARKER, compositeTuiLine, TuiBase, VIEWPORT_TUI } from "./tui.js";
|
|
9
|
-
import { extractAnsiCode, getGraphemeCellRange, getOsc8LinkAtColumn, sliceByColumn, stripTerminalSequences, visibleWidth, } from "./utils.js";
|
|
7
|
+
import { deleteAllKittyImages, deleteAllKittyPlacements, deleteKittyImage, getCapabilities, getKittyImagePlacement, isImageLine, setCapabilities, } from "./terminal-image.js";
|
|
8
|
+
import { CURSOR_MARKER, compositeTuiLine, TuiBase, VIEWPORT_TUI, } from "./tui.js";
|
|
9
|
+
import { extractAnsiCode, getGraphemeCellRange, getOsc8LinkAtColumn, getWordSegmenter, sliceByColumn, stripTerminalSequences, visibleWidth, } from "./utils.js";
|
|
10
10
|
const ENTER_ALT_SCREEN = "\x1b[?1049h";
|
|
11
11
|
const EXIT_ALT_SCREEN = "\x1b[?1049l";
|
|
12
12
|
const DISABLE_AUTOWRAP = "\x1b[?7l";
|
|
13
13
|
const ENABLE_AUTOWRAP = "\x1b[?7h";
|
|
14
|
-
const
|
|
14
|
+
const ENABLE_BUTTON_MOTION_MOUSE = "\x1b[?1000h\x1b[?1002h\x1b[?1004h\x1b[?1006h";
|
|
15
|
+
const ENABLE_ALL_MOTION_MOUSE = "\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1004h\x1b[?1006h";
|
|
15
16
|
const DISABLE_MOUSE = "\x1b[?1006l\x1b[?1004l\x1b[?1003l\x1b[?1002l\x1b[?1000l";
|
|
16
17
|
const FOCUS_IN = "\x1b[I";
|
|
17
18
|
const FOCUS_OUT = "\x1b[O";
|
|
@@ -20,11 +21,17 @@ const END_SYNCHRONIZED_OUTPUT = "\x1b[?2026l";
|
|
|
20
21
|
const OSC133_ZONE_PREFIX = /^(?:\x1b\]133;[ABC](?:\x07|\x1b\\))+/;
|
|
21
22
|
const OSC133_PROMPT_START = /^\x1b\]133;A(?:\x07|\x1b\\)/;
|
|
22
23
|
const PAGE_SCROLL_OVERLAP = 4;
|
|
24
|
+
const MAX_CACHED_OFFSCREEN_KITTY_IMAGES = 16;
|
|
25
|
+
const MAX_CACHED_OFFSCREEN_KITTY_TRANSMISSION_BYTES = 32 * 1024 * 1024;
|
|
26
|
+
const MAX_CACHED_OFFSCREEN_KITTY_DECODED_BYTES = 64 * 1024 * 1024;
|
|
27
|
+
const DOUBLE_CLICK_INTERVAL_MS = 500;
|
|
28
|
+
const wordSegmenter = getWordSegmenter();
|
|
23
29
|
/** Alternate-screen TUI with a scrollable, application-owned viewport. */
|
|
24
30
|
export class TuiAltScreen extends TuiBase {
|
|
25
31
|
static { _a = VIEWPORT_TUI; }
|
|
26
32
|
constructor(terminal, showHardwareCursor, logDirectory, options = {}) {
|
|
27
33
|
super(terminal, showHardwareCursor, logDirectory);
|
|
34
|
+
this.mode = "fullscreen";
|
|
28
35
|
this[_a] = true;
|
|
29
36
|
this.previousScreen = [];
|
|
30
37
|
this.lastDocument = [];
|
|
@@ -32,6 +39,8 @@ export class TuiAltScreen extends TuiBase {
|
|
|
32
39
|
this.previousScreenHeight = 0;
|
|
33
40
|
this.altScreenActive = false;
|
|
34
41
|
this.imageProtocol = null;
|
|
42
|
+
this.uploadedKittyImages = new Map();
|
|
43
|
+
this.selectionGranularity = "character";
|
|
35
44
|
this.selectionAutoScrollDirection = 0;
|
|
36
45
|
this.selectionPressActive = false;
|
|
37
46
|
this.selectionDragged = false;
|
|
@@ -47,6 +56,7 @@ export class TuiAltScreen extends TuiBase {
|
|
|
47
56
|
this.wheelScrollLines = Math.max(1, Math.floor(options.wheelScrollLines ?? 1));
|
|
48
57
|
this.mouseEnabled = options.mouse ?? true;
|
|
49
58
|
this.openUrl = options.openUrl;
|
|
59
|
+
this.onRightClickPaste = options.onRightClickPaste;
|
|
50
60
|
this.addInputListener((data) => this.handleViewportInput(data));
|
|
51
61
|
}
|
|
52
62
|
get viewportTop() {
|
|
@@ -65,10 +75,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
65
75
|
render(width) {
|
|
66
76
|
return this.layoutRoot?.render(width) ?? super.render(width);
|
|
67
77
|
}
|
|
68
|
-
invalidate() {
|
|
69
|
-
super.invalidate();
|
|
70
|
-
this.layoutRoot?.invalidate();
|
|
71
|
-
}
|
|
72
78
|
getMountedRoots() {
|
|
73
79
|
return this.layoutRoot ? [this.layoutRoot] : this.children;
|
|
74
80
|
}
|
|
@@ -84,6 +90,7 @@ export class TuiAltScreen extends TuiBase {
|
|
|
84
90
|
this.altScreenActive = true;
|
|
85
91
|
const capabilities = getCapabilities();
|
|
86
92
|
this.imageProtocol = capabilities.images;
|
|
93
|
+
this.uploadedKittyImages.clear();
|
|
87
94
|
if (capabilities.images === "iterm2") {
|
|
88
95
|
this.savedCapabilities = capabilities;
|
|
89
96
|
setCapabilities({ ...capabilities, images: null });
|
|
@@ -92,12 +99,25 @@ export class TuiAltScreen extends TuiBase {
|
|
|
92
99
|
this.lastDocument = [];
|
|
93
100
|
this.selectionAnchor = undefined;
|
|
94
101
|
this.selectionFocus = undefined;
|
|
102
|
+
this.selectionGranularity = "character";
|
|
103
|
+
this.selectionInitialRange = undefined;
|
|
104
|
+
this.lastClick = undefined;
|
|
95
105
|
this.pressedUrl = undefined;
|
|
96
106
|
this.selectionDragged = false;
|
|
97
107
|
this.resetRenderState();
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
const term = process.env.TERM?.toLowerCase() ?? "";
|
|
109
|
+
// Multiplexers can lag when every pointer movement is forwarded. Button-motion
|
|
110
|
+
// tracking preserves clicks, wheel events, selections, and scrollbar dragging.
|
|
111
|
+
const mouseSequence = process.env.TMUX !== undefined ||
|
|
112
|
+
process.env.ZELLIJ !== undefined ||
|
|
113
|
+
process.env.STY !== undefined ||
|
|
114
|
+
term.startsWith("tmux") ||
|
|
115
|
+
term.startsWith("screen")
|
|
116
|
+
? ENABLE_BUTTON_MOTION_MOUSE
|
|
117
|
+
: ENABLE_ALL_MOTION_MOUSE;
|
|
118
|
+
this.terminal.write(`${ENTER_ALT_SCREEN}${DISABLE_AUTOWRAP}${this.mouseEnabled ? mouseSequence : ""}\x1b[2J\x1b[H\x1b[?25l`);
|
|
119
|
+
}
|
|
120
|
+
beforeTerminalStop(_options) {
|
|
101
121
|
this.stopSelectionAutoScroll();
|
|
102
122
|
this.selectionPressActive = false;
|
|
103
123
|
this.stopScrollbarHover();
|
|
@@ -106,22 +126,28 @@ export class TuiAltScreen extends TuiBase {
|
|
|
106
126
|
if (!this.altScreenActive)
|
|
107
127
|
return;
|
|
108
128
|
this.terminal.write(`${BEGIN_SYNCHRONIZED_OUTPUT}${this.deleteAltScreenKittyImages()}${this.mouseEnabled ? DISABLE_MOUSE : ""}${ENABLE_AUTOWRAP}${END_SYNCHRONIZED_OUTPUT}`);
|
|
129
|
+
this.uploadedKittyImages.clear();
|
|
109
130
|
}
|
|
110
|
-
afterTerminalStop() {
|
|
131
|
+
afterTerminalStop(options) {
|
|
111
132
|
if (!this.altScreenActive)
|
|
112
133
|
return;
|
|
113
134
|
this.altScreenActive = false;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
buffer
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
135
|
+
if (options.preserveScreen) {
|
|
136
|
+
this.terminal.write(`${BEGIN_SYNCHRONIZED_OUTPUT}${EXIT_ALT_SCREEN}\x1b[?25h${END_SYNCHRONIZED_OUTPUT}`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const width = Math.max(1, this.terminal.columns);
|
|
140
|
+
const documentLines = this.render(width).map((line) => line.replace(OSC133_ZONE_PREFIX, ""));
|
|
141
|
+
this.lastDocument = this.applyLineResets(documentLines.map((line) => line.replaceAll(CURSOR_MARKER, ""))).map((line) => (isImageLine(line) || visibleWidth(line) <= width ? line : sliceByColumn(line, 0, width, true)));
|
|
142
|
+
let buffer = `${BEGIN_SYNCHRONIZED_OUTPUT}${EXIT_ALT_SCREEN}${DISABLE_AUTOWRAP}`;
|
|
143
|
+
for (let row = 0; row < this.lastDocument.length; row++) {
|
|
144
|
+
if (row > 0)
|
|
145
|
+
buffer += "\r\n";
|
|
146
|
+
buffer += `\r\x1b[2K${this.lastDocument[row] ?? ""}`;
|
|
147
|
+
}
|
|
148
|
+
buffer += `\x1b[0m${ENABLE_AUTOWRAP}\r\n\x1b[?25h${END_SYNCHRONIZED_OUTPUT}`;
|
|
149
|
+
this.terminal.write(buffer);
|
|
150
|
+
}
|
|
125
151
|
if (this.savedCapabilities) {
|
|
126
152
|
setCapabilities(this.savedCapabilities);
|
|
127
153
|
this.savedCapabilities = undefined;
|
|
@@ -130,6 +156,53 @@ export class TuiAltScreen extends TuiBase {
|
|
|
130
156
|
deleteAltScreenKittyImages() {
|
|
131
157
|
return this.imageProtocol === "kitty" ? deleteAllKittyImages() : "";
|
|
132
158
|
}
|
|
159
|
+
prepareKittyScreen(screen) {
|
|
160
|
+
const visibleImageIds = new Set();
|
|
161
|
+
const lines = screen.map((line) => {
|
|
162
|
+
const placement = getKittyImagePlacement(line);
|
|
163
|
+
if (!placement)
|
|
164
|
+
return line;
|
|
165
|
+
visibleImageIds.add(placement.imageId);
|
|
166
|
+
const cachedImage = this.uploadedKittyImages.get(placement.imageId);
|
|
167
|
+
const nextCachedImage = {
|
|
168
|
+
transmissionGeneration: placement.transmissionGeneration,
|
|
169
|
+
transmissionBytes: placement.transmissionBytes,
|
|
170
|
+
estimatedDecodedBytes: placement.estimatedDecodedBytes,
|
|
171
|
+
};
|
|
172
|
+
if (cachedImage)
|
|
173
|
+
this.uploadedKittyImages.delete(placement.imageId);
|
|
174
|
+
this.uploadedKittyImages.set(placement.imageId, nextCachedImage);
|
|
175
|
+
return cachedImage?.transmissionGeneration === placement.transmissionGeneration
|
|
176
|
+
? placement.replacementLine
|
|
177
|
+
: line;
|
|
178
|
+
});
|
|
179
|
+
let cachedOffscreenImageCount = 0;
|
|
180
|
+
let cachedOffscreenTransmissionBytes = 0;
|
|
181
|
+
let cachedOffscreenDecodedBytes = 0;
|
|
182
|
+
for (const [imageId, cachedImage] of this.uploadedKittyImages) {
|
|
183
|
+
if (visibleImageIds.has(imageId))
|
|
184
|
+
continue;
|
|
185
|
+
cachedOffscreenImageCount += 1;
|
|
186
|
+
cachedOffscreenTransmissionBytes += cachedImage.transmissionBytes;
|
|
187
|
+
cachedOffscreenDecodedBytes += cachedImage.estimatedDecodedBytes;
|
|
188
|
+
}
|
|
189
|
+
let evictedImageDeletion = "";
|
|
190
|
+
for (const [imageId, cachedImage] of this.uploadedKittyImages) {
|
|
191
|
+
if (cachedOffscreenImageCount <= MAX_CACHED_OFFSCREEN_KITTY_IMAGES &&
|
|
192
|
+
cachedOffscreenTransmissionBytes <= MAX_CACHED_OFFSCREEN_KITTY_TRANSMISSION_BYTES &&
|
|
193
|
+
cachedOffscreenDecodedBytes <= MAX_CACHED_OFFSCREEN_KITTY_DECODED_BYTES) {
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
if (visibleImageIds.has(imageId))
|
|
197
|
+
continue;
|
|
198
|
+
evictedImageDeletion += deleteKittyImage(imageId);
|
|
199
|
+
this.uploadedKittyImages.delete(imageId);
|
|
200
|
+
cachedOffscreenImageCount -= 1;
|
|
201
|
+
cachedOffscreenTransmissionBytes -= cachedImage.transmissionBytes;
|
|
202
|
+
cachedOffscreenDecodedBytes -= cachedImage.estimatedDecodedBytes;
|
|
203
|
+
}
|
|
204
|
+
return { lines, evictedImageDeletion };
|
|
205
|
+
}
|
|
133
206
|
resetRenderState() {
|
|
134
207
|
this.previousScreen = [];
|
|
135
208
|
this.previousScreenWidth = 0;
|
|
@@ -179,7 +252,10 @@ export class TuiAltScreen extends TuiBase {
|
|
|
179
252
|
if (hadActiveSelection) {
|
|
180
253
|
this.selectionAnchor = undefined;
|
|
181
254
|
this.selectionFocus = undefined;
|
|
255
|
+
this.selectionGranularity = "character";
|
|
256
|
+
this.selectionInitialRange = undefined;
|
|
182
257
|
}
|
|
258
|
+
this.lastClick = undefined;
|
|
183
259
|
this.requestRender();
|
|
184
260
|
return { consume: true };
|
|
185
261
|
}
|
|
@@ -192,6 +268,8 @@ export class TuiAltScreen extends TuiBase {
|
|
|
192
268
|
}
|
|
193
269
|
const mouseEvent = this.parseSgrMouseEvent(data);
|
|
194
270
|
if (mouseEvent) {
|
|
271
|
+
if (this.handleRightClickPaste(mouseEvent))
|
|
272
|
+
return { consume: true };
|
|
195
273
|
const handled = this.handleScrollbarMouseEvent(mouseEvent);
|
|
196
274
|
if (!this.scrollbarDrag)
|
|
197
275
|
this.updateScrollbarHover(mouseEvent.x, mouseEvent.y);
|
|
@@ -215,6 +293,16 @@ export class TuiAltScreen extends TuiBase {
|
|
|
215
293
|
}
|
|
216
294
|
return { consume: true };
|
|
217
295
|
}
|
|
296
|
+
if (keybindings.matches(data, "tui.altScreen.halfPageUp")) {
|
|
297
|
+
if (!isRelease)
|
|
298
|
+
this.scrollBy(-Math.max(1, Math.floor(this.getPrimaryScrollView().viewportHeight / 2)));
|
|
299
|
+
return { consume: true };
|
|
300
|
+
}
|
|
301
|
+
if (keybindings.matches(data, "tui.altScreen.halfPageDown")) {
|
|
302
|
+
if (!isRelease)
|
|
303
|
+
this.scrollBy(Math.max(1, Math.floor(this.getPrimaryScrollView().viewportHeight / 2)));
|
|
304
|
+
return { consume: true };
|
|
305
|
+
}
|
|
218
306
|
if (keybindings.matches(data, "tui.altScreen.previousPrompt")) {
|
|
219
307
|
if (!isRelease)
|
|
220
308
|
this.scrollToPrompt(-1);
|
|
@@ -293,6 +381,18 @@ export class TuiAltScreen extends TuiBase {
|
|
|
293
381
|
release: match[4] === "m",
|
|
294
382
|
};
|
|
295
383
|
}
|
|
384
|
+
handleRightClickPaste(event) {
|
|
385
|
+
if (!this.onRightClickPaste || process.platform !== "win32" || event.release || event.button !== 2) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
this.onRightClickPaste();
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// Clipboard paste is best-effort.
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
296
396
|
getScrollbarTargetAt(x, y) {
|
|
297
397
|
if (this.hasOverlay() || !this.currentLayout)
|
|
298
398
|
return undefined;
|
|
@@ -348,6 +448,9 @@ export class TuiAltScreen extends TuiBase {
|
|
|
348
448
|
this.selectionPressActive = false;
|
|
349
449
|
this.selectionAnchor = undefined;
|
|
350
450
|
this.selectionFocus = undefined;
|
|
451
|
+
this.selectionGranularity = "character";
|
|
452
|
+
this.selectionInitialRange = undefined;
|
|
453
|
+
this.lastClick = undefined;
|
|
351
454
|
this.pressedUrl = undefined;
|
|
352
455
|
this.selectionDragged = false;
|
|
353
456
|
this.setScrollbarHover(target.scrollView);
|
|
@@ -389,6 +492,79 @@ export class TuiAltScreen extends TuiBase {
|
|
|
389
492
|
col: Math.max(0, Math.min(this.terminal.columns - 1, event.x)),
|
|
390
493
|
};
|
|
391
494
|
}
|
|
495
|
+
getSelectionSourceLine(point) {
|
|
496
|
+
if (point.scrollView && this.currentLayout) {
|
|
497
|
+
const lines = getScrollViewBox(this.currentLayout, point.scrollView)?.scrollContentLines;
|
|
498
|
+
if (lines)
|
|
499
|
+
return lines[point.row] ?? "";
|
|
500
|
+
}
|
|
501
|
+
return this.previousScreen[point.row] ?? "";
|
|
502
|
+
}
|
|
503
|
+
getWordSelection(point) {
|
|
504
|
+
const line = stripTerminalSequences(this.getSelectionSourceLine(point));
|
|
505
|
+
let start = 0;
|
|
506
|
+
for (const segment of wordSegmenter.segment(line)) {
|
|
507
|
+
const end = start + visibleWidth(segment.segment);
|
|
508
|
+
if (point.col >= start && point.col < end) {
|
|
509
|
+
return {
|
|
510
|
+
start: { ...point, col: start },
|
|
511
|
+
end: { ...point, col: end, boundary: true },
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
start = end;
|
|
515
|
+
}
|
|
516
|
+
return undefined;
|
|
517
|
+
}
|
|
518
|
+
getLineSelection(point) {
|
|
519
|
+
return {
|
|
520
|
+
start: { ...point, col: 0 },
|
|
521
|
+
end: { ...point, col: visibleWidth(this.getSelectionSourceLine(point)), boundary: true },
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
updateSelectionFocus(point) {
|
|
525
|
+
if (this.selectionGranularity === "character" || !this.selectionInitialRange) {
|
|
526
|
+
this.selectionFocus = point;
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const range = this.selectionGranularity === "word" ? this.getWordSelection(point) : this.getLineSelection(point);
|
|
530
|
+
if (!range)
|
|
531
|
+
return;
|
|
532
|
+
const initial = this.selectionInitialRange;
|
|
533
|
+
const targetBeforeInitial = range.start.row < initial.start.row ||
|
|
534
|
+
(range.start.row === initial.start.row && range.start.col < initial.start.col);
|
|
535
|
+
if (targetBeforeInitial) {
|
|
536
|
+
this.selectionAnchor = initial.end;
|
|
537
|
+
this.selectionFocus = range.start;
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
this.selectionAnchor = initial.start;
|
|
541
|
+
this.selectionFocus = range.end;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
getClickCount(point, word) {
|
|
545
|
+
const now = Date.now();
|
|
546
|
+
const previous = this.lastClick;
|
|
547
|
+
const count = word &&
|
|
548
|
+
previous &&
|
|
549
|
+
now - previous.timestamp <= DOUBLE_CLICK_INTERVAL_MS &&
|
|
550
|
+
previous.row === point.row &&
|
|
551
|
+
previous.scrollView === point.scrollView &&
|
|
552
|
+
previous.wordStart === word.start.col &&
|
|
553
|
+
previous.wordEnd === word.end.col
|
|
554
|
+
? (previous.count % 3) + 1
|
|
555
|
+
: 1;
|
|
556
|
+
this.lastClick = word
|
|
557
|
+
? {
|
|
558
|
+
timestamp: now,
|
|
559
|
+
count,
|
|
560
|
+
row: point.row,
|
|
561
|
+
scrollView: point.scrollView,
|
|
562
|
+
wordStart: word.start.col,
|
|
563
|
+
wordEnd: word.end.col,
|
|
564
|
+
}
|
|
565
|
+
: undefined;
|
|
566
|
+
return count;
|
|
567
|
+
}
|
|
392
568
|
updateSelectionAutoScroll(event) {
|
|
393
569
|
const scrollView = this.selectionAnchor?.scrollView;
|
|
394
570
|
if (!scrollView || !this.currentLayout) {
|
|
@@ -428,7 +604,7 @@ export class TuiAltScreen extends TuiBase {
|
|
|
428
604
|
}
|
|
429
605
|
const point = this.getScrollSelectionPoint(scrollView, pointer.x, pointer.y);
|
|
430
606
|
if (point)
|
|
431
|
-
this.
|
|
607
|
+
this.updateSelectionFocus(point);
|
|
432
608
|
this.requestRender();
|
|
433
609
|
}
|
|
434
610
|
stopSelectionAutoScroll() {
|
|
@@ -451,7 +627,7 @@ export class TuiAltScreen extends TuiBase {
|
|
|
451
627
|
this.stopSelectionAutoScroll();
|
|
452
628
|
if (!this.selectionAnchor)
|
|
453
629
|
return;
|
|
454
|
-
this.
|
|
630
|
+
this.updateSelectionFocus(point);
|
|
455
631
|
const clickedUrl = !this.selectionDragged &&
|
|
456
632
|
this.selectionAnchor.scrollView === point.scrollView &&
|
|
457
633
|
this.selectionAnchor.row === point.row &&
|
|
@@ -479,8 +655,9 @@ export class TuiAltScreen extends TuiBase {
|
|
|
479
655
|
if (!this.selectionPressActive || !this.selectionAnchor)
|
|
480
656
|
return;
|
|
481
657
|
this.selectionDragged = true;
|
|
658
|
+
this.lastClick = undefined;
|
|
482
659
|
this.pressedUrl = undefined;
|
|
483
|
-
this.
|
|
660
|
+
this.updateSelectionFocus(point);
|
|
484
661
|
this.updateSelectionAutoScroll(event);
|
|
485
662
|
this.requestRender();
|
|
486
663
|
return;
|
|
@@ -491,10 +668,17 @@ export class TuiAltScreen extends TuiBase {
|
|
|
491
668
|
? getScrollViewsAt(this.currentLayout, event.x, event.y)[0]
|
|
492
669
|
: undefined;
|
|
493
670
|
const anchor = this.getSelectionPoint(event, scrollView);
|
|
494
|
-
|
|
495
|
-
|
|
671
|
+
const word = this.getWordSelection(anchor);
|
|
672
|
+
const clickCount = this.getClickCount(anchor, word);
|
|
673
|
+
const range = clickCount === 2 ? word : clickCount === 3 ? this.getLineSelection(anchor) : undefined;
|
|
674
|
+
this.selectionGranularity = range ? (clickCount === 2 ? "word" : "line") : "character";
|
|
675
|
+
this.selectionInitialRange = range;
|
|
676
|
+
this.selectionAnchor = range?.start ?? anchor;
|
|
677
|
+
this.selectionFocus = range?.end ?? anchor;
|
|
496
678
|
this.selectionDragged = false;
|
|
497
|
-
this.pressedUrl =
|
|
679
|
+
this.pressedUrl = range
|
|
680
|
+
? undefined
|
|
681
|
+
: getOsc8LinkAtColumn(this.previousScreen[Math.max(0, Math.min(this.terminal.rows - 1, event.y))] ?? "", Math.max(0, Math.min(this.terminal.columns - 1, event.x)));
|
|
498
682
|
this.requestRender();
|
|
499
683
|
}
|
|
500
684
|
getSelectionBounds() {
|
|
@@ -520,7 +704,9 @@ export class TuiAltScreen extends TuiBase {
|
|
|
520
704
|
start = getGraphemeCellRange(line, selection.start.col)?.start ?? Math.min(selection.start.col, lineWidth);
|
|
521
705
|
}
|
|
522
706
|
if (row === selection.end.row) {
|
|
523
|
-
end =
|
|
707
|
+
end = selection.end.boundary
|
|
708
|
+
? Math.min(selection.end.col, lineWidth)
|
|
709
|
+
: (getGraphemeCellRange(line, selection.end.col)?.end ?? Math.min(selection.end.col + 1, lineWidth));
|
|
524
710
|
}
|
|
525
711
|
return { start: Math.max(minColumn, start), end: Math.min(maxColumn, end) };
|
|
526
712
|
}
|
|
@@ -587,14 +773,14 @@ export class TuiAltScreen extends TuiBase {
|
|
|
587
773
|
maxColumn = Math.min(this.terminal.columns, box.rect.x + box.rect.width, box.clip.x + box.clip.width);
|
|
588
774
|
screenSelection = {
|
|
589
775
|
start: {
|
|
776
|
+
...selection.start,
|
|
590
777
|
row: box.rect.y + selection.start.row - selection.start.scrollView.scrollTop,
|
|
591
778
|
col: box.rect.x + selection.start.col,
|
|
592
|
-
scrollView: selection.start.scrollView,
|
|
593
779
|
},
|
|
594
780
|
end: {
|
|
781
|
+
...selection.end,
|
|
595
782
|
row: box.rect.y + selection.end.row - selection.start.scrollView.scrollTop,
|
|
596
783
|
col: box.rect.x + selection.end.col,
|
|
597
|
-
scrollView: selection.start.scrollView,
|
|
598
784
|
},
|
|
599
785
|
};
|
|
600
786
|
}
|
|
@@ -656,18 +842,30 @@ export class TuiAltScreen extends TuiBase {
|
|
|
656
842
|
});
|
|
657
843
|
const fullRedraw = this.previousScreen.length === 0 || this.previousScreenWidth !== width || this.previousScreenHeight !== height;
|
|
658
844
|
const imagesNeedRedraw = screen.some((line, row) => line !== this.previousScreen[row] && (isImageLine(line) || isImageLine(this.previousScreen[row] ?? "")));
|
|
845
|
+
const redrawImages = fullRedraw || imagesNeedRedraw;
|
|
846
|
+
const hadUploadedKittyImages = this.uploadedKittyImages.size > 0;
|
|
847
|
+
const preparedKittyScreen = redrawImages && this.imageProtocol === "kitty"
|
|
848
|
+
? this.prepareKittyScreen(screen)
|
|
849
|
+
: { lines: screen, evictedImageDeletion: "" };
|
|
659
850
|
let buffer = BEGIN_SYNCHRONIZED_OUTPUT;
|
|
660
851
|
if (fullRedraw) {
|
|
661
852
|
this.fullRedrawCount += 1;
|
|
662
|
-
|
|
853
|
+
const clearImages = this.imageProtocol === "kitty" && hadUploadedKittyImages
|
|
854
|
+
? deleteAllKittyPlacements()
|
|
855
|
+
: this.deleteAltScreenKittyImages();
|
|
856
|
+
buffer += `${clearImages}\x1b[2J`;
|
|
663
857
|
}
|
|
664
858
|
else if (imagesNeedRedraw) {
|
|
665
|
-
|
|
859
|
+
if (this.imageProtocol === "iterm2")
|
|
860
|
+
buffer += "\x1b[2J";
|
|
861
|
+
else if (this.imageProtocol === "kitty")
|
|
862
|
+
buffer += deleteAllKittyPlacements();
|
|
666
863
|
}
|
|
864
|
+
buffer += preparedKittyScreen.evictedImageDeletion;
|
|
667
865
|
for (let row = 0; row < height; row++) {
|
|
668
866
|
if (!fullRedraw && !imagesNeedRedraw && screen[row] === this.previousScreen[row])
|
|
669
867
|
continue;
|
|
670
|
-
buffer += `\x1b[${row + 1};1H\x1b[2K${
|
|
868
|
+
buffer += `\x1b[${row + 1};1H\x1b[2K${preparedKittyScreen.lines[row] ?? ""}`;
|
|
671
869
|
}
|
|
672
870
|
if (cursorPos) {
|
|
673
871
|
buffer += `\x1b[${cursorPos.row + 1};${Math.min(width, cursorPos.col) + 1}H`;
|
|
@@ -684,4 +882,4 @@ export class TuiAltScreen extends TuiBase {
|
|
|
684
882
|
this.currentLayout = nextLayout;
|
|
685
883
|
}
|
|
686
884
|
}
|
|
687
|
-
//# sourceMappingURL=
|
|
885
|
+
//# sourceMappingURL=tui-alt-screen.js.map
|