@code-yeongyu/senpi-tui 2026.9.12-3 → 2026.9.12

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 (94) hide show
  1. package/README.md +4 -53
  2. package/dist/alt-screen-search.d.ts +1 -21
  3. package/dist/alt-screen-search.d.ts.map +1 -1
  4. package/dist/alt-screen-search.js +42 -199
  5. package/dist/alt-screen-search.js.map +1 -1
  6. package/dist/components/box.d.ts +1 -3
  7. package/dist/components/box.d.ts.map +1 -1
  8. package/dist/components/box.js +0 -28
  9. package/dist/components/box.js.map +1 -1
  10. package/dist/components/editor.d.ts +1 -6
  11. package/dist/components/editor.d.ts.map +1 -1
  12. package/dist/components/editor.js +18 -98
  13. package/dist/components/editor.js.map +1 -1
  14. package/dist/components/input.d.ts +1 -12
  15. package/dist/components/input.d.ts.map +1 -1
  16. package/dist/components/input.js +6 -40
  17. package/dist/components/input.js.map +1 -1
  18. package/dist/components/latex.d.ts.map +1 -1
  19. package/dist/components/latex.js +0 -7
  20. package/dist/components/latex.js.map +1 -1
  21. package/dist/components/loader.d.ts +0 -3
  22. package/dist/components/loader.d.ts.map +1 -1
  23. package/dist/components/loader.js +8 -20
  24. package/dist/components/loader.js.map +1 -1
  25. package/dist/components/scroll-view.d.ts +3 -6
  26. package/dist/components/scroll-view.d.ts.map +1 -1
  27. package/dist/components/scroll-view.js +1 -6
  28. package/dist/components/scroll-view.js.map +1 -1
  29. package/dist/components/select-list.d.ts +1 -4
  30. package/dist/components/select-list.d.ts.map +1 -1
  31. package/dist/components/select-list.js +2 -48
  32. package/dist/components/select-list.js.map +1 -1
  33. package/dist/components/settings-list.d.ts +1 -5
  34. package/dist/components/settings-list.d.ts.map +1 -1
  35. package/dist/components/settings-list.js +5 -54
  36. package/dist/components/settings-list.js.map +1 -1
  37. package/dist/index.d.ts +1 -3
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +0 -3
  40. package/dist/index.js.map +1 -1
  41. package/dist/layout.d.ts +1 -3
  42. package/dist/layout.d.ts.map +1 -1
  43. package/dist/layout.js +9 -32
  44. package/dist/layout.js.map +1 -1
  45. package/dist/native-modifiers.d.ts +1 -2
  46. package/dist/native-modifiers.d.ts.map +1 -1
  47. package/dist/native-modifiers.js +44 -3
  48. package/dist/native-modifiers.js.map +1 -1
  49. package/dist/terminal.d.ts.map +1 -1
  50. package/dist/terminal.js +20 -2
  51. package/dist/terminal.js.map +1 -1
  52. package/dist/tui-alt-screen.d.ts +1 -31
  53. package/dist/tui-alt-screen.d.ts.map +1 -1
  54. package/dist/tui-alt-screen.js +41 -335
  55. package/dist/tui-alt-screen.js.map +1 -1
  56. package/dist/tui.d.ts +5 -83
  57. package/dist/tui.d.ts.map +1 -1
  58. package/dist/tui.js +4 -118
  59. package/dist/tui.js.map +1 -1
  60. package/dist/utils.d.ts +0 -2
  61. package/dist/utils.d.ts.map +1 -1
  62. package/dist/utils.js +0 -9
  63. package/dist/utils.js.map +1 -1
  64. package/native/darwin/README.md +6 -8
  65. package/native/darwin/build.sh +5 -12
  66. package/native/darwin/prebuilds/darwin-arm64/{darwin-platform.node → darwin-modifiers.node} +0 -0
  67. package/native/darwin/prebuilds/darwin-x64/{darwin-platform.node → darwin-modifiers.node} +0 -0
  68. package/native/darwin/src/darwin-modifiers.c +70 -0
  69. package/native/win32/README.md +7 -18
  70. package/native/win32/build.mjs +4 -7
  71. package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  72. package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  73. package/native/win32/src/win32-console-mode.c +119 -0
  74. package/package.json +3 -10
  75. package/dist/components/mouse-region.d.ts +0 -12
  76. package/dist/components/mouse-region.d.ts.map +0 -1
  77. package/dist/components/mouse-region.js +0 -19
  78. package/dist/components/mouse-region.js.map +0 -1
  79. package/dist/native-platform.d.ts +0 -18
  80. package/dist/native-platform.d.ts.map +0 -1
  81. package/dist/native-platform.js +0 -42
  82. package/dist/native-platform.js.map +0 -1
  83. package/native/clipboard.h +0 -170
  84. package/native/darwin/src/darwin-platform.m +0 -93
  85. package/native/linux/README.md +0 -23
  86. package/native/linux/build.sh +0 -65
  87. package/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  88. package/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  89. package/native/linux/src/clipboard-worker.h +0 -70
  90. package/native/linux/src/linux-platform-x11.c +0 -274
  91. package/native/napi.h +0 -89
  92. package/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  93. package/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  94. package/native/win32/src/win32-platform.c +0 -241
package/README.md CHANGED
@@ -11,7 +11,7 @@ Minimal terminal UI framework with differential rendering and synchronized outpu
11
11
  - **Bracketed Paste Mode**: Handles large pastes correctly with markers for >10 line pastes
12
12
  - **Component-based**: Simple Component interface with render() method
13
13
  - **Theme Support**: Components accept theme interfaces for customizable styling
14
- - **Built-in Components**: Text, TruncatedText, Input, Editor, Markdown, Loader, SelectList, SettingsList, MouseRegion, Spacer, Image, Box, Container, VStack, HStack, ScrollView
14
+ - **Built-in Components**: Text, TruncatedText, Input, Editor, Markdown, Loader, SelectList, SettingsList, Spacer, Image, Box, Container, VStack, HStack, ScrollView
15
15
  - **Inline Images**: Renders images in terminals that support Kitty or iTerm2 graphics protocols
16
16
  - **Autocomplete Support**: File paths and slash commands
17
17
 
@@ -121,7 +121,7 @@ if (isViewportTUI(tui)) {
121
121
  }
