@f5xc-salesdemos/pi-tui 18.43.0 → 18.44.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,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/pi-tui",
4
- "version": "18.43.0",
4
+ "version": "18.44.0",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://github.com/f5xc-salesdemos/xcsh",
7
7
  "author": "Can Boluk",
@@ -37,8 +37,8 @@
37
37
  "fmt": "biome format --write ."
38
38
  },
39
39
  "dependencies": {
40
- "@f5xc-salesdemos/pi-natives": "18.43.0",
41
- "@f5xc-salesdemos/pi-utils": "18.43.0",
40
+ "@f5xc-salesdemos/pi-natives": "18.44.0",
41
+ "@f5xc-salesdemos/pi-utils": "18.44.0",
42
42
  "marked": "^17.0"
43
43
  },
44
44
  "devDependencies": {
@@ -1,4 +1,4 @@
1
- import { getProjectDir, logger } from "@f5xc-salesdemos/pi-utils";
1
+ import { $flag, getProjectDir, logger } from "@f5xc-salesdemos/pi-utils";
2
2
  import type { AutocompleteProvider, CombinedAutocompleteProvider } from "../autocomplete";
3
3
  import { BracketedPasteHandler } from "../bracketed-paste";
4
4
  import { getKeybindings, type KeybindingsManager } from "../keybindings";
@@ -807,6 +807,13 @@ export class Editor implements Component, Focusable {
807
807
  displayWidth += visibleWidth(inlineHint);
808
808
  } else if (after.length === 0 && !borderVisible && displayWidth >= lineContentWidth) {
809
809
  displayText = this.#renderTerminalCursorMarker(before, marker, lineContentWidth);
810
+ } else if (borderVisible && after.length === 0 && displayWidth <= lineContentWidth) {
811
+ const { text: cursorGlyph, width: cursorGlyphWidth } = this.#getStyledInputCursor();
812
+ displayText = before + marker + cursorGlyph;
813
+ displayWidth += cursorGlyphWidth;
814
+ if (displayWidth > lineContentWidth && paddingX > 0) {
815
+ cursorInPadding = true;
816
+ }
810
817
  } else {
811
818
  displayText = before + marker + after;
812
819
  }
@@ -879,16 +886,22 @@ export class Editor implements Component, Focusable {
879
886
  // All lines have consistent borders based on padding
880
887
  const isLastLine = visibleIndex === visibleLayoutLines.length - 1;
881
888
  const rightPaddingWidth = Math.max(0, paddingX - (cursorInPadding ? 1 : 0));
889
+ // When the hardware cursor sits on the padding space before the border,
890
+ // the border's ANSI foreground color can make the cursor invisible.
891
+ // Insert a reset before the border so the cursor uses default colors.
892
+ const cursorAtEnd =
893
+ hasCursor && this.#useTerminalCursor && (layoutLine.cursorPos ?? 0) >= layoutLine.text.length;
894
+ const padReset = cursorAtEnd ? "\x1b[0m" : "";
882
895
  if (isLastLine) {
883
896
  const bottomRightPadding = Math.max(0, paddingX - 1 - (cursorInPadding ? 1 : 0));
884
897
  const bottomRightAdjusted = this.borderColor(
885
898
  `${padding(bottomRightPadding)}${box.horizontal}${box.bottomRight}`,
886
899
  );
887
- result.push(`${bottomLeft}${displayText}${linePad}${bottomRightAdjusted}`);
900
+ result.push(`${bottomLeft}${displayText}${linePad}${padReset}${bottomRightAdjusted}`);
888
901
  } else {
889
902
  const leftBorder = this.borderColor(`${box.vertical}${padding(paddingX)}`);
890
903
  const rightBorder = this.borderColor(`${padding(rightPaddingWidth)}${box.vertical}`);
891
- result.push(leftBorder + displayText + linePad + rightBorder);
904
+ result.push(leftBorder + displayText + linePad + padReset + rightBorder);
892
905
  }
893
906
  }
894
907
 
@@ -898,6 +911,29 @@ export class Editor implements Component, Focusable {
898
911
  result.push(...autocompleteResult);
899
912
  }
900
913
 
914
+ // Guard: clamp any oversized lines to prevent ANSI corruption from terminal wrapping
915
+ // Strip CURSOR_MARKER before measuring — it's an APC sequence that visibleWidth
916
+ // miscounts as visible chars; the TUI strips it later in #extractCursorPosition.
917
+ for (let i = 0; i < result.length; i++) {
918
+ const stripped = result[i].replaceAll(CURSOR_MARKER, "");
919
+ const w = visibleWidth(stripped);
920
+ if (w > width) {
921
+ if ($flag("PI_DEBUG_EDITOR_WIDTH")) {
922
+ const section =
923
+ i === 0 && borderVisible
924
+ ? "top-border"
925
+ : i === result.length - 1 && borderVisible
926
+ ? "bottom-border"
927
+ : borderVisible
928
+ ? "middle-line"
929
+ : "borderless";
930
+ logger.warn(`Editor line ${i} exceeds width (${w} > ${width}) in ${section}`);
931
+ }
932
+ const hadMarker = result[i] !== stripped;
933
+ result[i] = sliceByColumn(stripped, 0, width, true) + (hadMarker ? CURSOR_MARKER : "");
934
+ }
935
+ }
936
+
901
937
  return result;
902
938
  }
903
939
 
package/src/tui.ts CHANGED
@@ -998,9 +998,14 @@ export class TUI extends Container {
998
998
 
999
999
  // Clamp any oversized lines before dirty-checking so the truncated form
1000
1000
  // matches what we store in #previousLines, preventing perpetual repaints.
1001
+ // Strip CURSOR_MARKER before measuring — visibleWidth miscounts APC sequences
1002
+ // as visible chars; the marker is extracted later in #extractCursorPosition.
1001
1003
  for (let i = 0; i < newLines.length; i++) {
1002
- if (!TERMINAL.isImageLine(newLines[i]) && visibleWidth(newLines[i]) > width) {
1003
- newLines[i] = sliceByColumn(newLines[i], 0, width, true);
1004
+ if (TERMINAL.isImageLine(newLines[i])) continue;
1005
+ const stripped = newLines[i].replaceAll(CURSOR_MARKER, "");
1006
+ if (visibleWidth(stripped) > width) {
1007
+ const hadMarker = newLines[i] !== stripped;
1008
+ newLines[i] = sliceByColumn(stripped, 0, width, true) + (hadMarker ? CURSOR_MARKER : "");
1004
1009
  }
1005
1010
  }
1006
1011