@luan.sh/pi-libtui 0.3.8

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 (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +208 -0
  3. package/native-revision +1 -0
  4. package/package.json +58 -0
  5. package/src/activity-presentation.ts +75 -0
  6. package/src/appearance.ts +632 -0
  7. package/src/background-surface.ts +125 -0
  8. package/src/color/palette.ts +186 -0
  9. package/src/color/preview.ts +157 -0
  10. package/src/color/resolver.ts +75 -0
  11. package/src/color/theme.ts +531 -0
  12. package/src/component-stack.ts +247 -0
  13. package/src/content/markdown-code.ts +60 -0
  14. package/src/content/terminal-text.ts +135 -0
  15. package/src/content/text.ts +105 -0
  16. package/src/controls/action-panel.ts +420 -0
  17. package/src/controls/dialog-button-bar.ts +287 -0
  18. package/src/controls/multi-select.ts +467 -0
  19. package/src/controls/picker-panel.ts +447 -0
  20. package/src/controls/screen-icon-actions.ts +134 -0
  21. package/src/controls/searchable-select.ts +394 -0
  22. package/src/controls/select-box.ts +224 -0
  23. package/src/controls/selectable-list.ts +433 -0
  24. package/src/controls/selection-action-bar.ts +382 -0
  25. package/src/controls/semantic-input.ts +23 -0
  26. package/src/controls/tab.ts +189 -0
  27. package/src/cursor.ts +288 -0
  28. package/src/decoration/editor-pills.ts +156 -0
  29. package/src/decoration/glyphs.ts +326 -0
  30. package/src/decoration/pointer-interaction.ts +123 -0
  31. package/src/decoration/powerline-pill.ts +104 -0
  32. package/src/decoration/status.ts +185 -0
  33. package/src/decoration/transient-pill.ts +176 -0
  34. package/src/diff/index.ts +21 -0
  35. package/src/diff/model.ts +76 -0
  36. package/src/diff/parse.ts +310 -0
  37. package/src/diff/pierre-syntax.ts +86 -0
  38. package/src/diff/render.ts +504 -0
  39. package/src/diff/structured.ts +264 -0
  40. package/src/diff/view.ts +75 -0
  41. package/src/editor/chrome.ts +341 -0
  42. package/src/editor/composition.ts +53 -0
  43. package/src/editor/layout.ts +81 -0
  44. package/src/editor/presentation.ts +35 -0
  45. package/src/editor/protocol.ts +188 -0
  46. package/src/editor.ts +41 -0
  47. package/src/extension.ts +30 -0
  48. package/src/folding.ts +192 -0
  49. package/src/host/cursor-bridge.ts +210 -0
  50. package/src/host/editor-bridge.ts +148 -0
  51. package/src/host/extension-host.ts +231 -0
  52. package/src/host/mouse-bridge.ts +212 -0
  53. package/src/host/mouse-dispatch.ts +400 -0
  54. package/src/host/mouse-input.ts +85 -0
  55. package/src/host/mouse-targets.ts +238 -0
  56. package/src/host/pi-layout-adapter.ts +298 -0
  57. package/src/host/pi-native-selection.ts +227 -0
  58. package/src/host/split-pane-bridge.ts +720 -0
  59. package/src/host/transcript-bridge.ts +236 -0
  60. package/src/index.ts +334 -0
  61. package/src/line-layout.ts +6 -0
  62. package/src/motion.ts +1409 -0
  63. package/src/mouse/events.ts +55 -0
  64. package/src/mouse/fullscreen-layout.ts +260 -0
  65. package/src/mouse/registry.ts +348 -0
  66. package/src/mouse.ts +37 -0
  67. package/src/native-binary.ts +189 -0
  68. package/src/overlay/anchored.ts +91 -0
  69. package/src/overlay/detail-card.ts +158 -0
  70. package/src/overlay/dialog.ts +266 -0
  71. package/src/overlay/floating.ts +106 -0
  72. package/src/overlay/framed-editor.ts +236 -0
  73. package/src/overlay/fullscreen.ts +170 -0
  74. package/src/overlay/hover-tooltip.ts +109 -0
  75. package/src/overlay/modal-mount.ts +118 -0
  76. package/src/panels.ts +207 -0
  77. package/src/render-cache.ts +80 -0
  78. package/src/render-epoch.ts +48 -0
  79. package/src/request-animation.ts +160 -0
  80. package/src/scrollbar.ts +27 -0
  81. package/src/selection.ts +148 -0
  82. package/src/split-pane.ts +232 -0
  83. package/src/status-presentation.ts +411 -0
  84. package/src/stream.ts +543 -0
  85. package/src/syntax.ts +316 -0
  86. package/src/terminal/bridge-client.ts +243 -0
  87. package/src/terminal/embedding.ts +6 -0
  88. package/src/terminal/output.ts +151 -0
  89. package/src/terminal/projection.ts +362 -0
  90. package/src/terminal/pty-host.ts +263 -0
  91. package/src/terminal/pty-pane.ts +308 -0
  92. package/src/terminal-colors.ts +334 -0
  93. package/src/tool/action.ts +170 -0
  94. package/src/tool/activity.ts +500 -0
  95. package/src/tool/call-preview.ts +44 -0
  96. package/src/tool/disclosure-action.ts +89 -0
  97. package/src/tool/expanded-region-viewport.ts +103 -0
  98. package/src/tool/index.ts +26 -0
  99. package/src/tool/output.ts +271 -0
  100. package/src/tool/transcript.ts +150 -0
  101. package/src/tool/view-region.ts +581 -0
  102. package/test/appearance.test.ts +183 -0
  103. package/test/bounded-stream.test.ts +150 -0
  104. package/test/bridge-client.test.ts +42 -0
  105. package/test/color-preview.test.ts +23 -0
  106. package/test/component-stack.test.ts +206 -0
  107. package/test/components.test.ts +1165 -0
  108. package/test/content-text.test.ts +21 -0
  109. package/test/cursor-bridge.test.ts +151 -0
  110. package/test/cursor.test.ts +147 -0
  111. package/test/editor-chrome.test.ts +119 -0
  112. package/test/editor-layout.test.ts +30 -0
  113. package/test/editor-registry.test.ts +203 -0
  114. package/test/editor-theme.test.ts +33 -0
  115. package/test/editor-token-pill.test.ts +72 -0
  116. package/test/floating-overlay.test.ts +57 -0
  117. package/test/folding.test.ts +147 -0
  118. package/test/harmonious-theme.test.ts +23 -0
  119. package/test/interaction.test.ts +104 -0
  120. package/test/key-icons.test.ts +62 -0
  121. package/test/markdown-code.test.ts +24 -0
  122. package/test/modal-overlay-mount.test.ts +94 -0
  123. package/test/motion.test.ts +649 -0
  124. package/test/mouse/bridge.test.ts +1302 -0
  125. package/test/mouse/extension.test.ts +205 -0
  126. package/test/mouse/registry.test.ts +140 -0
  127. package/test/mouse/screen-decoration.test.ts +167 -0
  128. package/test/native-binary.test.ts +52 -0
  129. package/test/palette.test.ts +32 -0
  130. package/test/panels.test.ts +74 -0
  131. package/test/pointer-components.test.ts +151 -0
  132. package/test/progress.test.ts +54 -0
  133. package/test/pty-host.test.ts +126 -0
  134. package/test/pty-pane.test.ts +62 -0
  135. package/test/public-surface.test.ts +75 -0
  136. package/test/render-cache.test.ts +25 -0
  137. package/test/render-epoch.test.ts +119 -0
  138. package/test/request-animation.test.ts +193 -0
  139. package/test/screen-icon-actions.test.ts +69 -0
  140. package/test/select-box.test.ts +86 -0
  141. package/test/select-pointer.test.ts +369 -0
  142. package/test/selectable-list.test.ts +258 -0
  143. package/test/selection/registry.test.ts +44 -0
  144. package/test/selection-action-bar.test.ts +204 -0
  145. package/test/split-pane-bridge.test.ts +944 -0
  146. package/test/split-pane.test.ts +134 -0
  147. package/test/status-presentation.test.ts +39 -0
  148. package/test/syntax.test.ts +59 -0
  149. package/test/terminal-colors.test.ts +115 -0
  150. package/test/terminal-embedding.test.ts +12 -0
  151. package/test/terminal-projection.test.ts +312 -0
  152. package/test/theme-colors.test.ts +175 -0
  153. package/test/tool-output.test.ts +160 -0
  154. package/test/tool-transcript.test.ts +1190 -0
  155. package/test/unified-diff.test.ts +691 -0
  156. package/themes/harmonious.json +61 -0
  157. package/tsconfig.json +16 -0
@@ -0,0 +1,227 @@
1
+ import { sliceByColumn, stripTerminalSequences, visibleWidth } from "@earendil-works/pi-tui";
2
+ import type {
3
+ FullscreenLayout,
4
+ FullscreenLayoutCapability,
5
+ LayoutFrame,
6
+ LayoutScrollViewReference,
7
+ LayoutSelectionPoint,
8
+ } from "../mouse.ts";
9
+ import type { NativeSelectionCompleted, NativeSelectionGeometry, SelectionPoint } from "../selection.ts";
10
+ import {
11
+ findScrollViewBox,
12
+ intersect,
13
+ isRecord,
14
+ parseLayoutScrollViewReference,
15
+ rendererLayoutFrame,
16
+ terminalSize,
17
+ } from "./pi-layout-adapter.ts";
18
+
19
+ interface PrivateSelectionPoint extends SelectionPoint {
20
+ scrollView?: LayoutScrollViewReference;
21
+ boundary?: boolean;
22
+ }
23
+
24
+ // type-boundary: Pi 0.84.2's private selection fields are untyped; these validators narrow each reflected value.
25
+ type PiPrivateValue = unknown;
26
+
27
+ function parseSelectionPoint(value: PiPrivateValue): PrivateSelectionPoint | undefined {
28
+ if (!isRecord(value) || typeof value.row !== "number" || typeof value.col !== "number") return undefined;
29
+ if (!Number.isFinite(value.row) || !Number.isFinite(value.col)) return undefined;
30
+ const scrollView = value.scrollView === undefined ? undefined : parseLayoutScrollViewReference(value.scrollView);
31
+ if (value.scrollView !== undefined && !scrollView) return undefined;
32
+ return {
33
+ row: value.row,
34
+ col: value.col,
35
+ scrollView,
36
+ boundary: value.boundary === true,
37
+ };
38
+ }
39
+
40
+ function clamp(value: number, minimum: number, maximum: number): number {
41
+ return Math.max(minimum, Math.min(maximum, value));
42
+ }
43
+
44
+ function selectionScreenPoint(
45
+ point: PrivateSelectionPoint,
46
+ frame: LayoutFrame,
47
+ size: { rows: number; columns: number },
48
+ ): SelectionPoint | undefined {
49
+ if (!point.scrollView) {
50
+ return {
51
+ row: clamp(point.row, 0, Math.max(0, size.rows - 1)),
52
+ col: clamp(point.col, 0, Math.max(0, size.columns - 1)),
53
+ };
54
+ }
55
+ const box = findScrollViewBox(frame, point.scrollView);
56
+ if (!box || box.rect.width <= 0 || box.rect.height <= 0) return undefined;
57
+ const scrollTop = Reflect.get(point.scrollView, "scrollTop") as PiPrivateValue;
58
+ if (typeof scrollTop !== "number") return undefined;
59
+ const visible = intersect(box.rect, box.clip);
60
+ if (visible.width <= 0 || visible.height <= 0) return undefined;
61
+ const rawRow = box.rect.y + point.row - scrollTop;
62
+ const row = clamp(rawRow, visible.y, visible.y + visible.height - 1);
63
+ const rawCol =
64
+ rawRow < visible.y
65
+ ? visible.x
66
+ : rawRow >= visible.y + visible.height
67
+ ? visible.x + visible.width - 1
68
+ : box.rect.x + point.col;
69
+ return {
70
+ row: clamp(row, 0, Math.max(0, size.rows - 1)),
71
+ col: clamp(rawCol, visible.x, Math.min(size.columns - 1, visible.x + visible.width - 1)),
72
+ };
73
+ }
74
+
75
+ function selectionLines(
76
+ renderer: object,
77
+ frame: LayoutFrame,
78
+ start: PrivateSelectionPoint,
79
+ ): readonly string[] | undefined {
80
+ if (start.scrollView) {
81
+ return findScrollViewBox(frame, start.scrollView)?.scrollContentLines;
82
+ }
83
+ const previousScreen = Reflect.get(renderer, "previousScreen") as PiPrivateValue;
84
+ return Array.isArray(previousScreen) && previousScreen.every((line) => typeof line === "string")
85
+ ? previousScreen
86
+ : undefined;
87
+ }
88
+
89
+ function selectedText(lines: readonly string[], start: PrivateSelectionPoint, end: PrivateSelectionPoint): string {
90
+ const selected: string[] = [];
91
+ for (let row = start.row; row <= end.row; row += 1) {
92
+ const source = lines[row] ?? "";
93
+ const width = visibleWidth(source);
94
+ const from = row === start.row ? Math.min(start.col, width) : 0;
95
+ const to = row === end.row ? Math.min(end.boundary ? end.col : end.col + 1, width) : width;
96
+ selected.push(stripTerminalSequences(sliceByColumn(source, from, Math.max(0, to - from), true)).trimEnd());
97
+ }
98
+ return selected.join("\n");
99
+ }
100
+
101
+ const SELECTION_QUOTE_CONTEXT = 80;
102
+
103
+ function selectionQuote(
104
+ lines: readonly string[],
105
+ start: PrivateSelectionPoint,
106
+ exact: string,
107
+ ): { exact: string; prefix?: string; suffix?: string } {
108
+ const plainLines = lines.map((line) => stripTerminalSequences(line).trimEnd());
109
+ if (!Number.isInteger(start.row) || start.row < 0 || start.row >= plainLines.length) return { exact };
110
+ let offset = 0;
111
+ for (let row = 0; row < start.row; row += 1) offset += plainLines[row]!.length + 1;
112
+ const sourceLine = lines[start.row] ?? "";
113
+ const prefix = stripTerminalSequences(sliceByColumn(sourceLine, 0, Math.max(0, start.col), true));
114
+ offset += Math.min(prefix.length, plainLines[start.row]!.length);
115
+ const source = plainLines.join("\n");
116
+ if (source.slice(offset, offset + exact.length) !== exact) return { exact };
117
+ return {
118
+ exact,
119
+ ...(offset > 0 ? { prefix: source.slice(Math.max(0, offset - SELECTION_QUOTE_CONTEXT), offset) } : {}),
120
+ ...(offset + exact.length < source.length
121
+ ? { suffix: source.slice(offset + exact.length, offset + exact.length + SELECTION_QUOTE_CONTEXT) }
122
+ : {}),
123
+ };
124
+ }
125
+
126
+ interface NativeSelectionSnapshot {
127
+ start: PrivateSelectionPoint;
128
+ end: PrivateSelectionPoint;
129
+ frame: LayoutFrame;
130
+ geometry: NativeSelectionGeometry;
131
+ }
132
+
133
+ function nativeSelectionSnapshot(renderer: object): NativeSelectionSnapshot | undefined {
134
+ const anchor = parseSelectionPoint(Reflect.get(renderer, "selectionAnchor") as PiPrivateValue);
135
+ const focus = parseSelectionPoint(Reflect.get(renderer, "selectionFocus") as PiPrivateValue);
136
+ if (!anchor || !focus || anchor.scrollView !== focus.scrollView) return undefined;
137
+ if (anchor.row === focus.row && anchor.col === focus.col) return undefined;
138
+ const anchorFirst = anchor.row < focus.row || (anchor.row === focus.row && anchor.col < focus.col);
139
+ const start = anchorFirst ? anchor : focus;
140
+ const end = anchorFirst ? focus : anchor;
141
+ const frame = rendererLayoutFrame(renderer);
142
+ const size = terminalSize(renderer);
143
+ if (!frame || !size) return undefined;
144
+ const screenStart = selectionScreenPoint(start, frame, size);
145
+ const screenEnd = selectionScreenPoint(end, frame, size);
146
+ if (!screenStart || !screenEnd) return undefined;
147
+ return {
148
+ start,
149
+ end,
150
+ frame,
151
+ geometry: {
152
+ shape: Reflect.get(renderer, "selectionGranularity") === "line" ? "line" : "character",
153
+ logical: {
154
+ start: { row: start.row, col: start.col },
155
+ end: { row: end.row, col: end.col },
156
+ },
157
+ screen: { start: screenStart, end: screenEnd },
158
+ },
159
+ };
160
+ }
161
+
162
+ export function nativeSelectionGeometry(renderer: object): NativeSelectionGeometry | undefined {
163
+ return nativeSelectionSnapshot(renderer)?.geometry;
164
+ }
165
+
166
+ export function nativeSelectionCompleted(renderer: object): NativeSelectionCompleted | undefined {
167
+ const snapshot = nativeSelectionSnapshot(renderer);
168
+ if (!snapshot) return undefined;
169
+ const { start, end, frame, geometry } = snapshot;
170
+ const lines = selectionLines(renderer, frame, start);
171
+ if (!lines) return undefined;
172
+ const text = selectedText(lines, start, end);
173
+ return { ...geometry, text, source: { quote: selectionQuote(lines, start, text) } };
174
+ }
175
+
176
+ export function rendererFullscreenLayout(renderer: object): FullscreenLayout | undefined {
177
+ const frame = rendererLayoutFrame(renderer);
178
+ const primaryScrollView = frame?.primaryScrollView;
179
+ if (!frame || !primaryScrollView) return undefined;
180
+ const primaryBox = findScrollViewBox(frame, primaryScrollView);
181
+ const lines = primaryBox?.scrollContentLines;
182
+ const viewport = primaryBox ? intersect(primaryBox.rect, primaryBox.clip) : undefined;
183
+ if (!primaryBox || !lines || !viewport || viewport.width <= 0 || viewport.height <= 0) return undefined;
184
+ const selectionPoint = (key: "selectionAnchor" | "selectionFocus"): LayoutSelectionPoint | undefined => {
185
+ const point = parseSelectionPoint(Reflect.get(renderer, key) as PiPrivateValue);
186
+ if (point?.scrollView !== primaryScrollView) return undefined;
187
+ return {
188
+ row: point.row,
189
+ col: point.col,
190
+ scrollView: primaryScrollView,
191
+ ...(point.boundary ? { boundary: true } : {}),
192
+ };
193
+ };
194
+ return {
195
+ frame,
196
+ primaryBox,
197
+ primaryScrollView,
198
+ lines,
199
+ viewport: { ...viewport, scrollTop: primaryScrollView.scrollTop },
200
+ get selectionAnchor() {
201
+ return selectionPoint("selectionAnchor");
202
+ },
203
+ get selectionFocus() {
204
+ return selectionPoint("selectionFocus");
205
+ },
206
+ setSelection(anchor, focus) {
207
+ Reflect.set(renderer, "selectionAnchor", anchor);
208
+ Reflect.set(renderer, "selectionFocus", focus);
209
+ Reflect.set(renderer, "selectionActive", anchor !== undefined && focus !== undefined);
210
+ },
211
+ point(point, boundary = false) {
212
+ return { ...point, scrollView: primaryScrollView, ...(boundary ? { boundary: true } : {}) };
213
+ },
214
+ screenPoint(point) {
215
+ return {
216
+ row: primaryBox.rect.y + point.row - primaryScrollView.scrollTop,
217
+ col: primaryBox.rect.x + point.col,
218
+ };
219
+ },
220
+ };
221
+ }
222
+
223
+ export const fullscreenLayoutCapability: FullscreenLayoutCapability = {
224
+ protocol: "pi-libtui/fullscreen-layout/v1",
225
+ version: 1,
226
+ resolve: rendererFullscreenLayout,
227
+ };