122
122
  ```
123
123
 
124
- Stack entries support `basis`, `grow`, `shrink`, `minSize`, `maxSize`, and responsive `visible` callbacks. Mouse-wheel input targets the scroll view under the pointer and unused delta chains to outer scroll views by default. The primary scroll view receives the alternate-screen keyboard navigation actions and wheel input over non-scrollable regions. It can also jump between OSC 133 semantic prompt markers, matching common terminal prompt-navigation shortcuts. Press `Ctrl+Shift+F` to open or close its bordered search panel. The panel shows the configured previous/next shortcuts and provides clickable arrow controls; by default, `Enter`/`Ctrl+G` and `Shift+Enter`/`Ctrl+Shift+G` move between matches, and `Escape` also closes search. `TuiAltScreenOptions.searchMatchStyle` and `searchCurrentMatchStyle` customize match highlighting, while `searchNavigationButtonStyle` styles each arrow button and receives its hover state. `TuiAltScreenOptions.scrollToEndIndicator` renders a clickable label centered on the last row of a `follow: "end"` primary scroll view while it is scrolled away from the end; clicking it resumes end-following.
124
+ Stack entries support `basis`, `grow`, `shrink`, `minSize`, `maxSize`, and responsive `visible` callbacks. Mouse-wheel input targets the scroll view under the pointer and unused delta chains to outer scroll views by default. The primary scroll view receives the alternate-screen keyboard navigation actions and wheel input over non-scrollable regions. It can also jump between OSC 133 semantic prompt markers, matching common terminal prompt-navigation shortcuts. Press `Ctrl+Shift+F` to search its rendered content, `Enter`/`Ctrl+G` and `Shift+Enter`/`Ctrl+Shift+G` to move between matches, and `Escape` to close search. `TuiAltScreenOptions.searchMatchStyle` and `searchCurrentMatchStyle` customize match highlighting.
125
125
 
126
126
  Layout geometry is rebuilt for each requested frame. Stateful components are retained, and their existing rendered-line caches remain effective. Calling `render(width)` directly on these layout components produces an unbounded document, which is also used when alt mode restores the main screen.
127
127
 
@@ -177,7 +177,6 @@ handle.unfocus(); // Release focus to normal fallback
177
177
  handle.unfocus({ target: baseComponent }); // Release this overlay to a specific component
178
178
  handle.unfocus({ target: null }); // Release this overlay and leave focus empty
179
179
  handle.isFocused(); // Check if overlay has focus
180
- handle.getBounds(); // Get last rendered terminal-relative bounds
181
180
 
182
181
  handle.unfocus();
183
182
  // Overlay loses focus; TUI falls back to another visible capturing overlay or the previous focus target.
@@ -212,7 +211,6 @@ All components implement:
212
211
  interface Component {
213
212
  render(width: number): string[];
214
213
  handleInput?(data: string): void;
215
- handleMouse?(event: TuiMouseEvent): TuiMouseEventResult | undefined;
216
214
  invalidate?(): void;
217
215
  }
218
216
  ```
