@oh-my-pi/pi-tui 15.5.10 → 15.5.11

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.
@@ -31,6 +31,10 @@ export declare class Editor implements Component, Focusable {
31
31
  cursorOverride: string | undefined;
32
32
  /** Display width of the cursorOverride glyph (needed because override may contain ANSI escapes). */
33
33
  cursorOverrideWidth: number | undefined;
34
+ /** Optional hook that styles displayed input text with zero-width ANSI escapes.
35
+ * MUST preserve visible width (may only add SGR codes, never glyphs). Applied per
36
+ * layout line to the user-text segments — never to the cursor glyph or inline hint. */
37
+ decorateText: ((text: string) => string) | undefined;
34
38
  borderColor: (str: string) => string;
35
39
  onAutocompleteUpdate?: () => void;
36
40
  onSubmit?: (text: string) => void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-tui",
4
- "version": "15.5.10",
4
+ "version": "15.5.11",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -37,8 +37,8 @@
37
37
  "fmt": "biome format --write ."
38
38
  },
39
39
  "dependencies": {
40
- "@oh-my-pi/pi-natives": "15.5.10",
41
- "@oh-my-pi/pi-utils": "15.5.10",
40
+ "@oh-my-pi/pi-natives": "15.5.11",
41
+ "@oh-my-pi/pi-utils": "15.5.11",
42
42
  "lru-cache": "11.3.6",
43
43
  "marked": "^18.0.3"
44
44
  },
@@ -325,6 +325,10 @@ export class Editor implements Component, Focusable {
325
325
  cursorOverride: string | undefined;
326
326
  /** Display width of the cursorOverride glyph (needed because override may contain ANSI escapes). */
327
327
  cursorOverrideWidth: number | undefined;
328
+ /** Optional hook that styles displayed input text with zero-width ANSI escapes.
329
+ * MUST preserve visible width (may only add SGR codes, never glyphs). Applied per
330
+ * layout line to the user-text segments — never to the cursor glyph or inline hint. */
331
+ decorateText: ((text: string) => string) | undefined;
328
332
  #promptGutter: string | undefined;
329
333
 
330
334
  // Store last layout width for cursor navigation
@@ -583,6 +587,13 @@ export class Editor implements Component, Focusable {
583
587
  return Math.max(1, this.#maxHeight - verticalChrome);
584
588
  }
585
589
 
590
+ /** Apply the optional input decorator to a plain (ANSI-free) text segment.
591
+ * Decoration only adds zero-width SGR codes, so visible width is unchanged. */
592
+ #decorate(text: string): string {
593
+ const decorate = this.decorateText;
594
+ return decorate !== undefined && text.length > 0 ? decorate(text) : text;
595
+ }
596
+
586
597
  #getStyledInputCursor(): { text: string; width: number } {
587
598
  const cursorChar = this.#theme.symbols.inputCursor;
588
599
  return { text: `\x1b[5m${cursorChar}\x1b[0m`, width: visibleWidth(cursorChar) };
@@ -732,6 +743,7 @@ export class Editor implements Component, Focusable {
732
743
  let displayText = layoutLine.text;
733
744
  let displayWidth = visibleWidth(layoutLine.text);
734
745
  let cursorInPadding = false;
746
+ let decorated = false;
735
747
  const showPromptGutter = promptGutter !== undefined && visibleIndex === 0;
736
748
  const gutterText =
737
749
  promptGutter === undefined ? "" : showPromptGutter ? promptGutter.firstLine : promptGutter.continuation;
@@ -809,7 +821,11 @@ export class Editor implements Component, Focusable {
809
821
  const firstGrapheme = afterGraphemes[0]?.segment || "";
810
822
  const restAfter = after.slice(firstGrapheme.length);
811
823
  const cursor = `\x1b[7m${firstGrapheme}\x1b[0m`;
812
- displayText = before + marker + cursor + restAfter;
824
+ // Decorate the plain text on each side of the cursor glyph. The reverse-video
825
+ // reset (\x1b[0m) ends in "m" (a word char), so a boundary match on restAfter
826
+ // would fail in the whole-line fallback below — decorate the segments here.
827
+ displayText = this.#decorate(before) + marker + cursor + this.#decorate(restAfter);
828
+ decorated = true;
813
829
  // displayWidth stays the same - we're replacing, not adding
814
830
  } else if (this.cursorOverride) {
815
831
  // Cursor override replaces the normal end-of-text cursor glyph
@@ -856,6 +872,13 @@ export class Editor implements Component, Focusable {
856
872
  }
857
873
  }
858
874
 
875
+ // No cursor on this line, or a branch that left the user text intact: decorate the
876
+ // whole line. CURSOR_MARKER and cursor glyphs begin with ESC, so word boundaries
877
+ // around a decorated keyword stay intact when matched against the assembled line.
878
+ if (!decorated) {
879
+ displayText = this.#decorate(displayText);
880
+ }
881
+
859
882
  const linePad = padding(Math.max(0, lineContentWidth - displayWidth));
860
883
 
861
884
  if (!borderVisible) {