@earendil-works/pi-tui 0.87.1 → 0.99.0

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 (123) hide show
  1. package/README.md +41 -3
  2. package/dist/alt-screen-search.d.ts.map +1 -1
  3. package/dist/alt-screen-search.js.map +1 -1
  4. package/dist/autocomplete.d.ts +1 -1
  5. package/dist/autocomplete.d.ts.map +1 -1
  6. package/dist/autocomplete.js +28 -8
  7. package/dist/autocomplete.js.map +1 -1
  8. package/dist/colors.d.ts +73 -0
  9. package/dist/colors.d.ts.map +1 -0
  10. package/dist/colors.js +273 -0
  11. package/dist/colors.js.map +1 -0
  12. package/dist/components/alt-screen-flash.d.ts.map +1 -1
  13. package/dist/components/alt-screen-flash.js.map +1 -1
  14. package/dist/components/box.d.ts.map +1 -1
  15. package/dist/components/box.js +8 -8
  16. package/dist/components/box.js.map +1 -1
  17. package/dist/components/cancellable-loader.d.ts.map +1 -1
  18. package/dist/components/cancellable-loader.js.map +1 -1
  19. package/dist/components/editor.d.ts.map +1 -1
  20. package/dist/components/editor.js +4 -2
  21. package/dist/components/editor.js.map +1 -1
  22. package/dist/components/h-stack.d.ts.map +1 -1
  23. package/dist/components/h-stack.js.map +1 -1
  24. package/dist/components/image.d.ts.map +1 -1
  25. package/dist/components/image.js.map +1 -1
  26. package/dist/components/input.d.ts.map +1 -1
  27. package/dist/components/input.js.map +1 -1
  28. package/dist/components/loader.d.ts.map +1 -1
  29. package/dist/components/loader.js.map +1 -1
  30. package/dist/components/markdown.d.ts +1 -0
  31. package/dist/components/markdown.d.ts.map +1 -1
  32. package/dist/components/markdown.js +8 -2
  33. package/dist/components/markdown.js.map +1 -1
  34. package/dist/components/mouse-region.d.ts.map +1 -1
  35. package/dist/components/mouse-region.js.map +1 -1
  36. package/dist/components/scroll-view.d.ts.map +1 -1
  37. package/dist/components/scroll-view.js.map +1 -1
  38. package/dist/components/select-list.d.ts.map +1 -1
  39. package/dist/components/select-list.js.map +1 -1
  40. package/dist/components/settings-list.d.ts.map +1 -1
  41. package/dist/components/settings-list.js.map +1 -1
  42. package/dist/components/spacer.d.ts.map +1 -1
  43. package/dist/components/spacer.js.map +1 -1
  44. package/dist/components/stack.d.ts.map +1 -1
  45. package/dist/components/stack.js.map +1 -1
  46. package/dist/components/text.d.ts.map +1 -1
  47. package/dist/components/text.js.map +1 -1
  48. package/dist/components/truncated-text.d.ts.map +1 -1
  49. package/dist/components/truncated-text.js.map +1 -1
  50. package/dist/components/v-stack.d.ts.map +1 -1
  51. package/dist/components/v-stack.js.map +1 -1
  52. package/dist/editor-component.d.ts.map +1 -1
  53. package/dist/fuzzy.d.ts.map +1 -1
  54. package/dist/fuzzy.js.map +1 -1
  55. package/dist/index.d.ts +5 -2
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +5 -2
  58. package/dist/index.js.map +1 -1
  59. package/dist/keybindings.d.ts.map +1 -1
  60. package/dist/keybindings.js.map +1 -1
  61. package/dist/keys.d.ts.map +1 -1
  62. package/dist/keys.js.map +1 -1
  63. package/dist/kill-ring.d.ts.map +1 -1
  64. package/dist/kill-ring.js.map +1 -1
  65. package/dist/latex.d.ts.map +1 -1
  66. package/dist/latex.js.map +1 -1
  67. package/dist/layout-node.d.ts.map +1 -1
  68. package/dist/layout-node.js.map +1 -1
  69. package/dist/layout.d.ts.map +1 -1
  70. package/dist/layout.js.map +1 -1
  71. package/dist/native-modifiers.d.ts.map +1 -1
  72. package/dist/native-modifiers.js.map +1 -1
  73. package/dist/native-module-path.d.ts.map +1 -1
  74. package/dist/native-module-path.js.map +1 -1
  75. package/dist/native-platform.d.ts +2 -0
  76. package/dist/native-platform.d.ts.map +1 -1
  77. package/dist/native-platform.js.map +1 -1
  78. package/dist/oklab.d.ts +43 -0
  79. package/dist/oklab.d.ts.map +1 -0
  80. package/dist/oklab.js +210 -0
  81. package/dist/oklab.js.map +1 -0
  82. package/dist/stdin-buffer.d.ts.map +1 -1
  83. package/dist/stdin-buffer.js.map +1 -1
  84. package/dist/terminal-colors.d.ts +19 -2
  85. package/dist/terminal-colors.d.ts.map +1 -1
  86. package/dist/terminal-colors.js +12 -7
  87. package/dist/terminal-colors.js.map +1 -1
  88. package/dist/terminal-image.d.ts +3 -1
  89. package/dist/terminal-image.d.ts.map +1 -1
  90. package/dist/terminal-image.js +33 -9
  91. package/dist/terminal-image.js.map +1 -1
  92. package/dist/terminal.d.ts +3 -0
  93. package/dist/terminal.d.ts.map +1 -1
  94. package/dist/terminal.js +15 -8
  95. package/dist/terminal.js.map +1 -1
  96. package/dist/tui-alt-screen.d.ts +8 -4
  97. package/dist/tui-alt-screen.d.ts.map +1 -1
  98. package/dist/tui-alt-screen.js +13 -12
  99. package/dist/tui-alt-screen.js.map +1 -1
  100. package/dist/tui-main-screen.d.ts.map +1 -1
  101. package/dist/tui-main-screen.js.map +1 -1
  102. package/dist/tui.d.ts +23 -22
  103. package/dist/tui.d.ts.map +1 -1
  104. package/dist/tui.js +85 -66
  105. package/dist/tui.js.map +1 -1
  106. package/dist/undo-stack.d.ts.map +1 -1
  107. package/dist/undo-stack.js.map +1 -1
  108. package/dist/utils.d.ts.map +1 -1
  109. package/dist/utils.js +99 -55
  110. package/dist/utils.js.map +1 -1
  111. package/dist/wheel-scroll.d.ts +23 -0
  112. package/dist/wheel-scroll.d.ts.map +1 -0
  113. package/dist/wheel-scroll.js +73 -0
  114. package/dist/wheel-scroll.js.map +1 -0
  115. package/dist/word-navigation.d.ts.map +1 -1
  116. package/dist/word-navigation.js.map +1 -1
  117. package/native/clipboard.h +40 -2
  118. package/native/darwin/README.md +1 -1
  119. package/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  120. package/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  121. package/native/darwin/src/darwin-platform.m +39 -0
  122. package/native/napi.h +2 -0
  123. package/package.json +2 -2
