@oh-my-pi/pi-tui 10.6.2 → 11.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-tui",
3
- "version": "10.6.2",
3
+ "version": "11.0.0",
4
4
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -47,7 +47,8 @@
47
47
  "bun": ">=1.3.7"
48
48
  },
49
49
  "dependencies": {
50
- "@oh-my-pi/pi-natives": "10.6.2",
50
+ "@oh-my-pi/pi-natives": "11.0.0",
51
+ "@oh-my-pi/pi-utils": "11.0.0",
51
52
  "@types/mime-types": "^3.0.1",
52
53
  "chalk": "^5.6.2",
53
54
  "marked": "^17.0.1",
@@ -1,4 +1,10 @@
1
- import { getImageDimensions, type ImageDimensions, imageFallback, renderImage, TERMINAL_INFO } from "../terminal-image";
1
+ import {
2
+ getImageDimensions,
3
+ type ImageDimensions,
4
+ imageFallback,
5
+ renderImage,
6
+ TERMINAL,
7
+ } from "../terminal-capabilities";
2
8
  import type { Component } from "../tui";
3
9
 
4
10
  export interface ImageTheme {
@@ -49,7 +55,7 @@ export class Image implements Component {
49
55
 
50
56
  let lines: string[];
51
57
 
52
- if (TERMINAL_INFO.imageProtocol) {
58
+ if (TERMINAL.imageProtocol) {
53
59
  const result = renderImage(this.base64Data, this.dimensions, { maxWidthCells: maxWidth });
54
60
 
55
61
  if (result) {
@@ -1,7 +1,7 @@
1
1
  import { marked, type Token } from "marked";
2
2
  import type { MermaidImage } from "../mermaid";
3
3
  import type { SymbolTheme } from "../symbols";
4
- import { encodeITerm2, encodeKitty, getCellDimensions, ImageProtocol, TERMINAL_INFO } from "../terminal-image";
4
+ import { encodeITerm2, encodeKitty, getCellDimensions, ImageProtocol, TERMINAL } from "../terminal-capabilities";
5
5
  import type { Component } from "../tui";
6
6
  import { applyBackgroundToLine, padding, visibleWidth, wrapTextWithAnsi } from "../utils";
7
7
 
@@ -143,7 +143,7 @@ export class Markdown implements Component {
143
143
  const wrappedLines: string[] = [];
144
144
  for (const line of renderedLines) {
145
145
  // Skip wrapping for image protocol lines (would corrupt escape sequences)
146
- if (TERMINAL_INFO.isImageLine(line)) {
146
+ if (TERMINAL.isImageLine(line)) {
147
147
  wrappedLines.push(line);
148
148
  } else {
149
149
  wrappedLines.push(...wrapTextWithAnsi(line, contentWidth));
@@ -158,7 +158,7 @@ export class Markdown implements Component {
158
158
 
159
159
  for (const line of wrappedLines) {
160
160
  // Image lines must be output raw - no margins or background
161
- if (TERMINAL_INFO.isImageLine(line)) {
161
+ if (TERMINAL.isImageLine(line)) {
162
162
  contentLines.push(line);
163
163
  continue;
164
164
  }
@@ -314,7 +314,7 @@ export class Markdown implements Component {
314
314
  const hash = Bun.hash(token.text.trim()).toString(16);
315
315
  const image = this.theme.getMermaidImage(hash);
316
316
 
317
- if (image && TERMINAL_INFO.imageProtocol) {
317
+ if (image && TERMINAL.imageProtocol) {
318
318
  const imageLines = this.renderMermaidImage(image, width);
319
319
  if (imageLines) {
320
320
  lines.push(...imageLines);
@@ -809,7 +809,7 @@ export class Markdown implements Component {
809
809
  * Returns array of lines (image placeholder rows) or null if rendering fails.
810
810
  */
811
811
  private renderMermaidImage(image: MermaidImage, availableWidth: number): string[] | null {
812
- if (!TERMINAL_INFO.imageProtocol) return null;
812
+ if (!TERMINAL.imageProtocol) return null;
813
813
 
814
814
  const cellDims = getCellDimensions();
815
815
  const scale = 0.5; // Render at 50% of natural size
@@ -833,7 +833,7 @@ export class Markdown implements Component {
833
833
  }
834
834
 
835
835
  let sequence: string;
836
- switch (TERMINAL_INFO.imageProtocol) {
836
+ switch (TERMINAL.imageProtocol) {
837
837
  case ImageProtocol.Kitty:
838
838
  sequence = encodeKitty(image.base64, { columns, rows });
839
839
  break;
package/src/index.ts CHANGED
@@ -59,28 +59,7 @@ export type { BoxSymbols, SymbolTheme } from "./symbols";
59
59
  // Terminal interface and implementations
60
60
  export { emergencyTerminalRestore, ProcessTerminal, type Terminal } from "./terminal";
61
61
  // Terminal image support
62
- export {
63
- type CellDimensions,
64
- calculateImageRows,
65
- encodeITerm2,
66
- encodeKitty,
67
- getCellDimensions,
68
- getGifDimensions,
69
- getImageDimensions,
70
- getJpegDimensions,
71
- getPngDimensions,
72
- getTerminalInfo,
73
- getWebpDimensions,
74
- type ImageDimensions,
75
- type ImageRenderOptions,
76
- imageFallback,
77
- renderImage,
78
- setCellDimensions,
79
- TERMINAL_ID,
80
- TERMINAL_INFO,
81
- type TerminalId,
82
- type TerminalInfo,
83
- } from "./terminal-image";
62
+ export * from "./terminal-capabilities";
84
63
  export { type Component, Container, type OverlayHandle, type SizeValue, TUI } from "./tui";
85
64
  // Utilities
86
65
  export { Ellipsis, padding, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "./utils";
@@ -1,35 +1,63 @@
1
+ import { $env } from "@oh-my-pi/pi-utils";
2
+
1
3
  export enum ImageProtocol {
2
4
  Kitty = "\x1b_G",
3
5
  Iterm2 = "\x1b]1337;File=",
4
6
  }
5
7
 
8
+ export enum NotifyProtocol {
9
+ Bell = "\x07",
10
+ Osc99 = "\x1b]99;;",
11
+ Osc9 = "\x1b]9;",
12
+ }
13
+
6
14
  export type TerminalId = "kitty" | "ghostty" | "wezterm" | "iterm2" | "vscode" | "alacritty" | "base" | "trueColor";
7
15
 
16
+ /** Terminal capability details used for rendering and protocol selection. */
8
17
  export class TerminalInfo {
9
18
  constructor(
10
19
  public readonly id: TerminalId,
11
20
  public readonly imageProtocol: ImageProtocol | null,
12
21
  public readonly trueColor: boolean,
13
22
  public readonly hyperlinks: boolean,
23
+ public readonly notifyProtocol: NotifyProtocol = NotifyProtocol.Bell,
14
24
  ) {}
15
25
 
16
26
  isImageLine(line: string): boolean {
17
27
  if (!this.imageProtocol) return false;
18
28
  return line.slice(0, 64).includes(this.imageProtocol);
19
29
  }
30
+
31
+ formatNotification(message: string): string {
32
+ if (this.notifyProtocol === NotifyProtocol.Bell) {
33
+ return NotifyProtocol.Bell;
34
+ }
35
+ return `${this.notifyProtocol}${message}\x1b\\`;
36
+ }
37
+
38
+ sendNotification(message: string): void {
39
+ if (isNotificationSuppressed()) return;
40
+ process.stdout.write(this.formatNotification(message));
41
+ }
42
+ }
43
+
44
+ export function isNotificationSuppressed(): boolean {
45
+ const value = $env.PI_NOTIFICATIONS;
46
+ if (!value) return false;
47
+ return value === "off" || value === "0" || value === "false";
20
48
  }
21
49
 
22
50
  const KNOWN_TERMINALS = Object.freeze({
23
51
  // Fallback terminals
24
- base: new TerminalInfo("base", null, false, true),
25
- trueColor: new TerminalInfo("trueColor", null, true, true),
52
+ base: new TerminalInfo("base", null, false, true, NotifyProtocol.Bell),
53
+ trueColor: new TerminalInfo("trueColor", null, true, true, NotifyProtocol.Bell),
26
54
  // Recognized terminals
27
- kitty: new TerminalInfo("kitty", ImageProtocol.Kitty, true, true),
28
- ghostty: new TerminalInfo("ghostty", ImageProtocol.Kitty, true, true),
29
- wezterm: new TerminalInfo("wezterm", ImageProtocol.Kitty, true, true),
30
- iterm2: new TerminalInfo("iterm2", ImageProtocol.Iterm2, true, true),
31
- vscode: new TerminalInfo("vscode", null, true, true),
32
- alacritty: new TerminalInfo("alacritty", null, true, true),
55
+ kitty: new TerminalInfo("kitty", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc99),
56
+ ghostty: new TerminalInfo("ghostty", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc9),
57
+ wezterm: new TerminalInfo("wezterm", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc9),
58
+ iterm2: new TerminalInfo("iterm2", ImageProtocol.Iterm2, true, true, NotifyProtocol.Osc9),
59
+ vscode: new TerminalInfo("vscode", null, true, true, NotifyProtocol.Bell),
60
+ alacritty: new TerminalInfo("alacritty", null, true, true, NotifyProtocol.Bell),
33
61
  });
34
62
 
35
63
  export const TERMINAL_ID: TerminalId = (() => {
@@ -73,7 +101,7 @@ export const TERMINAL_ID: TerminalId = (() => {
73
101
  return "base";
74
102
  })();
75
103
 
76
- export const TERMINAL_INFO = getTerminalInfo(TERMINAL_ID);
104
+ export const TERMINAL = getTerminalInfo(TERMINAL_ID);
77
105
 
78
106
  export function getTerminalInfo(terminalId: TerminalId): TerminalInfo {
79
107
  return KNOWN_TERMINALS[terminalId];
@@ -332,19 +360,19 @@ export function renderImage(
332
360
  imageDimensions: ImageDimensions,
333
361
  options: ImageRenderOptions = {},
334
362
  ): { sequence: string; rows: number } | null {
335
- if (!TERMINAL_INFO.imageProtocol) {
363
+ if (!TERMINAL.imageProtocol) {
336
364
  return null;
337
365
  }
338
366
 
339
367
  const maxWidth = options.maxWidthCells ?? 80;
340
368
  const rows = calculateImageRows(imageDimensions, maxWidth, getCellDimensions());
341
369
 
342
- if (TERMINAL_INFO.imageProtocol === ImageProtocol.Kitty) {
370
+ if (TERMINAL.imageProtocol === ImageProtocol.Kitty) {
343
371
  const sequence = encodeKitty(base64Data, { columns: maxWidth, rows });
344
372
  return { sequence, rows };
345
373
  }
346
374
 
347
- if (TERMINAL_INFO.imageProtocol === ImageProtocol.Iterm2) {
375
+ if (TERMINAL.imageProtocol === ImageProtocol.Iterm2) {
348
376
  const sequence = encodeITerm2(base64Data, {
349
377
  width: maxWidth,
350
378
  height: "auto",
package/src/terminal.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as fs from "node:fs";
2
- import { logger } from "@oh-my-pi/pi-utils";
2
+ import { $env, logger } from "@oh-my-pi/pi-utils";
3
3
  import { setKittyProtocolActive } from "./keys";
4
4
  import { StdinBuffer } from "./stdin-buffer";
5
5
 
@@ -82,7 +82,7 @@ export class ProcessTerminal implements Terminal {
82
82
  private stdinBuffer?: StdinBuffer;
83
83
  private stdinDataHandler?: (data: string) => void;
84
84
  private dead = false;
85
- private writeLogPath = process.env.OMP_TUI_WRITE_LOG || "";
85
+ private writeLogPath = $env.PI_TUI_WRITE_LOG || "";
86
86
 
87
87
  get kittyProtocolActive(): boolean {
88
88
  return this._kittyProtocolActive;
package/src/tui.ts CHANGED
@@ -4,9 +4,10 @@
4
4
  import * as fs from "node:fs";
5
5
  import * as os from "node:os";
6
6
  import * as path from "node:path";
7
+ import { $env } from "@oh-my-pi/pi-utils";
7
8
  import { isKeyRelease, matchesKey } from "./keys";
8
9
  import type { Terminal } from "./terminal";
9
- import { setCellDimensions, TERMINAL_INFO } from "./terminal-image";
10
+ import { setCellDimensions, TERMINAL } from "./terminal-capabilities";
10
11
  import { extractSegments, padding, sliceByColumn, sliceWithWidth, visibleWidth } from "./utils";
11
12
 
12
13
  /**
@@ -227,7 +228,7 @@ export class TUI extends Container {
227
228
  private hardwareCursorRow = 0; // Actual terminal cursor row (may differ due to IME positioning)
228
229
  private inputBuffer = ""; // Buffer for parsing terminal responses
229
230
  private cellSizeQueryPending = false;
230
- private showHardwareCursor = process.env.OMP_HARDWARE_CURSOR === "1";
231
+ private showHardwareCursor = $env.PI_HARDWARE_CURSOR === "1";
231
232
  private maxLinesRendered = 0; // Track terminal's working area (max lines ever rendered)
232
233
  private previousViewportTop = 0; // Track previous viewport top for resize-aware cursor moves
233
234
  private fullRedrawCount = 0;
@@ -384,7 +385,7 @@ export class TUI extends Container {
384
385
 
385
386
  private queryCellSize(): void {
386
387
  // Only query if terminal supports images (cell size is only used for image rendering)
387
- if (!TERMINAL_INFO.imageProtocol) {
388
+ if (!TERMINAL.imageProtocol) {
388
389
  return;
389
390
  }
390
391
  // Query terminal for cell size in pixels: CSI 16 t
@@ -767,7 +768,7 @@ export class TUI extends Container {
767
768
  const reset = TUI.SEGMENT_RESET;
768
769
  for (let i = 0; i < lines.length; i++) {
769
770
  const line = lines[i];
770
- if (!TERMINAL_INFO.isImageLine(line)) {
771
+ if (!TERMINAL.isImageLine(line)) {
771
772
  lines[i] = line + reset;
772
773
  }
773
774
  }
@@ -846,7 +847,7 @@ export class TUI extends Container {
846
847
  overlayWidth: number,
847
848
  totalWidth: number,
848
849
  ): string {
849
- if (TERMINAL_INFO.isImageLine(baseLine)) return baseLine;
850
+ if (TERMINAL.isImageLine(baseLine)) return baseLine;
850
851
 
851
852
  // Single pass through baseLine extracts both before and after segments
852
853
  const afterStart = startCol + overlayWidth;
@@ -1081,7 +1082,7 @@ export class TUI extends Container {
1081
1082
  if (i > firstChanged) buffer += "\r\n";
1082
1083
  buffer += "\x1b[2K"; // Clear current line
1083
1084
  let line = newLines[i];
1084
- const isImageLine = TERMINAL_INFO.isImageLine(line);
1085
+ const isImageLine = TERMINAL.isImageLine(line);
1085
1086
  if (!isImageLine && visibleWidth(line) > width) {
1086
1087
  if (process.platform === "win32") {
1087
1088
  line = sliceByColumn(line, 0, width, true);