@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.
Files changed (103) hide show
  1. package/dist/autocomplete.d.ts.map +1 -1
  2. package/dist/autocomplete.js.map +1 -1
  3. package/dist/components/alt-screen-flash.d.ts.map +1 -1
  4. package/dist/components/box.d.ts.map +1 -1
  5. package/dist/components/cancellable-loader.d.ts.map +1 -1
  6. package/dist/components/editor.d.ts.map +1 -1
  7. package/dist/components/editor.js +11 -0
  8. package/dist/components/editor.js.map +1 -1
  9. package/dist/components/h-stack.d.ts.map +1 -1
  10. package/dist/components/h-stack.js.map +1 -1
  11. package/dist/components/image.d.ts.map +1 -1
  12. package/dist/components/image.js.map +1 -1
  13. package/dist/components/input.d.ts.map +1 -1
  14. package/dist/components/latex.d.ts.map +1 -1
  15. package/dist/components/loader.d.ts.map +1 -1
  16. package/dist/components/loader.js.map +1 -1
  17. package/dist/components/markdown.d.ts +2 -0
  18. package/dist/components/markdown.d.ts.map +1 -1
  19. package/dist/components/markdown.js +5 -3
  20. package/dist/components/markdown.js.map +1 -1
  21. package/dist/components/scroll-view.d.ts.map +1 -1
  22. package/dist/components/scroll-view.js.map +1 -1
  23. package/dist/components/select-list.d.ts.map +1 -1
  24. package/dist/components/select-list.js.map +1 -1
  25. package/dist/components/settings-list.d.ts.map +1 -1
  26. package/dist/components/settings-list.js.map +1 -1
  27. package/dist/components/spacer.d.ts.map +1 -1
  28. package/dist/components/spacer.js.map +1 -1
  29. package/dist/components/stack.d.ts.map +1 -1
  30. package/dist/components/stack.js.map +1 -1
  31. package/dist/components/text.d.ts.map +1 -1
  32. package/dist/components/text.js.map +1 -1
  33. package/dist/components/truncated-text.d.ts.map +1 -1
  34. package/dist/components/truncated-text.js.map +1 -1
  35. package/dist/components/v-stack.d.ts.map +1 -1
  36. package/dist/components/v-stack.js +7 -2
  37. package/dist/components/v-stack.js.map +1 -1
  38. package/dist/index.d.ts +3 -3
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +2 -2
  41. package/dist/index.js.map +1 -1
  42. package/dist/keybindings.d.ts +24 -4
  43. package/dist/keybindings.d.ts.map +1 -1
  44. package/dist/keybindings.js +21 -4
  45. package/dist/keybindings.js.map +1 -1
  46. package/dist/keys.d.ts.map +1 -1
  47. package/dist/keys.js.map +1 -1
  48. package/dist/kill-ring.d.ts.map +1 -1
  49. package/dist/layout.d.ts.map +1 -1
  50. package/dist/layout.js +6 -7
  51. package/dist/layout.js.map +1 -1
  52. package/dist/native-modifiers.d.ts.map +1 -1
  53. package/dist/native-modifiers.js +10 -3
  54. package/dist/native-modifiers.js.map +1 -1
  55. package/dist/paste-markers.d.ts.map +1 -1
  56. package/dist/stdin-buffer.d.ts.map +1 -1
  57. package/dist/stdin-buffer.js.map +1 -1
  58. package/dist/terminal-capabilities.d.ts +1 -1
  59. package/dist/terminal-capabilities.d.ts.map +1 -1
  60. package/dist/terminal-capabilities.js +7 -1
  61. package/dist/terminal-capabilities.js.map +1 -1
  62. package/dist/terminal-colors.js +1 -1
  63. package/dist/terminal-colors.js.map +1 -1
  64. package/dist/terminal-image.d.ts +12 -0
  65. package/dist/terminal-image.d.ts.map +1 -1
  66. package/dist/terminal-image.js +80 -3
  67. package/dist/terminal-image.js.map +1 -1
  68. package/dist/terminal.d.ts +1 -0
  69. package/dist/terminal.d.ts.map +1 -1
  70. package/dist/terminal.js +10 -7
  71. package/dist/terminal.js.map +1 -1
  72. package/dist/tmux-image-probe.js.map +1 -1
  73. package/dist/{TuiAltScreen.d.ts → tui-alt-screen.d.ts} +19 -5
  74. package/dist/tui-alt-screen.d.ts.map +1 -0
  75. package/dist/{TuiAltScreen.js → tui-alt-screen.js} +234 -36
  76. package/dist/tui-alt-screen.js.map +1 -0
  77. package/dist/tui-main-screen.d.ts +17 -0
  78. package/dist/tui-main-screen.d.ts.map +1 -0
  79. package/dist/tui-main-screen.js +31 -0
  80. package/dist/tui-main-screen.js.map +1 -0
  81. package/dist/tui.d.ts +31 -12
  82. package/dist/tui.d.ts.map +1 -1
  83. package/dist/tui.js +55 -26
  84. package/dist/tui.js.map +1 -1
  85. package/dist/undo-stack.d.ts.map +1 -1
  86. package/dist/utils.d.ts.map +1 -1
  87. package/dist/utils.js +24 -2
  88. package/dist/utils.js.map +1 -1
  89. package/native/darwin/README.md +20 -0
  90. package/native/darwin/build.sh +62 -0
  91. package/native/darwin/src/darwin-modifiers.c +70 -0
  92. package/native/win32/README.md +22 -0
  93. package/native/win32/build.mjs +229 -0
  94. package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  95. package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  96. package/native/win32/src/win32-console-mode.c +119 -0
  97. package/package.json +9 -1
  98. package/dist/TuiAltScreen.d.ts.map +0 -1
  99. package/dist/TuiAltScreen.js.map +0 -1
  100. package/dist/TuiMainScreen.d.ts +0 -5
  101. package/dist/TuiMainScreen.d.ts.map +0 -1
  102. package/dist/TuiMainScreen.js +0 -5
  103. 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=TuiAltScreen.d.ts.map
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 ENABLE_MOUSE = "\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1004h\x1b[?1006h";
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
- this.terminal.write(`${ENTER_ALT_SCREEN}${DISABLE_AUTOWRAP}${this.mouseEnabled ? ENABLE_MOUSE : ""}\x1b[2J\x1b[H\x1b[?25l`);
99
- }
100
- beforeTerminalStop() {
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
- const width = Math.max(1, this.terminal.columns);
115
- const documentLines = this.render(width).map((line) => line.replace(OSC133_ZONE_PREFIX, ""));
116
- this.lastDocument = this.applyLineResets(documentLines.map((line) => line.replaceAll(CURSOR_MARKER, ""))).map((line) => (isImageLine(line) || visibleWidth(line) <= width ? line : sliceByColumn(line, 0, width, true)));
117
- let buffer = `${BEGIN_SYNCHRONIZED_OUTPUT}${EXIT_ALT_SCREEN}${DISABLE_AUTOWRAP}`;
118
- for (let row = 0; row < this.lastDocument.length; row++) {
119
- if (row > 0)
120
- buffer += "\r\n";
121
- buffer += `\r\x1b[2K${this.lastDocument[row] ?? ""}`;
122
- }
123
- buffer += `\x1b[0m${ENABLE_AUTOWRAP}\r\n\x1b[?25h${END_SYNCHRONIZED_OUTPUT}`;
124
- this.terminal.write(buffer);
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.selectionFocus = point;
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.selectionFocus = point;
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.selectionFocus = point;
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
- this.selectionAnchor = anchor;
495
- this.selectionFocus = anchor;
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 = 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)));
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 = getGraphemeCellRange(line, selection.end.col)?.end ?? Math.min(selection.end.col + 1, lineWidth);
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
- buffer += `${this.deleteAltScreenKittyImages()}\x1b[2J`;
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
- buffer += this.imageProtocol === "iterm2" ? "\x1b[2J" : this.deleteAltScreenKittyImages();
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${screen[row] ?? ""}`;
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=TuiAltScreen.js.map
885
+ //# sourceMappingURL=tui-alt-screen.js.map