@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,204 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
3
+ import { sliceByColumn, stripTerminalSequences } from "@earendil-works/pi-tui";
4
+ import { configureTuiAppearance, DEFAULT_TUI_APPEARANCE } from "../src/appearance.ts";
5
+ import { tuiTheme } from "../src/color/theme.ts";
6
+ import {
7
+ mountSelectionActionBar,
8
+ placeSelectionActionBar,
9
+ SelectionActionBar,
10
+ } from "../src/controls/selection-action-bar.ts";
11
+ import { ensureMouseRegistry, getMouseRegistryState } from "../src/mouse/registry.ts";
12
+ import type { TuiMouseEvent } from "../src/mouse.ts";
13
+
14
+ // type-boundary: The test fixture implements only the Theme methods consumed by tuiTheme.
15
+ const theme = {
16
+ name: "dark",
17
+ getColorMode: () => "256color",
18
+ getFgAnsi: () => "\x1b[38;5;255m",
19
+ getBgAnsi: (color: string) => (color === "selectedBg" ? "\x1b[48;5;17m" : "\x1b[48;5;16m"),
20
+ } as unknown as Theme;
21
+
22
+ function mouse(type: TuiMouseEvent["type"], col: number, button?: 0 | 1 | 2): TuiMouseEvent {
23
+ return {
24
+ type,
25
+ row: 0,
26
+ col,
27
+ screenRow: 4,
28
+ screenCol: col + 10,
29
+ button,
30
+ wheel: undefined,
31
+ shift: false,
32
+ alt: false,
33
+ ctrl: false,
34
+ };
35
+ }
36
+
37
+ describe("selection action bar", () => {
38
+ afterEach(() => configureTuiAppearance(DEFAULT_TUI_APPEARANCE));
39
+
40
+ test("renders the connected Nerd Font surface and activates exact pointer and key targets", () => {
41
+ configureTuiAppearance({ iconPack: "nerd-fonts", powerline: true });
42
+ const activated: string[] = [];
43
+ let renders = 0;
44
+ const bar = new SelectionActionBar({
45
+ theme,
46
+ actions: [
47
+ { value: "comment", label: "comment", icon: "comment", shortcuts: ["c"] },
48
+ { value: "reaction", label: "react", icon: "reaction", shortcuts: ["r"] },
49
+ { value: "copy", label: "copy", icon: "copy", shortcuts: ["y"] },
50
+ ],
51
+ requestRender: () => {
52
+ renders += 1;
53
+ },
54
+ onActivate: (value) => activated.push(value),
55
+ });
56
+ const row = bar.render(80)[0]!;
57
+ expect(stripTerminalSequences(row)).toBe("  comment 󰬊   react 󰬙   copy 󰬠 ");
58
+ const colors = tuiTheme(theme);
59
+ expect(row).not.toContain(colors.bgAnsi("badge.neutral"));
60
+ expect(row).toContain(`${colors.fgAnsi("text.secondary")}󰬊`);
61
+ const reaction = bar.getGeometry()!.actions[1]!;
62
+ expect(bar.onMouse(mouse("move", reaction.x))).toBe(true);
63
+ expect(renders).toBe(1);
64
+ expect(stripTerminalSequences(bar.render(80)[0]!)).toBe("  comment 󰬊   react 󰬙   copy 󰬠 ");
65
+ expect(bar.render(80)[0]!).not.toContain(colors.bgAnsi("badge.neutral"));
66
+ expect(bar.onMouse(mouse("press", reaction.x, 0))).toBe(true);
67
+ expect(bar.onMouse(mouse("release", reaction.x, 0))).toBe(true);
68
+ expect(activated).toEqual(["reaction"]);
69
+ expect(bar.handleInput("y")).toBe(true);
70
+ expect(activated).toEqual(["reaction", "copy"]);
71
+ });
72
+
73
+ test("places above, flips below, and clamps within the transcript viewport", () => {
74
+ expect(
75
+ placeSelectionActionBar({
76
+ selection: { start: { row: 5, col: 20 }, end: { row: 5, col: 25 } },
77
+ barWidth: 12,
78
+ screenWidth: 40,
79
+ screenHeight: 20,
80
+ }),
81
+ ).toEqual({ x: 17, y: 4, width: 12, height: 1 });
82
+ expect(
83
+ placeSelectionActionBar({
84
+ selection: { start: { row: 2, col: 2 }, end: { row: 2, col: 4 } },
85
+ barWidth: 12,
86
+ screenWidth: 40,
87
+ screenHeight: 20,
88
+ viewport: { x: 0, y: 2, width: 40, height: 10, scrollTop: 0 },
89
+ }),
90
+ ).toEqual({ x: 0, y: 3, width: 12, height: 1 });
91
+ });
92
+
93
+ test("centers on selected content when the terminal endpoint includes trailing cells", () => {
94
+ const request = {
95
+ selection: { start: { row: 5, col: 20 }, end: { row: 5, col: 79 } },
96
+ barWidth: 12,
97
+ screenWidth: 80,
98
+ screenHeight: 20,
99
+ };
100
+ expect(placeSelectionActionBar({ ...request })).toEqual({ x: 44, y: 4, width: 12, height: 1 });
101
+ expect(placeSelectionActionBar({ ...request, selectedText: "hello\n" })).toEqual({
102
+ x: 17,
103
+ y: 4,
104
+ width: 12,
105
+ height: 1,
106
+ });
107
+ });
108
+
109
+ test("uses a readable semantic hover surface", () => {
110
+ configureTuiAppearance({ iconPack: "nerd-fonts", powerline: true });
111
+ const bar = new SelectionActionBar({
112
+ theme,
113
+ actions: [{ value: "comment", label: "comment", icon: "comment", shortcuts: ["c"] }],
114
+ requestRender() {},
115
+ onActivate() {},
116
+ });
117
+ bar.render(80);
118
+ const action = bar.getGeometry()!.actions[0]!;
119
+ bar.onMouse(mouse("move", action.x));
120
+ const hovered = bar.render(80)[0]!;
121
+ const colors = tuiTheme(theme);
122
+ expect(hovered).toContain(colors.bgAnsi("surface.hover"));
123
+ expect(hovered).toContain(colors.fgAnsi(colors.contrastBackground(colors.color("surface.hover"))));
124
+ expect(hovered).not.toContain(colors.bgAnsi(colors.contrastBackground(colors.color("action.neutral"))));
125
+ });
126
+
127
+ test("does not consume unused placement cells when compositing", () => {
128
+ configureTuiAppearance({ iconPack: "nerd-fonts", powerline: true });
129
+ const bar = new SelectionActionBar({
130
+ theme,
131
+ actions: [{ value: "copy", label: "copy", icon: "copy", shortcuts: ["y"] }],
132
+ requestRender() {},
133
+ onActivate() {},
134
+ });
135
+ const natural = bar.render(80)[0]!;
136
+ const naturalWidth = bar.getGeometry()!.width;
137
+ const base = `\x1b[48;5;22m${"x".repeat(80)}\x1b[49m`;
138
+ const rendered = bar.composite([base], { x: 3, y: 0, width: naturalWidth + 8, height: 1 })[0]!;
139
+ const plain = stripTerminalSequences(rendered);
140
+ const colors = tuiTheme(theme);
141
+ expect(rendered).not.toContain(colors.bgAnsi("badge.neutral"));
142
+ expect(rendered).toContain(colors.bgAnsi("action.neutral"));
143
+ expect(sliceByColumn(plain, 3, naturalWidth, true)).toBe(stripTerminalSequences(natural));
144
+ expect(sliceByColumn(plain, 3 + naturalWidth, 1, true)).toBe("x");
145
+ });
146
+
147
+ test("uses a subdued distinct surface over a matching user-message background", () => {
148
+ configureTuiAppearance({ iconPack: "nerd-fonts", powerline: true });
149
+ const bar = new SelectionActionBar({
150
+ theme,
151
+ actions: [{ value: "copy", label: "copy", icon: "copy", shortcuts: ["y"] }],
152
+ requestRender() {},
153
+ onActivate() {},
154
+ });
155
+ bar.render(80);
156
+ const width = bar.getGeometry()!.width;
157
+ const destination = tuiTheme(theme).bgAnsi("action.neutral");
158
+ const base = `${destination}${"x".repeat(80)}\x1b[49m`;
159
+ const rendered = bar.composite([base], { x: 3, y: 0, width, height: 1 })[0]!;
160
+ expect(rendered).toContain(tuiTheme(theme).bgAnsi("surface.raised"));
161
+ });
162
+
163
+ test("mounts placement, compositing, pointer routing, visibility, and disposal as one lifecycle", () => {
164
+ configureTuiAppearance({ iconPack: "nerd-fonts", powerline: true });
165
+ const registry = ensureMouseRegistry(Object.create(null) as typeof globalThis);
166
+ const activated: string[] = [];
167
+ let hidden = false;
168
+ const mount = mountSelectionActionBar({
169
+ registry,
170
+ id: "test.selection-actions",
171
+ theme,
172
+ actions: [{ value: "copy", label: "copy", icon: "copy", shortcuts: ["y"] }],
173
+ requestRender() {},
174
+ onActivate: (value) => activated.push(value),
175
+ isHidden: () => hidden,
176
+ getTarget: () => ({
177
+ selection: { start: { row: 2, col: 8 }, end: { row: 2, col: 11 } },
178
+ selectedText: "copy",
179
+ }),
180
+ });
181
+ const context = { width: 30, height: 5, hasOverlay: false };
182
+ const decorated = registry.dispatchScreenDecorators(
183
+ Array.from({ length: 5 }, () => " ".repeat(30)),
184
+ context,
185
+ );
186
+ expect(stripTerminalSequences(decorated[1]!)).toContain("copy");
187
+ const state = getMouseRegistryState(registry);
188
+ const region = state.regions[0]!;
189
+ const rect = region.getRect()!;
190
+ const actionCol = stripTerminalSequences(decorated[rect.y]!).indexOf("copy") - rect.x;
191
+ expect(region.onMouse(mouse("press", actionCol, 0))).toBe(true);
192
+ expect(region.onMouse(mouse("release", actionCol, 0))).toBe(true);
193
+ expect(activated).toEqual(["copy"]);
194
+ expect(region.getRect()).toBeUndefined();
195
+
196
+ hidden = true;
197
+ registry.dispatchScreenDecorators(decorated, context);
198
+ expect(region.getRect()).toBeUndefined();
199
+ mount.dispose();
200
+ mount.dispose();
201
+ expect(state.screenDecorators).toHaveLength(0);
202
+ expect(state.regions).toHaveLength(0);
203
+ });
204
+ });