@@ -221,50 +219,10 @@ interface Component {
221
219
  |--------|-------------|
222
220
  | `render(width)` | Returns an array of strings, one per line. Each line **must not exceed `width`** or the TUI will error. Use `truncateToWidth()` or manual wrapping to ensure this. |
223
221
  | `handleInput?(data)` | Called when the component has focus and receives keyboard input. The `data` string contains raw terminal input (may include ANSI escape sequences). |
224
- | `handleMouse?(event)` | Called by `TuiAltScreen` for normalized pointer input targeted at the component. |
225
222
  | `invalidate?()` | Called to clear any cached render state. Components should re-render from scratch on the next `render()` call. |
226
223
 
227
224
  The TUI appends a full SGR reset and OSC 8 reset at the end of each rendered line. Styles do not carry across lines. If you emit multi-line text with styling, reapply styles per line or use `wrapTextWithAnsi()` so styles are preserved for each wrapped line.
228
225
 
229
- ### Mouse Input
230
-
231
- `TuiAltScreen` normalizes SGR mouse input and hit-tests components and overlays. Events contain component-local `x`/`y`, absolute `screenX`/`screenY`, bounds, button, modifiers, click count, and wheel delta. `TuiMainScreen` does not capture mouse input because the terminal owns its scrollback.
232
-
233
- ```typescript
234
- import type { TuiMouseEvent, TuiMouseEventResult } from "@earendil-works/pi-tui";
235
-
236
- handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
237
- if (event.type === "click" && event.button === "left") {
238
- this.expanded = !this.expanded;
239
- return { handled: true };
240
- }
241
- if (event.type === "press" && event.button === "left") {
242
- return { handled: true, capture: true, focus: true };
243
- }
244
- if (event.type === "drag") {
245
- this.updateFromPointer(event.x, event.y);
246
- return { handled: true, render: true };
247
- }
248
- return undefined;
249
- }
250
- ```
251
-
252
- Returning `handled` suppresses renderer-level fallback behavior. `capture` keeps subsequent drag and release events routed to the same component. `focus` requests keyboard focus. The optional `render` flag controls repainting: press, click, drag, and wheel default to rendering; move and release do not. Set `render: true` for a hover state that visibly changed, or `render: false` for a handled no-op. Render requests are coalesced and terminal output remains differential.
253
-
254
- Unhandled gestures retain alternate-screen defaults: wheel input scrolls the nearest `ScrollView` and chains unused delta, primary-button drags select text, OSC 8 links open before parent click handlers, and unhandled right-click preserves configured paste behavior. A click is emitted only when press/release completes without a drag.
255
-
256
- Use `MouseRegion` to add mouse behavior without changing a component's rendering:
257
-
258
- ```typescript
259
- const collapsible = new MouseRegion(content, (event) => {
260
- if (event.type !== "click" || event.button !== "left") return undefined;
261
- expanded = !expanded;
262
- return { handled: true };
263
- });
264
- ```
265
-
266
- `Container` and `Box` route events to nested children using geometry recorded by the last rendered frame, so pointer motion does not rerender children merely to hit-test them. Explicit `VStack`, `HStack`, and `ScrollView` layouts use the alternate-screen layout frame directly.
267
-
268
226
  ### Focusable Interface (IME Support)
269
227
 
270
228
  Components that display a text cursor and need IME (Input Method Editor) support should implement the `Focusable` interface:
@@ -289,7 +247,7 @@ When a `Focusable` component has focus, TUI:
289
247
  3. Positions the hardware terminal cursor at that location
290
248
  4. Shows the hardware cursor only when `showHardwareCursor` is enabled
291
249
 
292
- The cursor remains hidden by default. This keeps the fake cursor rendering, while still positioning the hardware cursor for terminals that track IME candidate windows with hidden cursors. Some terminals require a visible hardware cursor for IME positioning; enable it with the renderer constructor's `showHardwareCursor` argument or `setShowHardwareCursor(true)`. The `Editor` and `Input` built-in components already implement this interface.
250
+ The cursor remains hidden by default. This keeps the fake cursor rendering, while still positioning the hardware cursor for terminals that track IME candidate windows with hidden cursors. Some terminals require a visible hardware cursor for IME positioning; enable it with the renderer constructor's `showHardwareCursor` argument, `setShowHardwareCursor(true)`, or `PI_HARDWARE_CURSOR=1`. The `Editor` and `Input` built-in components already implement this interface.
293
251
 
294
252
  **Container components with embedded inputs:** When a container component (dialog, selector, etc.) contains an `Input` or `Editor` child, the container must implement `Focusable` and propagate the focus state to the child:
295
253
 
@@ -381,8 +339,6 @@ input.setValue("initial");
381
339
  input.getValue();
382
340
  ```
383
341
 
384
- Clicking positions the cursor and gives the input keyboard focus in alternate-screen mode.
385
-
386
342
  **Key Bindings:**
387
343
  - `Enter` - Submit
388
344
  - `Ctrl+A` / `Ctrl+E` - Line start/end
@@ -418,7 +374,6 @@ editor.getPaddingX(); // Get current padding
418
374
  ```
419
375
 
420
376
  **Features:**
421
- - Click-to-position cursor and clickable autocomplete rows in alternate-screen mode
422
377
  - Multi-line editing with word wrap
423
378
  - Slash command autocomplete (type `/`)
424
379
  - File path autocomplete (press `Tab`)
@@ -559,8 +514,6 @@ list.setFilter("opt"); // Filter items
559
514
  ```
560
515
 
561
516
  **Controls:**
562
- - Mouse move/wheel: Highlight rows in alternate-screen mode
563
- - Click: Select a row
564
517
  - Arrow keys: Navigate
565
518
  - Enter: Select
566
519
  - Escape: Cancel
@@ -601,8 +554,6 @@ settings.updateValue("theme", "light");
601
554
  ```
602
555
 
603
556
  **Controls:**
604
- - Mouse move/wheel: Highlight rows in alternate-screen mode
605
- - Click: Activate a row
606
557
  - Arrow keys: Navigate
607
558
  - Enter/Space: Activate (cycle value or open submenu)
608
559
  - Escape: Cancel
@@ -706,7 +657,7 @@ if (matchesKey(data, Key.enter)) {
706
657
  2. **Width Changed or Change Above Viewport**: Clear screen and fully re-render
707
658
  3. **Normal Update**: Move the cursor to the first changed line, clear to the end, and render changed lines
708
659
 
709
- `TuiAltScreen` owns a terminal-height viewport. Without an explicit layout root it preserves the legacy single-document scrolling behavior. With `setLayoutRoot()`, `VStack`, `HStack`, and nested `ScrollView` components can reserve fixed regions and independently scroll constrained regions. It updates changed viewport rows in place, follows streaming output while at the bottom, and preserves a manually selected scroll position while content grows. Mouse-wheel and configurable keyboard navigation scroll without modifying terminal scrollback, including jumps between OSC 133 semantic prompt markers. Scrollbars support hover expansion, thumb dragging, and track-click jumping. Clicking an OSC 8 hyperlink opens it with the configured URL handler. Dragging with the primary mouse button selects text and, unless `TuiAltScreenOptions.copyOnSelect` is `false`, copies it to the clipboard with OSC 52; holding the drag at a scroll view's top or bottom edge auto-scrolls and extends the selection into off-screen content. Kitty images support vertical viewport cropping; iTerm2 inline images fall back to text because the iTerm2 protocol cannot delete or crop placements during viewport repainting.
660
+ `TuiAltScreen` owns a terminal-height viewport. Without an explicit layout root it preserves the legacy single-document scrolling behavior. With `setLayoutRoot()`, `VStack`, `HStack`, and nested `ScrollView` components can reserve fixed regions and independently scroll constrained regions. It updates changed viewport rows in place, follows streaming output while at the bottom, and preserves a manually selected scroll position while content grows. Mouse-wheel and configurable keyboard navigation scroll without modifying terminal scrollback, including jumps between OSC 133 semantic prompt markers. Clicking an OSC 8 hyperlink opens it with the configured URL handler. Dragging with the primary mouse button selects text and, unless `TuiAltScreenOptions.copyOnSelect` is `false`, copies it to the clipboard with OSC 52; holding the drag at a scroll view's top or bottom edge auto-scrolls and extends the selection into off-screen content. Kitty images support vertical viewport cropping; iTerm2 inline images fall back to text because the iTerm2 protocol cannot delete or crop placements during viewport repainting.
710
661
 
711
662
  Both renderers wrap updates in **synchronized output** (`\x1b[?2026h` ... `\x1b[?2026l`) for atomic, flicker-free rendering.
712
663
 
@@ -7,38 +7,18 @@ export interface AltScreenSearchSegment {
7
7
  export interface AltScreenSearchMatch {
8
8
  segments: AltScreenSearchSegment[];
9
9
  }
10
- export interface AltScreenSearchResult {
11
- matches: AltScreenSearchMatch[];
12
- changed: boolean;
13
- }
14
- /** Cache the searchable corpus and matches while rendered transcript lines remain unchanged. */
15
- export declare class AltScreenSearchIndex {
16
- private sourceLines;
17
- private corpus;
18
- private normalizedQuery;
19
- private matches;
20
- search(lines: readonly string[], query: string): AltScreenSearchResult;
21
- }
22
10
  export declare function findAltScreenSearchMatches(lines: readonly string[], query: string): AltScreenSearchMatch[];
23
11
  export declare function getAltScreenSearchMatchKey(match: AltScreenSearchMatch): string;
24
12
  export declare class AltScreenSearchComponent implements Component, Focusable {
25
13
  private readonly input;
26
14
  private readonly onQueryChange;
27
- private readonly navigationButtonStyle;
28
15
  private resultCount;
29
16
  private resultIndex;
30
- private previousButtonStart;
31
- private previousButtonEnd;
32
- private nextButtonStart;
33
- private nextButtonEnd;
34
- private hoveredNavigationDirection;
35
17
  private _focused;
36
- constructor(onQueryChange: (query: string) => void, navigationButtonStyle?: (text: string, hovered: boolean) => string);
18
+ constructor(onQueryChange: (query: string) => void);
37
19
  get focused(): boolean;
38
20
  set focused(value: boolean);
39
21
  setResult(index: number, count: number): void;
40
- getNavigationDirectionAt(row: number, column: number): -1 | 1 | undefined;
41
- setHoveredNavigationDirection(direction: -1 | 1 | undefined): boolean;
42
22
  handleInput(data: string): void;
43
23
  invalidate(): void;
44
24
  render(width: number): string[];
@@ -1 +1 @@
1
- {"version":3,"file":"alt-screen-search.d.ts","sourceRoot":"","sources":["../src/alt-screen-search.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAmBrD,MAAM,WAAW,sBAAsB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,EAAE,sBAAsB,EAAE,CAAC;CACnC;AAwHD,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,gGAAgG;AAChG,qBAAa,oBAAoB;IAChC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,OAAO,CAA8B;IAE7C,MAAM,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,qBAAqB,CAqBrE;CACD;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAG1G;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAI9E;AAED,qBAAa,wBAAyB,YAAW,SAAS,EAAE,SAAS;IACpE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAInB;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA6C;IACnF,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,mBAAmB,CAAM;IACjC,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,0BAA0B,CAAqB;IACvD,OAAO,CAAC,QAAQ,CAAS;IAEzB,YACC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACtC,qBAAqB,GAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,MAAuB,EAIlF;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5C;IAED,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,CAKxE;IAED,6BAA6B,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,GAAG,OAAO,CAIpE;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAK9B;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAkE9B;CACD"}
1
+ {"version":3,"file":"alt-screen-search.d.ts","sourceRoot":"","sources":["../src/alt-screen-search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAWrD,MAAM,WAAW,sBAAsB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,EAAE,sBAAsB,EAAE,CAAC;CACnC;AAkDD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,oBAAoB,EAAE,CA0B1G;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAI9E;AAED,qBAAa,wBAAyB,YAAW,SAAS,EAAE,SAAS;IACpE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,QAAQ,CAAS;IAEzB,YAAY,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EAEjD;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5C;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAK9B;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAe9B;CACD"}
@@ -1,82 +1,37 @@
1
1
  import { Input } from "./components/input.js";
2
- import { getKeybindings } from "./keybindings.js";
3
2
  import { getGraphemeSegmenter, stripTerminalSequences, truncateToWidth, visibleWidth } from "./utils.js";
4
3
  const segmenter = getGraphemeSegmenter();
5
- const PRINTABLE_ASCII = /^[\x20-\x7e]*$/;
4
+ function appendMappedText(text, span, corpus) {
5
+ corpus.text += text;
6
+ for (let index = 0; index < text.length; index++)
7
+ corpus.source.push(span);
8
+ }
6
9
  function buildSearchCorpus(lines) {
7
- const chunks = [];
8
- const spans = [];
9
- let textLength = 0;
10
+ const corpus = { text: "", source: [] };
10
11
  let pendingSeparator = false;
11
- const appendSeparator = () => {
12
- if (!pendingSeparator)
13
- return;
14
- chunks.push(" ");
15
- textLength += 1;
16
- pendingSeparator = false;
17
- };
18
12
  for (let row = 0; row < lines.length; row++) {
19
13
  const line = stripTerminalSequences(lines[row] ?? "");
20
14
  let column = 0;
21
- // Rendered transcripts are overwhelmingly ASCII. Index complete non-space
22
- // runs at once instead of segmenting and allocating one mapping per cell.
23
- if (PRINTABLE_ASCII.test(line)) {
24
- let index = 0;
25
- while (index < line.length) {
26
- if (line.charCodeAt(index) === 0x20) {
27
- if (textLength > 0)
28
- pendingSeparator = true;
29
- column += 1;
30
- index += 1;
31
- continue;
32
- }
33
- let end = index + 1;
34
- while (end < line.length && line.charCodeAt(end) !== 0x20)
35
- end += 1;
36
- appendSeparator();
37
- const text = line.slice(index, end);
38
- chunks.push(text);
39
- spans.push({
40
- textStart: textLength,
41
- textEnd: textLength + text.length,
42
- row,
43
- startCol: column,
44
- endCol: column + text.length,
45
- linearColumns: true,
46
- });
47
- textLength += text.length;
48
- column += text.length;
49
- index = end;
50
- }
51
- }
52
- else {
53
- for (const grapheme of segmenter.segment(line)) {
54
- const text = grapheme.segment;
55
- const width = visibleWidth(text);
56
- if (/^\s+$/u.test(text)) {
57
- if (textLength > 0)
58
- pendingSeparator = true;
59
- column += width;
60
- continue;
61
- }
62
- appendSeparator();
63
- chunks.push(text);
64
- spans.push({
65
- textStart: textLength,
66
- textEnd: textLength + text.length,
67
- row,
68
- startCol: column,
69
- endCol: column + width,
70
- linearColumns: false,
71
- });
72
- textLength += text.length;
15
+ for (const grapheme of segmenter.segment(line)) {
16
+ const text = grapheme.segment;
17
+ const width = visibleWidth(text);
18
+ if (/^\s+$/u.test(text)) {
19
+ if (corpus.text.length > 0)
20
+ pendingSeparator = true;
73
21
  column += width;
22
+ continue;
23
+ }
24
+ if (pendingSeparator) {
25
+ appendMappedText(" ", undefined, corpus);
26
+ pendingSeparator = false;
74
27
  }
28
+ appendMappedText(text, { row, startCol: column, endCol: column + width }, corpus);
29
+ column += width;
75
30
  }
76
- if (textLength > 0)
31
+ if (corpus.text.length > 0)
77
32
  pendingSeparator = true;
78
33
  }
79
- return { text: chunks.join(""), spans };
34
+ return corpus;
80
35
  }
81
36
  function normalizeQuery(query) {
82
37
  return query.replace(/\s+/gu, " ").trim();
@@ -84,96 +39,46 @@ function normalizeQuery(query) {
84
39
  function escapeRegExp(text) {
85
40
  return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
86
41
  }
87
- function findSearchCorpusMatches(corpus, normalizedQuery) {
42
+ export function findAltScreenSearchMatches(lines, query) {
43
+ const normalizedQuery = normalizeQuery(query);
88
44
  if (!normalizedQuery)
89
45
  return [];
46
+ const corpus = buildSearchCorpus(lines);
90
47
  const expression = new RegExp(escapeRegExp(normalizedQuery), "giu");
91
48
  const matches = [];
92
- let spanIndex = 0;
93
49
  for (const match of corpus.text.matchAll(expression)) {
94
50
  const start = match.index;
95
51
  const end = start + match[0].length;
96
- while (spanIndex < corpus.spans.length && corpus.spans[spanIndex].textEnd <= start)
97
- spanIndex += 1;
98
52
  const segments = [];
99
- for (let index = spanIndex; index < corpus.spans.length; index++) {
100
- const span = corpus.spans[index];
101
- if (span.textStart >= end)
102
- break;
103
- if (span.textEnd <= start)
53
+ for (let index = start; index < end; index++) {
54
+ const span = corpus.source[index];
55
+ if (!span)
104
56
  continue;
105
- const startCol = span.linearColumns
106
- ? span.startCol + Math.max(start, span.textStart) - span.textStart
107
- : span.startCol;
108
- const endCol = span.linearColumns ? span.startCol + Math.min(end, span.textEnd) - span.textStart : span.endCol;
109
57
  const previous = segments[segments.length - 1];
110
- if (previous && previous.row === span.row && startCol <= previous.endCol) {
111
- previous.endCol = Math.max(previous.endCol, endCol);
58
+ if (previous && previous.row === span.row && span.startCol <= previous.endCol) {
59
+ previous.endCol = Math.max(previous.endCol, span.endCol);
112
60
  }
113
61
  else {
114
- segments.push({ row: span.row, startCol, endCol });
62
+ segments.push({ ...span });
115
63
  }
116
64
  }
117
- while (spanIndex < corpus.spans.length && corpus.spans[spanIndex].textEnd <= end)
118
- spanIndex += 1;
119
65
  if (segments.length > 0)
120
66
  matches.push({ segments });
121
67
  }
122
68
  return matches;
123
69
  }
124
- /** Cache the searchable corpus and matches while rendered transcript lines remain unchanged. */
125
- export class AltScreenSearchIndex {
126
- constructor() {
127
- this.matches = [];
128
- }
129
- search(lines, query) {
130
- let sourceChanged = this.sourceLines?.length !== lines.length;
131
- if (!sourceChanged && this.sourceLines) {
132
- for (let index = 0; index < lines.length; index++) {
133
- if (this.sourceLines[index] === lines[index])
134
- continue;
135
- sourceChanged = true;
136
- break;
137
- }
138
- }
139
- if (sourceChanged || !this.corpus) {
140
- this.sourceLines = Array.from(lines);
141
- this.corpus = buildSearchCorpus(lines);
142
- }
143
- const normalizedQuery = normalizeQuery(query);
144
- const changed = sourceChanged || normalizedQuery !== this.normalizedQuery;
145
- if (changed) {
146
- this.normalizedQuery = normalizedQuery;
147
- this.matches = findSearchCorpusMatches(this.corpus, normalizedQuery);
148
- }
149
- return { matches: this.matches, changed };
150
- }
151
- }
152
- export function findAltScreenSearchMatches(lines, query) {
153
- const normalizedQuery = normalizeQuery(query);
154
- return normalizedQuery ? findSearchCorpusMatches(buildSearchCorpus(lines), normalizedQuery) : [];
155
- }
156
70
  export function getAltScreenSearchMatchKey(match) {
157
71
  const first = match.segments[0];
158
72
  const last = match.segments[match.segments.length - 1];
159
73
  return first && last ? `${first.row}:${first.startCol}:${last.row}:${last.endCol}` : "";
160
74
  }
161
75
  export class AltScreenSearchComponent {
162
- constructor(onQueryChange, navigationButtonStyle = (text) => text) {
163
- this.input = new Input({
164
- prompt: " ",
165
- placeholder: "Find in transcript",
166
- placeholderStyle: (text) => `\x1b[2m${text}\x1b[22m`,
167
- });
76
+ constructor(onQueryChange) {
77
+ this.input = new Input();
168
78
  this.resultCount = 0;
169
79
  this.resultIndex = -1;
170
- this.previousButtonStart = -1;
171
- this.previousButtonEnd = -1;
172
- this.nextButtonStart = -1;
173
- this.nextButtonEnd = -1;
174
80
  this._focused = false;
175
81
  this.onQueryChange = onQueryChange;
176
- this.navigationButtonStyle = navigationButtonStyle;
177
82
  }
178
83
  get focused() {
179
84
  return this._focused;
@@ -186,21 +91,6 @@ export class AltScreenSearchComponent {
186
91
  this.resultIndex = index;
187
92
  this.resultCount = count;
188
93
  }
189
- getNavigationDirectionAt(row, column) {
190
- if (row !== 2)
191
- return undefined;
192
- if (column >= this.previousButtonStart && column < this.previousButtonEnd)
193
- return -1;
194
- if (column >= this.nextButtonStart && column < this.nextButtonEnd)
195
- return 1;
196
- return undefined;
197
- }
198
- setHoveredNavigationDirection(direction) {
199
- if (direction === this.hoveredNavigationDirection)
200
- return false;
201
- this.hoveredNavigationDirection = direction;
202
- return true;
203
- }
204
94
  handleInput(data) {
205
95
  const previous = this.input.getValue();
206
96
  this.input.handleInput(data);
@@ -213,66 +103,19 @@ export class AltScreenSearchComponent {
213
103
  }
214
104
  render(width) {
215
105
  const safeWidth = Math.max(1, width);
216
- const innerWidth = Math.max(0, safeWidth - 2);
217
- const formatKey = (key) => key
218
- ? key
219
- .split("+")
220
- .map((part) => {
221
- if (process.platform === "darwin" && part.toLowerCase() === "alt")
222
- return "Option";
223
- return part.charAt(0).toUpperCase() + part.slice(1);
224
- })
225
- .join("+")
226
- : "Unbound";
227
- const keybindings = getKeybindings();
228
- const previousKey = formatKey(keybindings.getKeys("tui.altScreen.searchPrevious")[0]);
229
- const nextKey = formatKey(keybindings.getKeys("tui.altScreen.searchNext")[0]);
106
+ const label = " Find transcript";
230
107
  const query = this.input.getValue();
231
- const result = !query
108
+ const status = !query
232
109
  ? ""
233
110
  : this.resultCount === 0
234
- ? "No matches"
235
- : `${this.resultIndex + 1}/${this.resultCount}`;
236
- const resultSpace = Math.max(0, innerWidth - 3);
237
- const visibleResult = truncateToWidth(result, resultSpace, "");
238
- const resultText = visibleResult ? `\x1b[2m ${visibleResult} \x1b[22m` : "";
239
- const inputWidth = Math.max(0, innerWidth - visibleWidth(resultText));
240
- const inputLine = truncateToWidth(this.input.render(Math.max(1, inputWidth))[0] ?? "", inputWidth, "");
241
- const inputPadding = " ".repeat(Math.max(0, inputWidth - visibleWidth(inputLine)));
242
- const content = `${inputLine}${inputPadding}${resultText}`;
243
- let previousButton = `↑ ${previousKey}`;
244
- let nextButton = `↓ ${nextKey}`;
245
- let separator = " · ";
246
- const outerGapWidth = 1;
247
- const availableControlsWidth = Math.max(0, innerWidth - outerGapWidth * 2 - 1);
248
- let controlsWidth = visibleWidth(previousButton) + visibleWidth(separator) + visibleWidth(nextButton);
249
- if (controlsWidth > availableControlsWidth) {
250
- previousButton = "↑";
251
- nextButton = "↓";
252
- separator = " ";
253
- controlsWidth = visibleWidth(previousButton) + visibleWidth(separator) + visibleWidth(nextButton);
254
- }
255
- const showButtons = controlsWidth <= availableControlsWidth;
256
- const renderedButtons = showButtons
257
- ? this.navigationButtonStyle(previousButton, this.hoveredNavigationDirection === -1) +
258
- separator +
259
- this.navigationButtonStyle(nextButton, this.hoveredNavigationDirection === 1)
260
- : "";
261
- const outerGapsWidth = showButtons ? outerGapWidth * 2 : 0;
262
- const rightRuleWidth = renderedButtons && innerWidth > controlsWidth + outerGapsWidth ? 1 : 0;
263
- const leftRuleWidth = Math.max(0, innerWidth - (showButtons ? controlsWidth : 0) - outerGapsWidth - rightRuleWidth);
264
- const previousStart = 1 + leftRuleWidth + outerGapWidth;
265
- this.previousButtonStart = showButtons ? previousStart : -1;
266
- this.previousButtonEnd = showButtons ? previousStart + visibleWidth(previousButton) : -1;
267
- this.nextButtonStart = showButtons ? this.previousButtonEnd + visibleWidth(separator) : -1;
268
- this.nextButtonEnd = showButtons ? this.nextButtonStart + visibleWidth(nextButton) : -1;
269
- if (safeWidth === 1)
270
- return ["┌", "│", "└"];
271
- return [
272
- `┌${"─".repeat(innerWidth)}┐`,
273
- `│${content}│`,
274
- `└${"─".repeat(leftRuleWidth)}${renderedButtons ? " " : ""}${renderedButtons}${renderedButtons ? " " : ""}${"─".repeat(rightRuleWidth)}┘`,
275
- ];
111
+ ? "No matches "
112
+ : `${this.resultIndex + 1}/${this.resultCount} `;
113
+ const labelWidth = visibleWidth(label);
114
+ const statusWidth = visibleWidth(status);
115
+ const gap = " ".repeat(Math.max(1, safeWidth - labelWidth - statusWidth));
116
+ const title = truncateToWidth(`${label}${gap}${status}`, safeWidth, "");
117
+ const padding = " ".repeat(Math.max(0, safeWidth - visibleWidth(title)));
118
+ return [`\x1b[7m${title}${padding}\x1b[27m`, ...this.input.render(safeWidth)];
276
119
  }
277
120
  }
278
121
  //# sourceMappingURL=alt-screen-search.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"alt-screen-search.js","sourceRoot":"","sources":["../src/alt-screen-search.ts"],"names":[],"mappings":"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","sourcesContent":["import { Input } from \"./components/input.ts\";\nimport { getKeybindings } from \"./keybindings.ts\";\nimport type { Component, Focusable } from \"./tui.ts\";\nimport { getGraphemeSegmenter, stripTerminalSequences, truncateToWidth, visibleWidth } from \"./utils.ts\";\n\nconst segmenter = getGraphemeSegmenter();\n\ninterface SearchSourceSpan {\n\ttextStart: number;\n\ttextEnd: number;\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n\tlinearColumns: boolean;\n}\n\ninterface SearchCorpus {\n\ttext: string;\n\tspans: SearchSourceSpan[];\n}\n\nexport interface AltScreenSearchSegment {\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n}\n\nexport interface AltScreenSearchMatch {\n\tsegments: AltScreenSearchSegment[];\n}\n\nconst PRINTABLE_ASCII = /^[\\x20-\\x7e]*$/;\n\nfunction buildSearchCorpus(lines: readonly string[]): SearchCorpus {\n\tconst chunks: string[] = [];\n\tconst spans: SearchSourceSpan[] = [];\n\tlet textLength = 0;\n\tlet pendingSeparator = false;\n\n\tconst appendSeparator = (): void => {\n\t\tif (!pendingSeparator) return;\n\t\tchunks.push(\" \");\n\t\ttextLength += 1;\n\t\tpendingSeparator = false;\n\t};\n\n\tfor (let row = 0; row < lines.length; row++) {\n\t\tconst line = stripTerminalSequences(lines[row] ?? \"\");\n\t\tlet column = 0;\n\n\t\t// Rendered transcripts are overwhelmingly ASCII. Index complete non-space\n\t\t// runs at once instead of segmenting and allocating one mapping per cell.\n\t\tif (PRINTABLE_ASCII.test(line)) {\n\t\t\tlet index = 0;\n\t\t\twhile (index < line.length) {\n\t\t\t\tif (line.charCodeAt(index) === 0x20) {\n\t\t\t\t\tif (textLength > 0) pendingSeparator = true;\n\t\t\t\t\tcolumn += 1;\n\t\t\t\t\tindex += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tlet end = index + 1;\n\t\t\t\twhile (end < line.length && line.charCodeAt(end) !== 0x20) end += 1;\n\t\t\t\tappendSeparator();\n\t\t\t\tconst text = line.slice(index, end);\n\t\t\t\tchunks.push(text);\n\t\t\t\tspans.push({\n\t\t\t\t\ttextStart: textLength,\n\t\t\t\t\ttextEnd: textLength + text.length,\n\t\t\t\t\trow,\n\t\t\t\t\tstartCol: column,\n\t\t\t\t\tendCol: column + text.length,\n\t\t\t\t\tlinearColumns: true,\n\t\t\t\t});\n\t\t\t\ttextLength += text.length;\n\t\t\t\tcolumn += text.length;\n\t\t\t\tindex = end;\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const grapheme of segmenter.segment(line)) {\n\t\t\t\tconst text = grapheme.segment;\n\t\t\t\tconst width = visibleWidth(text);\n\t\t\t\tif (/^\\s+$/u.test(text)) {\n\t\t\t\t\tif (textLength > 0) pendingSeparator = true;\n\t\t\t\t\tcolumn += width;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tappendSeparator();\n\t\t\t\tchunks.push(text);\n\t\t\t\tspans.push({\n\t\t\t\t\ttextStart: textLength,\n\t\t\t\t\ttextEnd: textLength + text.length,\n\t\t\t\t\trow,\n\t\t\t\t\tstartCol: column,\n\t\t\t\t\tendCol: column + width,\n\t\t\t\t\tlinearColumns: false,\n\t\t\t\t});\n\t\t\t\ttextLength += text.length;\n\t\t\t\tcolumn += width;\n\t\t\t}\n\t\t}\n\t\tif (textLength > 0) pendingSeparator = true;\n\t}\n\n\treturn { text: chunks.join(\"\"), spans };\n}\n\nfunction normalizeQuery(query: string): string {\n\treturn query.replace(/\\s+/gu, \" \").trim();\n}\n\nfunction escapeRegExp(text: string): string {\n\treturn text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction findSearchCorpusMatches(corpus: SearchCorpus, normalizedQuery: string): AltScreenSearchMatch[] {\n\tif (!normalizedQuery) return [];\n\tconst expression = new RegExp(escapeRegExp(normalizedQuery), \"giu\");\n\tconst matches: AltScreenSearchMatch[] = [];\n\tlet spanIndex = 0;\n\n\tfor (const match of corpus.text.matchAll(expression)) {\n\t\tconst start = match.index;\n\t\tconst end = start + match[0].length;\n\t\twhile (spanIndex < corpus.spans.length && corpus.spans[spanIndex]!.textEnd <= start) spanIndex += 1;\n\n\t\tconst segments: AltScreenSearchSegment[] = [];\n\t\tfor (let index = spanIndex; index < corpus.spans.length; index++) {\n\t\t\tconst span = corpus.spans[index]!;\n\t\t\tif (span.textStart >= end) break;\n\t\t\tif (span.textEnd <= start) continue;\n\t\t\tconst startCol = span.linearColumns\n\t\t\t\t? span.startCol + Math.max(start, span.textStart) - span.textStart\n\t\t\t\t: span.startCol;\n\t\t\tconst endCol = span.linearColumns ? span.startCol + Math.min(end, span.textEnd) - span.textStart : span.endCol;\n\t\t\tconst previous = segments[segments.length - 1];\n\t\t\tif (previous && previous.row === span.row && startCol <= previous.endCol) {\n\t\t\t\tprevious.endCol = Math.max(previous.endCol, endCol);\n\t\t\t} else {\n\t\t\t\tsegments.push({ row: span.row, startCol, endCol });\n\t\t\t}\n\t\t}\n\t\twhile (spanIndex < corpus.spans.length && corpus.spans[spanIndex]!.textEnd <= end) spanIndex += 1;\n\t\tif (segments.length > 0) matches.push({ segments });\n\t}\n\n\treturn matches;\n}\n\nexport interface AltScreenSearchResult {\n\tmatches: AltScreenSearchMatch[];\n\tchanged: boolean;\n}\n\n/** Cache the searchable corpus and matches while rendered transcript lines remain unchanged. */\nexport class AltScreenSearchIndex {\n\tprivate sourceLines: string[] | undefined;\n\tprivate corpus: SearchCorpus | undefined;\n\tprivate normalizedQuery: string | undefined;\n\tprivate matches: AltScreenSearchMatch[] = [];\n\n\tsearch(lines: readonly string[], query: string): AltScreenSearchResult {\n\t\tlet sourceChanged = this.sourceLines?.length !== lines.length;\n\t\tif (!sourceChanged && this.sourceLines) {\n\t\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\t\tif (this.sourceLines[index] === lines[index]) continue;\n\t\t\t\tsourceChanged = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (sourceChanged || !this.corpus) {\n\t\t\tthis.sourceLines = Array.from(lines);\n\t\t\tthis.corpus = buildSearchCorpus(lines);\n\t\t}\n\n\t\tconst normalizedQuery = normalizeQuery(query);\n\t\tconst changed = sourceChanged || normalizedQuery !== this.normalizedQuery;\n\t\tif (changed) {\n\t\t\tthis.normalizedQuery = normalizedQuery;\n\t\t\tthis.matches = findSearchCorpusMatches(this.corpus, normalizedQuery);\n\t\t}\n\t\treturn { matches: this.matches, changed };\n\t}\n}\n\nexport function findAltScreenSearchMatches(lines: readonly string[], query: string): AltScreenSearchMatch[] {\n\tconst normalizedQuery = normalizeQuery(query);\n\treturn normalizedQuery ? findSearchCorpusMatches(buildSearchCorpus(lines), normalizedQuery) : [];\n}\n\nexport function getAltScreenSearchMatchKey(match: AltScreenSearchMatch): string {\n\tconst first = match.segments[0];\n\tconst last = match.segments[match.segments.length - 1];\n\treturn first && last ? `${first.row}:${first.startCol}:${last.row}:${last.endCol}` : \"\";\n}\n\nexport class AltScreenSearchComponent implements Component, Focusable {\n\tprivate readonly input = new Input({\n\t\tprompt: \" \",\n\t\tplaceholder: \"Find in transcript\",\n\t\tplaceholderStyle: (text) => `\\x1b[2m${text}\\x1b[22m`,\n\t});\n\tprivate readonly onQueryChange: (query: string) => void;\n\tprivate readonly navigationButtonStyle: (text: string, hovered: boolean) => string;\n\tprivate resultCount = 0;\n\tprivate resultIndex = -1;\n\tprivate previousButtonStart = -1;\n\tprivate previousButtonEnd = -1;\n\tprivate nextButtonStart = -1;\n\tprivate nextButtonEnd = -1;\n\tprivate hoveredNavigationDirection: -1 | 1 | undefined;\n\tprivate _focused = false;\n\n\tconstructor(\n\t\tonQueryChange: (query: string) => void,\n\t\tnavigationButtonStyle: (text: string, hovered: boolean) => string = (text) => text,\n\t) {\n\t\tthis.onQueryChange = onQueryChange;\n\t\tthis.navigationButtonStyle = navigationButtonStyle;\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.input.focused = value;\n\t}\n\n\tsetResult(index: number, count: number): void {\n\t\tthis.resultIndex = index;\n\t\tthis.resultCount = count;\n\t}\n\n\tgetNavigationDirectionAt(row: number, column: number): -1 | 1 | undefined {\n\t\tif (row !== 2) return undefined;\n\t\tif (column >= this.previousButtonStart && column < this.previousButtonEnd) return -1;\n\t\tif (column >= this.nextButtonStart && column < this.nextButtonEnd) return 1;\n\t\treturn undefined;\n\t}\n\n\tsetHoveredNavigationDirection(direction: -1 | 1 | undefined): boolean {\n\t\tif (direction === this.hoveredNavigationDirection) return false;\n\t\tthis.hoveredNavigationDirection = direction;\n\t\treturn true;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst previous = this.input.getValue();\n\t\tthis.input.handleInput(data);\n\t\tconst query = this.input.getValue();\n\t\tif (query !== previous) this.onQueryChange(query);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.input.invalidate();\n\t}\n\n\trender(width: number): string[] {\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst innerWidth = Math.max(0, safeWidth - 2);\n\t\tconst formatKey = (key: string | undefined): string =>\n\t\t\tkey\n\t\t\t\t? key\n\t\t\t\t\t\t.split(\"+\")\n\t\t\t\t\t\t.map((part) => {\n\t\t\t\t\t\t\tif (process.platform === \"darwin\" && part.toLowerCase() === \"alt\") return \"Option\";\n\t\t\t\t\t\t\treturn part.charAt(0).toUpperCase() + part.slice(1);\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.join(\"+\")\n\t\t\t\t: \"Unbound\";\n\t\tconst keybindings = getKeybindings();\n\t\tconst previousKey = formatKey(keybindings.getKeys(\"tui.altScreen.searchPrevious\")[0]);\n\t\tconst nextKey = formatKey(keybindings.getKeys(\"tui.altScreen.searchNext\")[0]);\n\t\tconst query = this.input.getValue();\n\t\tconst result = !query\n\t\t\t? \"\"\n\t\t\t: this.resultCount === 0\n\t\t\t\t? \"No matches\"\n\t\t\t\t: `${this.resultIndex + 1}/${this.resultCount}`;\n\t\tconst resultSpace = Math.max(0, innerWidth - 3);\n\t\tconst visibleResult = truncateToWidth(result, resultSpace, \"\");\n\t\tconst resultText = visibleResult ? `\\x1b[2m ${visibleResult} \\x1b[22m` : \"\";\n\t\tconst inputWidth = Math.max(0, innerWidth - visibleWidth(resultText));\n\t\tconst inputLine = truncateToWidth(this.input.render(Math.max(1, inputWidth))[0] ?? \"\", inputWidth, \"\");\n\t\tconst inputPadding = \" \".repeat(Math.max(0, inputWidth - visibleWidth(inputLine)));\n\t\tconst content = `${inputLine}${inputPadding}${resultText}`;\n\n\t\tlet previousButton = `↑ ${previousKey}`;\n\t\tlet nextButton = `↓ ${nextKey}`;\n\t\tlet separator = \" · \";\n\t\tconst outerGapWidth = 1;\n\t\tconst availableControlsWidth = Math.max(0, innerWidth - outerGapWidth * 2 - 1);\n\t\tlet controlsWidth = visibleWidth(previousButton) + visibleWidth(separator) + visibleWidth(nextButton);\n\t\tif (controlsWidth > availableControlsWidth) {\n\t\t\tpreviousButton = \"↑\";\n\t\t\tnextButton = \"↓\";\n\t\t\tseparator = \" \";\n\t\t\tcontrolsWidth = visibleWidth(previousButton) + visibleWidth(separator) + visibleWidth(nextButton);\n\t\t}\n\t\tconst showButtons = controlsWidth <= availableControlsWidth;\n\t\tconst renderedButtons = showButtons\n\t\t\t? this.navigationButtonStyle(previousButton, this.hoveredNavigationDirection === -1) +\n\t\t\t\tseparator +\n\t\t\t\tthis.navigationButtonStyle(nextButton, this.hoveredNavigationDirection === 1)\n\t\t\t: \"\";\n\t\tconst outerGapsWidth = showButtons ? outerGapWidth * 2 : 0;\n\t\tconst rightRuleWidth = renderedButtons && innerWidth > controlsWidth + outerGapsWidth ? 1 : 0;\n\t\tconst leftRuleWidth = Math.max(\n\t\t\t0,\n\t\t\tinnerWidth - (showButtons ? controlsWidth : 0) - outerGapsWidth - rightRuleWidth,\n\t\t);\n\t\tconst previousStart = 1 + leftRuleWidth + outerGapWidth;\n\t\tthis.previousButtonStart = showButtons ? previousStart : -1;\n\t\tthis.previousButtonEnd = showButtons ? previousStart + visibleWidth(previousButton) : -1;\n\t\tthis.nextButtonStart = showButtons ? this.previousButtonEnd + visibleWidth(separator) : -1;\n\t\tthis.nextButtonEnd = showButtons ? this.nextButtonStart + visibleWidth(nextButton) : -1;\n\n\t\tif (safeWidth === 1) return [\"┌\", \"│\", \"└\"];\n\t\treturn [\n\t\t\t`┌${\"─\".repeat(innerWidth)}┐`,\n\t\t\t`│${content}│`,\n\t\t\t`└${\"─\".repeat(leftRuleWidth)}${renderedButtons ? \" \" : \"\"}${renderedButtons}${renderedButtons ? \" \" : \"\"}${\"─\".repeat(rightRuleWidth)}┘`,\n\t\t];\n\t}\n}\n"]}
1
+ {"version":3,"file":"alt-screen-search.js","sourceRoot":"","sources":["../src/alt-screen-search.ts"],"names":[],"mappings":"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","sourcesContent":["import { Input } from \"./components/input.ts\";\nimport type { Component, Focusable } from \"./tui.ts\";\nimport { getGraphemeSegmenter, stripTerminalSequences, truncateToWidth, visibleWidth } from \"./utils.ts\";\n\nconst segmenter = getGraphemeSegmenter();\n\ninterface SearchSourceSpan {\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n}\n\nexport interface AltScreenSearchSegment {\n\trow: number;\n\tstartCol: number;\n\tendCol: number;\n}\n\nexport interface AltScreenSearchMatch {\n\tsegments: AltScreenSearchSegment[];\n}\n\nfunction appendMappedText(\n\ttext: string,\n\tspan: SearchSourceSpan | undefined,\n\tcorpus: { text: string; source: Array<SearchSourceSpan | undefined> },\n): void {\n\tcorpus.text += text;\n\tfor (let index = 0; index < text.length; index++) corpus.source.push(span);\n}\n\nfunction buildSearchCorpus(lines: readonly string[]): {\n\ttext: string;\n\tsource: Array<SearchSourceSpan | undefined>;\n} {\n\tconst corpus: { text: string; source: Array<SearchSourceSpan | undefined> } = { text: \"\", source: [] };\n\tlet pendingSeparator = false;\n\n\tfor (let row = 0; row < lines.length; row++) {\n\t\tconst line = stripTerminalSequences(lines[row] ?? \"\");\n\t\tlet column = 0;\n\t\tfor (const grapheme of segmenter.segment(line)) {\n\t\t\tconst text = grapheme.segment;\n\t\t\tconst width = visibleWidth(text);\n\t\t\tif (/^\\s+$/u.test(text)) {\n\t\t\t\tif (corpus.text.length > 0) pendingSeparator = true;\n\t\t\t\tcolumn += width;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (pendingSeparator) {\n\t\t\t\tappendMappedText(\" \", undefined, corpus);\n\t\t\t\tpendingSeparator = false;\n\t\t\t}\n\t\t\tappendMappedText(text, { row, startCol: column, endCol: column + width }, corpus);\n\t\t\tcolumn += width;\n\t\t}\n\t\tif (corpus.text.length > 0) pendingSeparator = true;\n\t}\n\n\treturn corpus;\n}\n\nfunction normalizeQuery(query: string): string {\n\treturn query.replace(/\\s+/gu, \" \").trim();\n}\n\nfunction escapeRegExp(text: string): string {\n\treturn text.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nexport function findAltScreenSearchMatches(lines: readonly string[], query: string): AltScreenSearchMatch[] {\n\tconst normalizedQuery = normalizeQuery(query);\n\tif (!normalizedQuery) return [];\n\n\tconst corpus = buildSearchCorpus(lines);\n\tconst expression = new RegExp(escapeRegExp(normalizedQuery), \"giu\");\n\tconst matches: AltScreenSearchMatch[] = [];\n\n\tfor (const match of corpus.text.matchAll(expression)) {\n\t\tconst start = match.index;\n\t\tconst end = start + match[0].length;\n\t\tconst segments: AltScreenSearchSegment[] = [];\n\t\tfor (let index = start; index < end; index++) {\n\t\t\tconst span = corpus.source[index];\n\t\t\tif (!span) continue;\n\t\t\tconst previous = segments[segments.length - 1];\n\t\t\tif (previous && previous.row === span.row && span.startCol <= previous.endCol) {\n\t\t\t\tprevious.endCol = Math.max(previous.endCol, span.endCol);\n\t\t\t} else {\n\t\t\t\tsegments.push({ ...span });\n\t\t\t}\n\t\t}\n\t\tif (segments.length > 0) matches.push({ segments });\n\t}\n\n\treturn matches;\n}\n\nexport function getAltScreenSearchMatchKey(match: AltScreenSearchMatch): string {\n\tconst first = match.segments[0];\n\tconst last = match.segments[match.segments.length - 1];\n\treturn first && last ? `${first.row}:${first.startCol}:${last.row}:${last.endCol}` : \"\";\n}\n\nexport class AltScreenSearchComponent implements Component, Focusable {\n\tprivate readonly input = new Input();\n\tprivate readonly onQueryChange: (query: string) => void;\n\tprivate resultCount = 0;\n\tprivate resultIndex = -1;\n\tprivate _focused = false;\n\n\tconstructor(onQueryChange: (query: string) => void) {\n\t\tthis.onQueryChange = onQueryChange;\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.input.focused = value;\n\t}\n\n\tsetResult(index: number, count: number): void {\n\t\tthis.resultIndex = index;\n\t\tthis.resultCount = count;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst previous = this.input.getValue();\n\t\tthis.input.handleInput(data);\n\t\tconst query = this.input.getValue();\n\t\tif (query !== previous) this.onQueryChange(query);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.input.invalidate();\n\t}\n\n\trender(width: number): string[] {\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst label = \" Find transcript\";\n\t\tconst query = this.input.getValue();\n\t\tconst status = !query\n\t\t\t? \"\"\n\t\t\t: this.resultCount === 0\n\t\t\t\t? \"No matches \"\n\t\t\t\t: `${this.resultIndex + 1}/${this.resultCount} `;\n\t\tconst labelWidth = visibleWidth(label);\n\t\tconst statusWidth = visibleWidth(status);\n\t\tconst gap = \" \".repeat(Math.max(1, safeWidth - labelWidth - statusWidth));\n\t\tconst title = truncateToWidth(`${label}${gap}${status}`, safeWidth, \"\");\n\t\tconst padding = \" \".repeat(Math.max(0, safeWidth - visibleWidth(title)));\n\t\treturn [`\\x1b[7m${title}${padding}\\x1b[27m`, ...this.input.render(safeWidth)];\n\t}\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { type Component, type TuiMouseDispatchResult, type TuiMouseEvent } from "../tui.ts";
1
+ import type { Component } from "../tui.ts";
2
2
  /**
3
3
  * Box component - a container that applies padding and background to all children
4
4
  */
@@ -9,7 +9,6 @@ export declare class Box implements Component {
9
9
  private bgFn?;
10
10
  private disposed;
11
11
  private cache?;
12
- private mouseLayout?;
13
12
  constructor(paddingX?: number, paddingY?: number, bgFn?: (text: string) => string);
14
13
  addChild(component: Component): void;
15
14
  removeChild(component: Component): void;
@@ -20,7 +19,6 @@ export declare class Box implements Component {
20
19
  private invalidateCache;
21
20
  private matchCache;
22
21
  invalidate(): void;
23
- handleMouse(event: TuiMouseEvent): TuiMouseDispatchResult | undefined;
24
22
  render(width: number): string[];
25
23
  private applyBg;
26
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"box.d.ts","sourceRoot":"","sources":["../../src/components/box.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAsB,KAAK,sBAAsB,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAUhH;;GAEG;AACH,qBAAa,GAAI,YAAW,SAAS;IACpC,QAAQ,EAAE,SAAS,EAAE,CAAM;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAC,CAA2B;IACxC,OAAO,CAAC,QAAQ,CAAS;IAGzB,OAAO,CAAC,KAAK,CAAC,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAC,CAA+E;IAEnG,YAAY,QAAQ,SAAI,EAAE,QAAQ,SAAI,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAItE;IAED,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAGnC;IAED,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAOtC;IAED,KAAK,IAAI,IAAI,CAMZ;IAED,SAAS,IAAI,IAAI,CAGhB;IAED,OAAO,IAAI,IAAI,CAOd;IAED,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAG7C;IAED,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,UAAU;IAWlB,UAAU,IAAI,IAAI,CAKjB;IAED,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,sBAAsB,GAAG,SAAS,CAwBpE;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAsD9B;IAED,OAAO,CAAC,OAAO;CAUf"}
1
+ {"version":3,"file":"box.d.ts","sourceRoot":"","sources":["../../src/components/box.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAU3C;;GAEG;AACH,qBAAa,GAAI,YAAW,SAAS;IACpC,QAAQ,EAAE,SAAS,EAAE,CAAM;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAC,CAA2B;IACxC,OAAO,CAAC,QAAQ,CAAS;IAGzB,OAAO,CAAC,KAAK,CAAC,CAAc;IAE5B,YAAY,QAAQ,SAAI,EAAE,QAAQ,SAAI,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAItE;IAED,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAGnC;IAED,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAOtC;IAED,KAAK,IAAI,IAAI,CAMZ;IAED,SAAS,IAAI,IAAI,CAGhB;IAED,OAAO,IAAI,IAAI,CAOd;IAED,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAG7C;IAED,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,UAAU;IAWlB,UAAU,IAAI,IAAI,CAKjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAmD9B;IAED,OAAO,CAAC,OAAO;CAUf"}