package/dist/tui.js CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { performance } from "node:perf_hooks";
5
5
  import { isKeyRelease, matchesKey } from "./keys.js";
6
- import { isOsc11BackgroundColorResponse, parseOsc11BackgroundColor, parseTerminalColorSchemeReport, } from "./terminal-colors.js";
6
+ import { parseOscColorResponse, parseTerminalColorSchemeReport, } from "./terminal-colors.js";
7
7
  import { getCapabilities, isImageLine, setCellDimensions } from "./terminal-image.js";
8
8
  import { extractSegments, normalizeTerminalOutput, sliceByColumn, sliceWithWidth, visibleWidth } from "./utils.js";
9
9
  /**
@@ -14,8 +14,13 @@ export function dispatchMouseEvent(component, event) {
14
14
  const result = component.handleMouse?.(event);
15
15
  if (!result)
16
16
  return undefined;
17
- if ("target" in result)
18
- return result;
17
+ if ("target" in result) {
18
+ // The component forwarded the event to a child it hosts. Like a delegating container, it routes
19
+ // keys to that child itself, so it keeps keyboard focus. Focusing the child directly would leave
20
+ // focus on a detached component once the host removes it, e.g. a closed settings submenu.
21
+ const forwarded = result;
22
+ return forwarded.focus && component.handleInput ? { ...forwarded, focusTarget: component } : forwarded;
23
+ }
19
24
  if (!result.handled && !result.capture && !result.focus)
20
25
  return undefined;
21
26
  return {
@@ -41,6 +46,16 @@ export function retargetMouseEvent(event, target) {
41
46
  height: target.height,
42
47
  };
43
48
  }
49
+ const TERMINAL_PALETTE_SIZE = 16;
50
+ /** OSC 10 and 11 plus OSC 4 for every palette color. */
51
+ const TERMINAL_COLOR_REPLY_COUNT = 2 + TERMINAL_PALETTE_SIZE;
52
+ /**
53
+ * Default colors, palette colors 0-15, and a trailing primary device attributes (DA1) request.
54
+ * Every terminal answers DA1 and terminals answer in order, so the DA1 reply marks the end of
55
+ * the color replies, including for terminals that ignore the color queries.
56
+ */
57
+ const TERMINAL_COLOR_QUERY = `\x1b]10;?\x07\x1b]11;?\x07${Array.from({ length: TERMINAL_PALETTE_SIZE }, (_, index) => `\x1b]4;${index};?\x07`).join("")}\x1b[c`;
58
+ const DEVICE_ATTRIBUTES_RESPONSE_PATTERN = /^\x1b\[\?[\d;]*c$/;
44
59
  /** Type guard to check if a component implements Focusable */
45
60
  export function isFocusable(component) {
46
61
  return component !== null && "focused" in component;
@@ -171,8 +186,11 @@ export class TuiBase extends Container {
171
186
  clearOnShrink = false;
172
187
  fullRedrawCount = 0;
173
188
  stopped = false;
174
- pendingOsc11BackgroundReplies = 0;
175
- pendingOsc11BackgroundQueries = [];
189
+ /**
190
+ * Color queries waiting for their DA1 reply, oldest first. Terminals answer in order, so color
191
+ * replies belong to the oldest one. Queries stay here after a timeout to collect late replies.
192
+ */
193
+ pendingTerminalColorQueries = [];
176
194
  terminalColorSchemeListeners = new Set();
177
195
  terminalColorSchemeNotificationsEnabled = false;
178
196
  /** Directory for debug/crash logs. When undefined, debug logging is disabled and crash dumps fall back to the OS temp directory. */
@@ -209,7 +227,7 @@ export class TuiBase extends Container {
209
227
  return;
210
228
  this.showHardwareCursor = enabled;
211
229
  if (!enabled) {
212
- this.terminal.hideCursor();
230
+ this.hideTerminalCursor();
213
231
  }
214
232
  this.requestRender();
215
233
  }
@@ -357,7 +375,7 @@ export class TuiBase extends Container {
357
375
  if (!options?.nonCapturing && this.isOverlayVisible(entry)) {
358
376
  this.setFocus(component);
359
377
  }
360
- this.terminal.hideCursor();
378
+ this.hideTerminalCursor();
361
379
  this.requestRender();
362
380
  // Return handle for controlling this overlay
363
381
  return {
@@ -373,7 +391,7 @@ export class TuiBase extends Container {
373
391
  this.setFocus(topVisible?.component ?? entry.preFocus);
374
392
  }
375
393
  if (this.overlayStack.length === 0)
376
- this.terminal.hideCursor();
394
+ this.hideTerminalCursor();
377
395
  this.requestRender();
378
396
  }
379
397
  },
@@ -460,9 +478,14 @@ export class TuiBase extends Container {
460
478
  this.setFocus(topVisible?.component ?? overlay.preFocus);
461
479
  }
462
480
  if (this.overlayStack.length === 0)
463
- this.terminal.hideCursor();
481
+ this.hideTerminalCursor();
464
482
  this.requestRender();
465
483
  }
484
+ /** Hide the cursor while running. After stop(), the shell owns the cursor and it must stay visible. */
485
+ hideTerminalCursor() {
486
+ if (!this.stopped)
487
+ this.terminal.hideCursor();
488
+ }
466
489
  /** Check if there are any visible overlays */
467
490
  hasOverlay() {
468
491
  return this.overlayStack.some((o) => this.isOverlayVisible(o));
@@ -653,7 +676,7 @@ export class TuiBase extends Container {
653
676
  }, delay);
654
677
  }
655
678
  handleTerminalInput(data) {
656
- if (this.consumeOsc11BackgroundResponse(data)) {
679
+ if (this.consumeTerminalColorResponse(data)) {
657
680
  return;
658
681
  }
659
682
  if (this.consumeTerminalColorSchemeReport(data)) {
@@ -726,27 +749,50 @@ export class TuiBase extends Container {
726
749
  this.requestImmediateRender();
727
750
  }
728
751
  }
729
- consumeOsc11BackgroundResponse(data) {
730
- if (this.pendingOsc11BackgroundReplies <= 0) {
752
+ consumeTerminalColorResponse(data) {
753
+ const query = this.pendingTerminalColorQueries[0];
754
+ if (!query) {
731
755
  return false;
732
756
  }
733
- if (!isOsc11BackgroundColorResponse(data)) {
757
+ if (DEVICE_ATTRIBUTES_RESPONSE_PATTERN.test(data)) {
758
+ this.pendingTerminalColorQueries.shift();
759
+ this.completeTerminalColorQuery(query);
760
+ return true;
761
+ }
762
+ const response = parseOscColorResponse(data);
763
+ if (!response) {
734
764
  return false;
735
765
  }
736
- const rgb = parseOsc11BackgroundColor(data);
737
- this.pendingOsc11BackgroundReplies -= 1;
738
- const query = this.pendingOsc11BackgroundQueries.shift();
739
- if (query && !query.settled) {
740
- query.settled = true;
741
- if (query.timer) {
742
- clearTimeout(query.timer);
743
- query.timer = undefined;
744
- }
745
- query.resolve?.(rgb);
746
- query.resolve = undefined;
766
+ const { target, rgb } = response;
767
+ const key = String(target);
768
+ if (!query.deliver || query.replied.has(key)) {
769
+ return true;
770
+ }
771
+ query.replied.add(key);
772
+ if (target === "foreground") {
773
+ query.foreground = rgb;
774
+ }
775
+ else if (target === "background") {
776
+ query.background = rgb;
777
+ }
778
+ else if (target < TERMINAL_PALETTE_SIZE) {
779
+ query.palette[target] = rgb;
780
+ }
781
+ if (query.replied.size === TERMINAL_COLOR_REPLY_COUNT) {
782
+ this.completeTerminalColorQuery(query);
747
783
  }
748
784
  return true;
749
785
  }
786
+ terminalColorQueryResult(query) {
787
+ const palette = query.palette.every((color) => color !== undefined) ? query.palette : undefined;
788
+ return { foreground: query.foreground, background: query.background, palette };
789
+ }
790
+ completeTerminalColorQuery(query) {
791
+ const deliver = query.deliver;
792
+ query.deliver = undefined;
793
+ clearTimeout(query.timer);
794
+ deliver?.(this.terminalColorQueryResult(query));
795
+ }
750
796
  consumeTerminalColorSchemeReport(data) {
751
797
  const scheme = parseTerminalColorSchemeReport(data);
752
798
  if (!scheme) {
@@ -1000,55 +1046,28 @@ export class TuiBase extends Container {
1000
1046
  return null;
1001
1047
  }
1002
1048
  /**
1003
- * Query the terminal's default background color with OSC 11 (`ESC ] 11 ; ? BEL`).
1004
- * @param timeoutMs Query timeout in milliseconds.
1005
- * @returns Promise containing the parsed RGB color, or undefined if it times out or fails to parse.
1049
+ * Query the terminal's theme colors: the default foreground (OSC 10), the default background
1050
+ * (OSC 11), and ANSI colors 0-15 (OSC 4), followed by a DA1 request that marks the end of the
1051
+ * replies. Resolves when the DA1 reply or all color replies arrive, or when the timeout expires.
1052
+ * Colors the terminal did not report are undefined; the palette is only set when all 16 arrived.
1053
+ * @param timeoutMs Query timeout in milliseconds, for terminals that do not answer DA1 either.
1054
+ * @param onLateReply Receives the replies if the query completes after the timeout, e.g. over slow links.
1006
1055
  */
1007
- queryTerminalBackgroundColor({ timeoutMs }) {
1056
+ queryTerminalColors({ timeoutMs, onLateReply, }) {
1008
1057
  return new Promise((resolve) => {
1009
1058
  const query = {
1010
- settled: false,
1011
- resolve,
1059
+ palette: Array.from({ length: TERMINAL_PALETTE_SIZE }, () => undefined),
1060
+ replied: new Set(),
1061
+ deliver: resolve,
1012
1062
  timer: undefined,
1013
1063
  };
1064
+ // Resolve with the replies so far, and keep collecting late replies for `onLateReply`.
1014
1065
  query.timer = setTimeout(() => {
1015
- if (query.settled) {
1016
- return;
1017
- }
1018
- query.settled = true;
1019
- query.timer = undefined;
1020
- query.resolve?.(undefined);
1021
- query.resolve = undefined;
1066
+ query.deliver = onLateReply;
1067
+ resolve(this.terminalColorQueryResult(query));
1022
1068
  }, timeoutMs);
1023
- this.pendingOsc11BackgroundQueries.push(query);
1024
- this.pendingOsc11BackgroundReplies += 1;
1025
- this.terminal.write("\x1b]11;?\x07");
1026
- });
1027
- }
1028
- /**
1029
- * Query the terminal's color-scheme preference with DSR (`CSI ? 996 n`).
1030
- * Terminals that support the color palette notification protocol reply with
1031
- * `CSI ? 997 ; 1 n` for dark or `CSI ? 997 ; 2 n` for light.
1032
- */
1033
- queryTerminalColorScheme({ timeoutMs }) {
1034
- return new Promise((resolve) => {
1035
- let settled = false;
1036
- let timer;
1037
- let unsubscribe = () => { };
1038
- const settle = (scheme) => {
1039
- if (settled)
1040
- return;
1041
- settled = true;
1042
- if (timer) {
1043
- clearTimeout(timer);
1044
- timer = undefined;
1045
- }
1046
- unsubscribe();
1047
- resolve(scheme);
1048
- };
1049
- unsubscribe = this.onTerminalColorSchemeChange(settle);
1050
- timer = setTimeout(() => settle(undefined), timeoutMs);
1051
- this.terminal.write("\x1b[?996n");
1069
+ this.pendingTerminalColorQueries.push(query);
1070
+ this.terminal.write(TERMINAL_COLOR_QUERY);
1052
1071
  });
1053
1072
  }
1054
1073
  }