@code-yeongyu/senpi-tui 2026.7.25-2 → 2026.7.28

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.
Files changed (50) hide show
  1. package/dist/components/editor.d.ts +22 -6
  2. package/dist/components/editor.d.ts.map +1 -1
  3. package/dist/components/editor.js +56 -120
  4. package/dist/components/editor.js.map +1 -1
  5. package/dist/components/image.d.ts.map +1 -1
  6. package/dist/components/image.js +7 -1
  7. package/dist/components/image.js.map +1 -1
  8. package/dist/components/select-list.d.ts +32 -0
  9. package/dist/components/select-list.d.ts.map +1 -1
  10. package/dist/components/select-list.js +15 -7
  11. package/dist/components/select-list.js.map +1 -1
  12. package/dist/editor-component.d.ts +18 -0
  13. package/dist/editor-component.d.ts.map +1 -1
  14. package/dist/editor-component.js.map +1 -1
  15. package/dist/index.d.ts +4 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +3 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/paste-markers.d.ts +29 -0
  20. package/dist/paste-markers.d.ts.map +1 -0
  21. package/dist/paste-markers.js +185 -0
  22. package/dist/paste-markers.js.map +1 -0
  23. package/dist/terminal-capabilities.d.ts +18 -0
  24. package/dist/terminal-capabilities.d.ts.map +1 -0
  25. package/dist/terminal-capabilities.js +62 -0
  26. package/dist/terminal-capabilities.js.map +1 -0
  27. package/dist/terminal-image.d.ts +31 -6
  28. package/dist/terminal-image.d.ts.map +1 -1
  29. package/dist/terminal-image.js +137 -92
  30. package/dist/terminal-image.js.map +1 -1
  31. package/dist/tmux-focus.d.ts +8 -0
  32. package/dist/tmux-focus.d.ts.map +1 -0
  33. package/dist/tmux-focus.js +14 -0
  34. package/dist/tmux-focus.js.map +1 -0
  35. package/dist/tmux-image-capability.d.ts +17 -0
  36. package/dist/tmux-image-capability.d.ts.map +1 -0
  37. package/dist/tmux-image-capability.js +61 -0
  38. package/dist/tmux-image-capability.js.map +1 -0
  39. package/dist/tmux-image-probe.d.ts +39 -0
  40. package/dist/tmux-image-probe.d.ts.map +1 -0
  41. package/dist/tmux-image-probe.js +121 -0
  42. package/dist/tmux-image-probe.js.map +1 -0
  43. package/dist/tui.d.ts +3 -0
  44. package/dist/tui.d.ts.map +1 -1
  45. package/dist/tui.js +63 -33
  46. package/dist/tui.js.map +1 -1
  47. package/dist/utils.d.ts.map +1 -1
  48. package/dist/utils.js +19 -0
  49. package/dist/utils.js.map +1 -1
  50. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paste-markers.js","sourceRoot":"","sources":["../src/paste-markers.ts"],"names":[],"mappings":"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","sourcesContent":["const PASTE_MARKER_REGEX = /\\[paste #(\\d+)( (\\+\\d+ lines|\\d+ chars))?\\]/g;\nconst PASTE_MARKER_SINGLE = /^\\[paste #(\\d+)( (\\+\\d+ lines|\\d+ chars))?\\]$/;\n\ninterface PasteMarkerEntry {\n\tcontent: string;\n\tmarker: string;\n\tlineCount: number;\n\tcharCount: number;\n}\n\nexport interface EditorPasteState {\n\tpastes: ReadonlyMap<number, string>;\n\tmarkers?: ReadonlyMap<number, string>;\n\tpasteCounter: number;\n}\n\nexport interface PasteMarkerRemoval {\n\ttext: string;\n\tremoved: boolean;\n}\n\nfunction formatPasteMarker(id: number, lineCount: number, charCount: number): string {\n\treturn lineCount > 10 ? `[paste #${id} +${lineCount} lines]` : `[paste #${id} ${charCount} chars]`;\n}\n\nfunction countLines(content: string): number {\n\tlet lineCount = 1;\n\tfor (let index = 0; index < content.length; index++) {\n\t\tif (content.charCodeAt(index) === 10) lineCount++;\n\t}\n\treturn lineCount;\n}\n\nfunction createEntry(id: number, content: string, marker?: string): PasteMarkerEntry {\n\tconst charCount = content.length;\n\tconst parsed = marker?.match(PASTE_MARKER_SINGLE);\n\tconst lineMatch = parsed?.[3]?.match(/^\\+(\\d+) lines$/);\n\tconst lineCount = lineMatch ? Number.parseInt(lineMatch[1]!, 10) : countLines(content);\n\tconst canonical = formatPasteMarker(id, lineCount, charCount);\n\treturn { content, marker: marker === canonical ? marker : canonical, lineCount, charCount };\n}\n\nfunction countOccurrences(text: string, marker: string): number {\n\tlet count = 0;\n\tlet offset = 0;\n\twhile (offset <= text.length - marker.length) {\n\t\tconst index = text.indexOf(marker, offset);\n\t\tif (index === -1) break;\n\t\tcount++;\n\t\toffset = index + marker.length;\n\t}\n\treturn count;\n}\n\nfunction replaceSingleOccurrence(text: string, marker: string, replacement: string): string {\n\tconst index = text.indexOf(marker);\n\tif (index === -1 || text.indexOf(marker, index + marker.length) !== -1) return text;\n\treturn text.slice(0, index) + replacement + text.slice(index + marker.length);\n}\n\nfunction entriesFromState(state: EditorPasteState): Map<number, PasteMarkerEntry> {\n\tconst entries = new Map<number, PasteMarkerEntry>();\n\tfor (const [id, content] of state.pastes) {\n\t\tentries.set(id, createEntry(id, content, state.markers?.get(id)));\n\t}\n\treturn entries;\n}\n\nexport function expandPasteMarkers(text: string, state: EditorPasteState): string {\n\tconst entries = entriesFromState(state);\n\tconst byMarker = new Map<string, PasteMarkerEntry>();\n\tfor (const entry of entries.values()) byMarker.set(entry.marker, entry);\n\n\tconst matches: Array<{ end: number; marker: string; start: number }> = [];\n\tconst counts = new Map<string, number>();\n\tfor (const match of text.matchAll(PASTE_MARKER_REGEX)) {\n\t\tconst marker = match[0];\n\t\tif (!byMarker.has(marker)) continue;\n\t\tmatches.push({ start: match.index, end: match.index + marker.length, marker });\n\t\tcounts.set(marker, (counts.get(marker) ?? 0) + 1);\n\t}\n\tif (matches.length === 0) return text;\n\n\tconst chunks: string[] = [];\n\tlet offset = 0;\n\tfor (const match of matches) {\n\t\tchunks.push(text.slice(offset, match.start));\n\t\tconst entry = byMarker.get(match.marker)!;\n\t\tchunks.push(counts.get(match.marker) === 1 ? entry.content : match.marker);\n\t\toffset = match.end;\n\t}\n\tchunks.push(text.slice(offset));\n\treturn chunks.join(\"\");\n}\n\nexport function isPasteMarker(segment: string): boolean {\n\treturn segment.length >= 10 && PASTE_MARKER_SINGLE.test(segment);\n}\n\nexport function pasteMarkerId(segment: string): number | undefined {\n\tconst match = segment.length >= 10 ? PASTE_MARKER_SINGLE.exec(segment) : null;\n\treturn match ? Number.parseInt(match[1]!, 10) : undefined;\n}\n\nexport function segmentWithPasteMarkers(\n\ttext: string,\n\tbaseSegmenter: Intl.Segmenter,\n\tvalidMarkers: ReadonlySet<string>,\n): Iterable<Intl.SegmentData> {\n\tif (validMarkers.size === 0 || !text.includes(\"[paste #\")) return baseSegmenter.segment(text);\n\n\tconst markers: Array<{ start: number; end: number }> = [];\n\tfor (const match of text.matchAll(PASTE_MARKER_REGEX)) {\n\t\tif (validMarkers.has(match[0])) markers.push({ start: match.index, end: match.index + match[0].length });\n\t}\n\tif (markers.length === 0) return baseSegmenter.segment(text);\n\n\tconst result: Intl.SegmentData[] = [];\n\tlet markerIndex = 0;\n\tlet marker = markers[0];\n\tfor (const segment of baseSegmenter.segment(text)) {\n\t\twhile (marker && segment.index >= marker.end) marker = markers[++markerIndex];\n\t\tif (!marker || segment.index < marker.start) {\n\t\t\tresult.push(segment);\n\t\t} else if (segment.index === marker.start) {\n\t\t\tresult.push({ segment: text.slice(marker.start, marker.end), index: marker.start, input: text });\n\t\t}\n\t}\n\treturn result;\n}\n\nexport class PasteMarkerRegistry {\n\tprivate entries = new Map<number, PasteMarkerEntry>();\n\tprivate pasteCounter = 0;\n\tprivate metadataBuilds = 0;\n\n\tadd(content: string, lineCount: number, charCount: number): string {\n\t\tconst id = ++this.pasteCounter;\n\t\tconst marker = formatPasteMarker(id, lineCount, charCount);\n\t\tthis.entries.set(id, { content, marker, lineCount, charCount });\n\t\tthis.metadataBuilds++;\n\t\treturn marker;\n\t}\n\n\tauthorizedMarkers(text: string): ReadonlySet<string> {\n\t\tconst markers = new Set<string>();\n\t\tfor (const entry of this.entries.values()) {\n\t\t\tif (countOccurrences(text, entry.marker) === 1) markers.add(entry.marker);\n\t\t}\n\t\treturn markers;\n\t}\n\n\tclear(): void {\n\t\tthis.entries.clear();\n\t\tthis.pasteCounter = 0;\n\t}\n\n\texpand(text: string): string {\n\t\treturn expandPasteMarkers(text, this.snapshot());\n\t}\n\n\tget metadataBuildCount(): number {\n\t\treturn this.metadataBuilds;\n\t}\n\n\tinstall(state: EditorPasteState, text: string): void {\n\t\tthis.entries = entriesFromState(state);\n\t\tthis.metadataBuilds += this.entries.size;\n\t\tthis.pasteCounter = Math.max(state.pasteCounter, ...this.entries.keys(), 0);\n\t\tthis.prune(text);\n\t}\n\n\tprune(text: string, previousText?: string): void {\n\t\tfor (const [id, entry] of this.entries) {\n\t\t\tconst wasAuthorized = previousText === undefined || countOccurrences(previousText, entry.marker) === 1;\n\t\t\tif (!wasAuthorized || countOccurrences(text, entry.marker) !== 1) this.entries.delete(id);\n\t\t}\n\t\tthis.pasteCounter = Math.max(...this.entries.keys(), 0);\n\t}\n\n\tremove(id: number, text: string): PasteMarkerRemoval {\n\t\tconst removedEntry = this.entries.get(id);\n\t\tif (!removedEntry || !this.entries.delete(id)) return { text, removed: false };\n\n\t\tlet updatedText = replaceSingleOccurrence(text, removedEntry.marker, \"\");\n\t\tconst higherIds = [...this.entries.keys()].filter((entryId) => entryId > id).sort((a, b) => a - b);\n\t\tfor (const oldId of higherIds) {\n\t\t\tconst entry = this.entries.get(oldId)!;\n\t\t\tthis.entries.delete(oldId);\n\t\t\tif (countOccurrences(updatedText, entry.marker) !== 1) continue;\n\t\t\tconst newId = oldId - 1;\n\t\t\tconst renamed = formatPasteMarker(newId, entry.lineCount, entry.charCount);\n\t\t\tupdatedText = replaceSingleOccurrence(updatedText, entry.marker, renamed);\n\t\t\tthis.entries.set(newId, { ...entry, marker: renamed });\n\t\t}\n\t\tthis.pasteCounter = Math.max(...this.entries.keys(), 0);\n\t\treturn { text: updatedText, removed: true };\n\t}\n\n\tsnapshot(): EditorPasteState {\n\t\tconst pastes = new Map<number, string>();\n\t\tconst markers = new Map<number, string>();\n\t\tfor (const [id, entry] of this.entries) {\n\t\t\tpastes.set(id, entry.content);\n\t\t\tmarkers.set(id, entry.marker);\n\t\t}\n\t\treturn { pastes, markers, pasteCounter: this.pasteCounter };\n\t}\n\n\trestore(state: EditorPasteState): void {\n\t\tthis.entries = entriesFromState(state);\n\t\tthis.metadataBuilds += this.entries.size;\n\t\tthis.pasteCounter = Math.max(state.pasteCounter, ...this.entries.keys(), 0);\n\t}\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { type TmuxExecFile } from "./tmux-image-probe.ts";
2
+ export type ImageProtocol = "kitty" | "iterm2" | null;
3
+ export interface TerminalCapabilities {
4
+ images: ImageProtocol;
5
+ trueColor: boolean;
6
+ hyperlinks: boolean;
7
+ tmuxPassthrough?: boolean;
8
+ kittyUnicodePlaceholders?: boolean;
9
+ }
10
+ export interface DetectedTerminalCapabilities extends TerminalCapabilities {
11
+ cellDimensions?: {
12
+ widthPx: number;
13
+ heightPx: number;
14
+ };
15
+ }
16
+ export declare function outerKittyGraphicsMode(clientTermname: string): "placeholder" | null;
17
+ export declare function detectTerminalCapabilities(env?: NodeJS.ProcessEnv, _platform?: NodeJS.Platform, execTmux?: TmuxExecFile): DetectedTerminalCapabilities;
18
+ //# sourceMappingURL=terminal-capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-capabilities.d.ts","sourceRoot":"","sources":["../src/terminal-capabilities.ts"],"names":[],"mappings":"AAKA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE/E,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,4BAA6B,SAAQ,oBAAoB;IACzE,cAAc,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CACvD;AAED,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAGnF;AAED,wBAAgB,0BAA0B,CACzC,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,SAAS,GAAE,MAAM,CAAC,QAA2B,EAC7C,QAAQ,CAAC,EAAE,YAAY,GACrB,4BAA4B,CAwD9B"}
@@ -0,0 +1,62 @@
1
+ import { decideTmuxImageCapability, parseTmuxKittyTerminalOverride, } from "./tmux-image-capability.js";
2
+ import { probeTmuxImageState } from "./tmux-image-probe.js";
3
+ export function outerKittyGraphicsMode(clientTermname) {
4
+ const term = clientTermname.trim().toLowerCase();
5
+ return term.includes("kitty") || term.includes("ghostty") || term.includes("warp") ? "placeholder" : null;
6
+ }
7
+ export function detectTerminalCapabilities(env = process.env, _platform = process.platform, execTmux) {
8
+ const term = env.TERM ?? "";
9
+ const termProgram = (env.TERM_PROGRAM ?? "").toLowerCase();
10
+ const terminalEmulator = env.TERMINAL_EMULATOR ?? "";
11
+ const colorTerm = env.COLORTERM ?? "";
12
+ const trueColor = colorTerm === "truecolor" || colorTerm === "24bit";
13
+ if (env.TMUX || term.startsWith("tmux")) {
14
+ const state = probeTmuxImageState(env, execTmux);
15
+ const override = parseTmuxKittyTerminalOverride(env.PI_TUI_TMUX_KITTY_TERMINAL);
16
+ const decision = decideTmuxImageCapability(state, override);
17
+ return {
18
+ images: decision.enabled ? "kitty" : null,
19
+ trueColor,
20
+ hyperlinks: state.hyperlinks,
21
+ ...(decision.enabled ? { tmuxPassthrough: true } : {}),
22
+ ...(decision.enabled && decision.placement === "placeholder" ? { kittyUnicodePlaceholders: true } : {}),
23
+ ...(state.cellDimensions ? { cellDimensions: state.cellDimensions } : {}),
24
+ };
25
+ }
26
+ if (env.KITTY_WINDOW_ID) {
27
+ return { images: "kitty", trueColor: true, hyperlinks: true };
28
+ }
29
+ if (termProgram === "ghostty") {
30
+ return { images: "kitty", trueColor: true, hyperlinks: true, kittyUnicodePlaceholders: true };
31
+ }
32
+ if (env.WARP_SESSION_ID || env.WARP_TERMINAL_SESSION_UUID || termProgram === "warpterminal") {
33
+ return { images: "kitty", trueColor: true, hyperlinks: true };
34
+ }
35
+ if (env.WEZTERM_PANE || termProgram === "wezterm") {
36
+ return { images: "kitty", trueColor: true, hyperlinks: true };
37
+ }
38
+ if (termProgram === "iterm.app" || env.ITERM_SESSION_ID) {
39
+ return { images: "iterm2", trueColor: true, hyperlinks: true };
40
+ }
41
+ if (termProgram === "apple_terminal") {
42
+ return { images: null, trueColor: false, hyperlinks: true };
43
+ }
44
+ if (env.WT_SESSION) {
45
+ return { images: null, trueColor: true, hyperlinks: true };
46
+ }
47
+ if (termProgram === "vscode") {
48
+ return { images: null, trueColor: true, hyperlinks: true };
49
+ }
50
+ if (env.CMUX_WORKSPACE_ID || terminalEmulator.includes("JetBrains")) {
51
+ return { images: null, trueColor: true, hyperlinks: false };
52
+ }
53
+ if (term.startsWith("screen")) {
54
+ return { images: null, trueColor, hyperlinks: false };
55
+ }
56
+ return {
57
+ images: term === "xterm-kitty" ? "kitty" : null,
58
+ trueColor,
59
+ hyperlinks: false,
60
+ };
61
+ }
62
+ //# sourceMappingURL=terminal-capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-capabilities.js","sourceRoot":"","sources":["../src/terminal-capabilities.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tdecideTmuxImageCapability,\n\tparseTmuxKittyTerminalOverride,\n\ttype TmuxKittyTerminalOverride,\n} from \"./tmux-image-capability.ts\";\nimport { probeTmuxImageState, type TmuxExecFile } from \"./tmux-image-probe.ts\";\n\nexport type ImageProtocol = \"kitty\" | \"iterm2\" | null;\n\nexport interface TerminalCapabilities {\n\timages: ImageProtocol;\n\ttrueColor: boolean;\n\thyperlinks: boolean;\n\ttmuxPassthrough?: boolean;\n\tkittyUnicodePlaceholders?: boolean;\n}\n\nexport interface DetectedTerminalCapabilities extends TerminalCapabilities {\n\tcellDimensions?: { widthPx: number; heightPx: number };\n}\n\nexport function outerKittyGraphicsMode(clientTermname: string): \"placeholder\" | null {\n\tconst term = clientTermname.trim().toLowerCase();\n\treturn term.includes(\"kitty\") || term.includes(\"ghostty\") || term.includes(\"warp\") ? \"placeholder\" : null;\n}\n\nexport function detectTerminalCapabilities(\n\tenv: NodeJS.ProcessEnv = process.env,\n\t_platform: NodeJS.Platform = process.platform,\n\texecTmux?: TmuxExecFile,\n): DetectedTerminalCapabilities {\n\tconst term = env.TERM ?? \"\";\n\tconst termProgram = (env.TERM_PROGRAM ?? \"\").toLowerCase();\n\tconst terminalEmulator = env.TERMINAL_EMULATOR ?? \"\";\n\tconst colorTerm = env.COLORTERM ?? \"\";\n\tconst trueColor = colorTerm === \"truecolor\" || colorTerm === \"24bit\";\n\n\tif (env.TMUX || term.startsWith(\"tmux\")) {\n\t\tconst state = probeTmuxImageState(env, execTmux);\n\t\tconst override: TmuxKittyTerminalOverride = parseTmuxKittyTerminalOverride(env.PI_TUI_TMUX_KITTY_TERMINAL);\n\t\tconst decision = decideTmuxImageCapability(state, override);\n\t\treturn {\n\t\t\timages: decision.enabled ? \"kitty\" : null,\n\t\t\ttrueColor,\n\t\t\thyperlinks: state.hyperlinks,\n\t\t\t...(decision.enabled ? { tmuxPassthrough: true } : {}),\n\t\t\t...(decision.enabled && decision.placement === \"placeholder\" ? { kittyUnicodePlaceholders: true } : {}),\n\t\t\t...(state.cellDimensions ? { cellDimensions: state.cellDimensions } : {}),\n\t\t};\n\t}\n\n\tif (env.KITTY_WINDOW_ID) {\n\t\treturn { images: \"kitty\", trueColor: true, hyperlinks: true };\n\t}\n\tif (termProgram === \"ghostty\") {\n\t\treturn { images: \"kitty\", trueColor: true, hyperlinks: true, kittyUnicodePlaceholders: true };\n\t}\n\tif (env.WARP_SESSION_ID || env.WARP_TERMINAL_SESSION_UUID || termProgram === \"warpterminal\") {\n\t\treturn { images: \"kitty\", trueColor: true, hyperlinks: true };\n\t}\n\tif (env.WEZTERM_PANE || termProgram === \"wezterm\") {\n\t\treturn { images: \"kitty\", trueColor: true, hyperlinks: true };\n\t}\n\tif (termProgram === \"iterm.app\" || env.ITERM_SESSION_ID) {\n\t\treturn { images: \"iterm2\", trueColor: true, hyperlinks: true };\n\t}\n\tif (termProgram === \"apple_terminal\") {\n\t\treturn { images: null, trueColor: false, hyperlinks: true };\n\t}\n\tif (env.WT_SESSION) {\n\t\treturn { images: null, trueColor: true, hyperlinks: true };\n\t}\n\tif (termProgram === \"vscode\") {\n\t\treturn { images: null, trueColor: true, hyperlinks: true };\n\t}\n\tif (env.CMUX_WORKSPACE_ID || terminalEmulator.includes(\"JetBrains\")) {\n\t\treturn { images: null, trueColor: true, hyperlinks: false };\n\t}\n\tif (term.startsWith(\"screen\")) {\n\t\treturn { images: null, trueColor, hyperlinks: false };\n\t}\n\treturn {\n\t\timages: term === \"xterm-kitty\" ? \"kitty\" : null,\n\t\ttrueColor,\n\t\thyperlinks: false,\n\t};\n}\n"]}
@@ -1,8 +1,11 @@
1
- export type ImageProtocol = "kitty" | "iterm2" | null;
2
- export interface TerminalCapabilities {
3
- images: ImageProtocol;
4
- trueColor: boolean;
5
- hyperlinks: boolean;
1
+ import { type DetectedTerminalCapabilities, type TerminalCapabilities } from "./terminal-capabilities.ts";
2
+ export type { ImageProtocol, TerminalCapabilities } from "./terminal-capabilities.ts";
3
+ export { outerKittyGraphicsMode } from "./terminal-capabilities.ts";
4
+ export interface TmuxPassthroughState {
5
+ allowPassthrough: "off" | "on" | "all";
6
+ clientTermname: string;
7
+ cellWidthPx?: number;
8
+ cellHeightPx?: number;
6
9
  }
7
10
  export interface CellDimensions {
8
11
  widthPx: number;
@@ -23,11 +26,17 @@ export interface ImageRenderOptions {
23
26
  }
24
27
  export declare function getCellDimensions(): CellDimensions;
25
28
  export declare function setCellDimensions(dims: CellDimensions): void;
26
- export declare function detectCapabilities(tmuxForwardsHyperlink?: () => boolean): TerminalCapabilities;
29
+ export declare function detectCapabilities(tmuxForwardsHyperlink?: () => boolean, tmuxPassthroughState?: () => TmuxPassthroughState): DetectedTerminalCapabilities;
27
30
  export declare function getCapabilities(): TerminalCapabilities;
28
31
  export declare function resetCapabilitiesCache(): void;
29
32
  /** Override the cached capabilities. Useful in tests to exercise both code paths. */
30
33
  export declare function setCapabilities(caps: TerminalCapabilities): void;
34
+ /**
35
+ * Wrap an escape sequence in a tmux DCS passthrough envelope so tmux forwards
36
+ * it verbatim to the outer terminal (requires `allow-passthrough`). Every ESC
37
+ * inside the payload must be doubled per the tmux protocol.
38
+ */
39
+ export declare function wrapTmuxPassthrough(sequence: string): string;
31
40
  export declare function isImageLine(line: string): boolean;
32
41
  /**
33
42
  * Generate a random image ID for Kitty graphics protocol.
@@ -41,7 +50,22 @@ export declare function encodeKitty(base64Data: string, options?: {
41
50
  imageId?: number;
42
51
  /** Whether Kitty should apply its default cursor movement after placement. Default: true. */
43
52
  moveCursor?: boolean;
53
+ /**
54
+ * Create a virtual placement (`U=1`) for Unicode placeholders instead of
55
+ * placing the image at the cursor. The image is only shown where
56
+ * placeholder cells (see {@link buildKittyPlaceholderRow}) are printed.
57
+ */
58
+ virtual?: boolean;
44
59
  }): string;
60
+ /** Maximum rows/columns addressable by Kitty Unicode placeholder diacritics. */
61
+ export declare const KITTY_PLACEHOLDER_MAX: number;
62
+ /**
63
+ * Build one row of Kitty Unicode placeholder cells for a virtual placement.
64
+ * The foreground color carries the low 24 bits of the image ID; the third
65
+ * diacritic carries the high byte when present. Placeholder cells are plain
66
+ * text (1 column each), so tmux and the TUI treat them like any other line.
67
+ */
68
+ export declare function buildKittyPlaceholderRow(imageId: number, row: number, columns: number): string;
45
69
  /**
46
70
  * Delete a Kitty graphics image by ID.
47
71
  * Uses uppercase 'I' to also free the image data.
@@ -73,6 +97,7 @@ export declare function renderImage(base64Data: string, imageDimensions: ImageDi
73
97
  sequence: string;
74
98
  rows: number;
75
99
  imageId?: number;
100
+ lines?: string[];
76
101
  } | null;
77
102
  /**
78
103
  * Wrap text in an OSC 8 hyperlink sequence.
@@ -1 +1 @@
1
- {"version":3,"file":"terminal-image.d.ts","sourceRoot":"","sources":["../src/terminal-image.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"terminal-image.d.ts","sourceRoot":"","sources":["../src/terminal-image.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { execSync } from "node:child_process";
1
+ import { detectTerminalCapabilities, } from "./terminal-capabilities.js";
2
+ export { outerKittyGraphicsMode } from "./terminal-capabilities.js";
2
3
  let cachedCapabilities = null;
3
4
  // Default cell dimensions - updated by TUI when terminal responds to query
4
5
  let cellDimensions = { widthPx: 9, heightPx: 18 };
@@ -8,79 +9,33 @@ export function getCellDimensions() {
8
9
  export function setCellDimensions(dims) {
9
10
  cellDimensions = dims;
10
11
  }
11
- /**
12
- * Checks whether the attached tmux client forwards OSC 8 hyperlinks to the
13
- * outer terminal. tmux only re-emits them when its `client_termfeatures` lists
14
- * `hyperlinks`, and strips them otherwise. On any error fallbacks `false`.
15
- */
16
- function probeTmuxHyperlinks() {
17
- try {
18
- const termfeatures = execSync("tmux display-message -p '#{client_termfeatures}'", {
19
- encoding: "utf8",
20
- timeout: 250,
21
- stdio: ["ignore", "pipe", "ignore"],
22
- });
23
- return termfeatures
24
- .split(",")
25
- .map((feature) => feature.trim())
26
- .includes("hyperlinks");
27
- }
28
- catch {
29
- return false;
30
- }
31
- }
32
- export function detectCapabilities(tmuxForwardsHyperlink = probeTmuxHyperlinks) {
33
- const termProgram = process.env.TERM_PROGRAM?.toLowerCase() || "";
34
- const terminalEmulator = process.env.TERMINAL_EMULATOR?.toLowerCase() || "";
35
- const term = process.env.TERM?.toLowerCase() || "";
36
- const colorTerm = process.env.COLORTERM?.toLowerCase() || "";
37
- const hasTrueColorHint = colorTerm === "truecolor" || colorTerm === "24bit";
38
- // Emit OSC 8 hyperlinks only when tmux confirms it forwards.
39
- // Image protocols are unreliable under tmux, so leave `images: null`.
40
- if (process.env.TMUX || term.startsWith("tmux")) {
41
- return { images: null, trueColor: hasTrueColorHint, hyperlinks: tmuxForwardsHyperlink() };
42
- }
43
- // screen does not forward OSC 8 hyperlinks, so keep them off there.
44
- if (term.startsWith("screen")) {
45
- return { images: null, trueColor: hasTrueColorHint, hyperlinks: false };
46
- }
47
- if (process.env.KITTY_WINDOW_ID || termProgram === "kitty") {
48
- return { images: "kitty", trueColor: true, hyperlinks: true };
49
- }
50
- if (termProgram === "ghostty" || term.includes("ghostty") || process.env.GHOSTTY_RESOURCES_DIR) {
51
- return { images: "kitty", trueColor: true, hyperlinks: true };
52
- }
53
- if (process.env.WEZTERM_PANE || termProgram === "wezterm") {
54
- return { images: "kitty", trueColor: true, hyperlinks: true };
55
- }
56
- // Warp supports the Kitty graphics protocol and OSC 8 hyperlinks.
57
- if (termProgram === "warpterminal" || process.env.WARP_SESSION_ID || process.env.WARP_TERMINAL_SESSION_UUID) {
58
- return { images: "kitty", trueColor: true, hyperlinks: true };
59
- }
60
- if (process.env.ITERM_SESSION_ID || termProgram === "iterm.app") {
61
- return { images: "iterm2", trueColor: true, hyperlinks: true };
62
- }
63
- if (process.env.WT_SESSION) {
64
- return { images: null, trueColor: true, hyperlinks: true };
65
- }
66
- if (termProgram === "vscode") {
67
- return { images: null, trueColor: true, hyperlinks: true };
68
- }
69
- if (termProgram === "alacritty") {
70
- return { images: null, trueColor: true, hyperlinks: true };
71
- }
72
- if (terminalEmulator === "jetbrains-jediterm") {
73
- return { images: null, trueColor: true, hyperlinks: false };
74
- }
75
- // Unknown terminal: be conservative. OSC 8 is rendered invisibly as "just
76
- // text" on terminals that swallow it, which means the URL disappears from
77
- // the rendered output. Default to the legacy `text (url)` behavior unless we
78
- // have positively identified a hyperlink-capable terminal above.
79
- return { images: null, trueColor: hasTrueColorHint, hyperlinks: false };
12
+ export function detectCapabilities(tmuxForwardsHyperlink, tmuxPassthroughState) {
13
+ if (!tmuxForwardsHyperlink || !tmuxPassthroughState)
14
+ return detectTerminalCapabilities();
15
+ const legacy = tmuxPassthroughState();
16
+ const hyperlinks = tmuxForwardsHyperlink();
17
+ const detected = detectTerminalCapabilities({ ...process.env, TMUX: process.env.TMUX ?? "compat,0,0" }, process.platform, () => [
18
+ "3.4",
19
+ legacy.allowPassthrough,
20
+ "on",
21
+ "1",
22
+ "1",
23
+ "1",
24
+ legacy.clientTermname,
25
+ legacy.cellWidthPx ?? "",
26
+ legacy.cellHeightPx ?? "",
27
+ hyperlinks ? "hyperlinks" : "",
28
+ ].join("|"));
29
+ if (detected.cellDimensions)
30
+ setCellDimensions(detected.cellDimensions);
31
+ return { ...detected, hyperlinks };
80
32
  }
81
33
  export function getCapabilities() {
82
34
  if (!cachedCapabilities) {
83
- cachedCapabilities = detectCapabilities();
35
+ const detected = detectCapabilities();
36
+ if (detected.cellDimensions)
37
+ setCellDimensions(detected.cellDimensions);
38
+ cachedCapabilities = detected;
84
39
  }
85
40
  return cachedCapabilities;
86
41
  }
@@ -93,6 +48,14 @@ export function setCapabilities(caps) {
93
48
  }
94
49
  const KITTY_PREFIX = "\x1b_G";
95
50
  const ITERM2_PREFIX = "\x1b]1337;File=";
51
+ /**
52
+ * Wrap an escape sequence in a tmux DCS passthrough envelope so tmux forwards
53
+ * it verbatim to the outer terminal (requires `allow-passthrough`). Every ESC
54
+ * inside the payload must be doubled per the tmux protocol.
55
+ */
56
+ export function wrapTmuxPassthrough(sequence) {
57
+ return `\x1bPtmux;${sequence.replaceAll("\x1b", "\x1b\x1b")}\x1b\\`;
58
+ }
96
59
  export function isImageLine(line) {
97
60
  // Fast path: sequence at line start (single-row images)
98
61
  if (line.startsWith(KITTY_PREFIX) || line.startsWith(ITERM2_PREFIX)) {
@@ -113,7 +76,9 @@ export function allocateImageId() {
113
76
  export function encodeKitty(base64Data, options = {}) {
114
77
  const CHUNK_SIZE = 4096;
115
78
  const params = ["a=T", "f=100", "q=2"];
116
- if (options.moveCursor === false)
79
+ if (options.virtual)
80
+ params.push("U=1");
81
+ else if (options.moveCursor === false)
117
82
  params.push("C=1");
118
83
  if (options.columns)
119
84
  params.push(`c=${options.columns}`);
@@ -121,42 +86,107 @@ export function encodeKitty(base64Data, options = {}) {
121
86
  params.push(`r=${options.rows}`);
122
87
  if (options.imageId)
123
88
  params.push(`i=${options.imageId}`);
124
- if (base64Data.length <= CHUNK_SIZE) {
125
- return `\x1b_G${params.join(",")};${base64Data}\x1b\\`;
126
- }
127
89
  const chunks = [];
128
- let offset = 0;
129
- let isFirst = true;
130
- while (offset < base64Data.length) {
131
- const chunk = base64Data.slice(offset, offset + CHUNK_SIZE);
132
- const isLast = offset + CHUNK_SIZE >= base64Data.length;
133
- if (isFirst) {
134
- chunks.push(`\x1b_G${params.join(",")},m=1;${chunk}\x1b\\`);
135
- isFirst = false;
136
- }
137
- else if (isLast) {
138
- chunks.push(`\x1b_Gm=0;${chunk}\x1b\\`);
139
- }
140
- else {
141
- chunks.push(`\x1b_Gm=1;${chunk}\x1b\\`);
90
+ if (base64Data.length <= CHUNK_SIZE) {
91
+ chunks.push(`\x1b_G${params.join(",")};${base64Data}\x1b\\`);
92
+ }
93
+ else {
94
+ let offset = 0;
95
+ let isFirst = true;
96
+ while (offset < base64Data.length) {
97
+ const chunk = base64Data.slice(offset, offset + CHUNK_SIZE);
98
+ const isLast = offset + CHUNK_SIZE >= base64Data.length;
99
+ if (isFirst) {
100
+ chunks.push(`\x1b_G${params.join(",")},m=1;${chunk}\x1b\\`);
101
+ isFirst = false;
102
+ }
103
+ else if (isLast) {
104
+ chunks.push(`\x1b_Gm=0;${chunk}\x1b\\`);
105
+ }
106
+ else {
107
+ chunks.push(`\x1b_Gm=1;${chunk}\x1b\\`);
108
+ }
109
+ offset += CHUNK_SIZE;
142
110
  }
143
- offset += CHUNK_SIZE;
111
+ }
112
+ // Under tmux passthrough every APC chunk must be wrapped individually so
113
+ // tmux forwards each one as its own DCS envelope.
114
+ if (getCapabilities().tmuxPassthrough) {
115
+ return chunks.map(wrapTmuxPassthrough).join("");
144
116
  }
145
117
  return chunks.join("");
146
118
  }
119
+ /**
120
+ * Placeholder codepoint for Kitty Unicode placements (U+10EEEE). Each cell of
121
+ * a virtual placement is this character with combining diacritics encoding the
122
+ * row/column and the foreground color encoding the image ID.
123
+ */
124
+ const KITTY_PLACEHOLDER = String.fromCodePoint(0x10eeee);
125
+ /**
126
+ * Row/column index diacritics from the Kitty graphics protocol
127
+ * (rowcolumn-diacritics.txt): diacritic at index N encodes row/column N.
128
+ */
129
+ const KITTY_PLACEHOLDER_DIACRITICS = [
130
+ 0x0305, 0x030d, 0x030e, 0x0310, 0x0312, 0x033d, 0x033e, 0x033f, 0x0346, 0x034a, 0x034b, 0x034c, 0x0350, 0x0351,
131
+ 0x0352, 0x0357, 0x035b, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, 0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d,
132
+ 0x036e, 0x036f, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0592, 0x0593, 0x0594, 0x0595, 0x0597, 0x0598, 0x0599,
133
+ 0x059c, 0x059d, 0x059e, 0x059f, 0x05a0, 0x05a1, 0x05a8, 0x05a9, 0x05ab, 0x05ac, 0x05af, 0x05c4, 0x0610, 0x0611,
134
+ 0x0612, 0x0613, 0x0614, 0x0615, 0x0616, 0x0617, 0x0657, 0x0658, 0x0659, 0x065a, 0x065b, 0x065d, 0x065e, 0x06d6,
135
+ 0x06d7, 0x06d8, 0x06d9, 0x06da, 0x06db, 0x06dc, 0x06df, 0x06e0, 0x06e1, 0x06e2, 0x06e4, 0x06e7, 0x06e8, 0x06eb,
136
+ 0x06ec, 0x0730, 0x0732, 0x0733, 0x0735, 0x0736, 0x073a, 0x073d, 0x073f, 0x0740, 0x0741, 0x0743, 0x0745, 0x0747,
137
+ 0x0749, 0x074a, 0x07eb, 0x07ec, 0x07ed, 0x07ee, 0x07ef, 0x07f0, 0x07f1, 0x07f3, 0x0816, 0x0817, 0x0818, 0x0819,
138
+ 0x081b, 0x081c, 0x081d, 0x081e, 0x081f, 0x0820, 0x0821, 0x0822, 0x0823, 0x0825, 0x0826, 0x0827, 0x0829, 0x082a,
139
+ 0x082b, 0x082c, 0x082d, 0x0951, 0x0953, 0x0954, 0x0f82, 0x0f83, 0x0f86, 0x0f87, 0x135d, 0x135e, 0x135f, 0x17dd,
140
+ 0x193a, 0x1a17, 0x1a75, 0x1a76, 0x1a77, 0x1a78, 0x1a79, 0x1a7a, 0x1a7b, 0x1a7c, 0x1b6b, 0x1b6d, 0x1b6e, 0x1b6f,
141
+ 0x1b70, 0x1b71, 0x1b72, 0x1b73, 0x1cd0, 0x1cd1, 0x1cd2, 0x1cda, 0x1cdb, 0x1ce0, 0x1dc0, 0x1dc1, 0x1dc3, 0x1dc4,
142
+ 0x1dc5, 0x1dc6, 0x1dc7, 0x1dc8, 0x1dc9, 0x1dcb, 0x1dcc, 0x1dd1, 0x1dd2, 0x1dd3, 0x1dd4, 0x1dd5, 0x1dd6, 0x1dd7,
143
+ 0x1dd8, 0x1dd9, 0x1dda, 0x1ddb, 0x1ddc, 0x1ddd, 0x1dde, 0x1ddf, 0x1de0, 0x1de1, 0x1de2, 0x1de3, 0x1de4, 0x1de5,
144
+ 0x1de6, 0x1dfe, 0x20d0, 0x20d1, 0x20d4, 0x20d5, 0x20d6, 0x20d7, 0x20db, 0x20dc, 0x20e1, 0x20e7, 0x20e9, 0x20f0,
145
+ 0x2cef, 0x2cf0, 0x2cf1, 0x2de0, 0x2de1, 0x2de2, 0x2de3, 0x2de4, 0x2de5, 0x2de6, 0x2de7, 0x2de8, 0x2de9, 0x2dea,
146
+ 0x2deb, 0x2dec, 0x2ded, 0x2dee, 0x2def, 0x2df0, 0x2df1, 0x2df2, 0x2df3, 0x2df4, 0x2df5, 0x2df6, 0x2df7, 0x2df8,
147
+ 0x2df9, 0x2dfa, 0x2dfb, 0x2dfc, 0x2dfd, 0x2dfe, 0x2dff, 0xa66f, 0xa67c, 0xa67d, 0xa6f0, 0xa6f1, 0xa8e0, 0xa8e1,
148
+ 0xa8e2, 0xa8e3, 0xa8e4, 0xa8e5, 0xa8e6, 0xa8e7, 0xa8e8, 0xa8e9, 0xa8ea, 0xa8eb, 0xa8ec, 0xa8ed, 0xa8ee, 0xa8ef,
149
+ 0xa8f0, 0xa8f1, 0xaab0, 0xaab2, 0xaab3, 0xaab7, 0xaab8, 0xaabe, 0xaabf, 0xaac1, 0xfe20, 0xfe21, 0xfe22, 0xfe23,
150
+ 0xfe24, 0xfe25, 0xfe26, 0x10a0f, 0x10a38, 0x1d185, 0x1d186, 0x1d187, 0x1d188, 0x1d189, 0x1d1aa, 0x1d1ab, 0x1d1ac,
151
+ 0x1d1ad, 0x1d242, 0x1d243, 0x1d244,
152
+ ];
153
+ /** Maximum rows/columns addressable by Kitty Unicode placeholder diacritics. */
154
+ export const KITTY_PLACEHOLDER_MAX = KITTY_PLACEHOLDER_DIACRITICS.length;
155
+ /**
156
+ * Build one row of Kitty Unicode placeholder cells for a virtual placement.
157
+ * The foreground color carries the low 24 bits of the image ID; the third
158
+ * diacritic carries the high byte when present. Placeholder cells are plain
159
+ * text (1 column each), so tmux and the TUI treat them like any other line.
160
+ */
161
+ export function buildKittyPlaceholderRow(imageId, row, columns) {
162
+ const rowDiacritic = String.fromCodePoint(KITTY_PLACEHOLDER_DIACRITICS[Math.min(row, KITTY_PLACEHOLDER_MAX - 1)]);
163
+ const idHighByte = (imageId >>> 24) & 0xff;
164
+ const idDiacritic = idHighByte > 0 ? String.fromCodePoint(KITTY_PLACEHOLDER_DIACRITICS[idHighByte]) : "";
165
+ const r = (imageId >>> 16) & 0xff;
166
+ const g = (imageId >>> 8) & 0xff;
167
+ const b = imageId & 0xff;
168
+ let cells = "";
169
+ for (let column = 0; column < columns; column++) {
170
+ const columnDiacritic = String.fromCodePoint(KITTY_PLACEHOLDER_DIACRITICS[Math.min(column, KITTY_PLACEHOLDER_MAX - 1)]);
171
+ cells += KITTY_PLACEHOLDER + rowDiacritic + columnDiacritic + idDiacritic;
172
+ }
173
+ return `\x1b[38;2;${r};${g};${b}m${cells}\x1b[39m`;
174
+ }
147
175
  /**
148
176
  * Delete a Kitty graphics image by ID.
149
177
  * Uses uppercase 'I' to also free the image data.
150
178
  */
151
179
  export function deleteKittyImage(imageId) {
152
- return `\x1b_Ga=d,d=I,i=${imageId},q=2\x1b\\`;
180
+ const sequence = `\x1b_Ga=d,d=I,i=${imageId},q=2\x1b\\`;
181
+ return getCapabilities().tmuxPassthrough ? wrapTmuxPassthrough(sequence) : sequence;
153
182
  }
154
183
  /**
155
184
  * Delete all visible Kitty graphics images.
156
185
  * Uses uppercase 'A' to also free the image data.
157
186
  */
158
187
  export function deleteAllKittyImages() {
159
- return "\x1b_Ga=d,d=A,q=2\x1b\\";
188
+ const sequence = "\x1b_Ga=d,d=A,q=2\x1b\\";
189
+ return getCapabilities().tmuxPassthrough ? wrapTmuxPassthrough(sequence) : sequence;
160
190
  }
161
191
  export function encodeITerm2(base64Data, options = {}) {
162
192
  const params = [`inline=${options.inline !== false ? 1 : 0}`];
@@ -323,6 +353,21 @@ export function renderImage(base64Data, imageDimensions, options = {}) {
323
353
  }
324
354
  const maxWidth = options.maxWidthCells ?? 80;
325
355
  const size = calculateImageCellSize(imageDimensions, maxWidth, options.maxHeightCells, getCellDimensions());
356
+ if (caps.images === "kitty" && caps.kittyUnicodePlaceholders) {
357
+ // Virtual placement: transmit once, then show the image with plain-text
358
+ // placeholder cells. Under tmux the placeholder cells move/clip with the
359
+ // pane, so split layouts render correctly.
360
+ const imageId = options.imageId ?? allocateImageId();
361
+ const columns = Math.min(size.columns, KITTY_PLACEHOLDER_MAX);
362
+ const rows = Math.min(size.rows, KITTY_PLACEHOLDER_MAX);
363
+ const sequence = encodeKitty(base64Data, { columns, rows, imageId, virtual: true });
364
+ const lines = [];
365
+ for (let row = 0; row < rows; row++) {
366
+ const placeholderRow = buildKittyPlaceholderRow(imageId, row, columns);
367
+ lines.push(row === 0 ? sequence + placeholderRow : placeholderRow);
368
+ }
369
+ return { sequence, rows, imageId, lines };
370
+ }
326
371
  if (caps.images === "kitty") {
327
372
  const sequence = encodeKitty(base64Data, {
328
373
  columns: size.columns,