@oh-my-pi/pi-tui 11.8.3 → 11.10.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": "11.8.3",
3
+ "version": "11.10.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,8 +47,8 @@
47
47
  "bun": ">=1.3.7"
48
48
  },
49
49
  "dependencies": {
50
- "@oh-my-pi/pi-natives": "11.8.3",
51
- "@oh-my-pi/pi-utils": "11.8.3",
50
+ "@oh-my-pi/pi-natives": "11.10.0",
51
+ "@oh-my-pi/pi-utils": "11.10.0",
52
52
  "@types/mime-types": "^3.0.1",
53
53
  "chalk": "^5.6.2",
54
54
  "marked": "^17.0.1",
@@ -130,6 +130,8 @@ export interface AutocompleteItem {
130
130
  value: string;
131
131
  label: string;
132
132
  description?: string;
133
+ /** Dim hint text shown inline after cursor when this item is selected */
134
+ hint?: string;
133
135
  }
134
136
 
135
137
  export interface SlashCommand {
@@ -138,11 +140,12 @@ export interface SlashCommand {
138
140
  // Function to get argument completions for this command
139
141
  // Returns null if no argument completion is available
140
142
  getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
143
+ /** Return inline hint text for the current argument state (shown as dim ghost text after cursor) */
144
+ getInlineHint?(argumentText: string): string | null;
141
145
  }
142
146
 
143
147
  export interface AutocompleteProvider {
144
- // Get autocomplete suggestions for current text/cursor position
145
- // Returns null if no suggestions available
148
+ /** Get autocomplete suggestions for current text/cursor position */
146
149
  getSuggestions(
147
150
  lines: string[],
148
151
  cursorLine: number,
@@ -152,8 +155,7 @@ export interface AutocompleteProvider {
152
155
  prefix: string; // What we're matching against (e.g., "/" or "src/")
153
156
  } | null>;
154
157
 
155
- // Apply the selected item
156
- // Returns the new text and cursor position
158
+ /** Apply the selected item and return new text + cursor position */
157
159
  applyCompletion(
158
160
  lines: string[],
159
161
  cursorLine: number,
@@ -165,6 +167,9 @@ export interface AutocompleteProvider {
165
167
  cursorLine: number;
166
168
  cursorCol: number;
167
169
  };
170
+
171
+ /** Get inline hint text to show as dim ghost text after the cursor */
172
+ getInlineHint?(lines: string[], cursorLine: number, cursorCol: number): string | null;
168
173
  }
169
174
 
170
175
  // Combined provider that handles both slash commands and file paths
@@ -715,4 +720,29 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
715
720
 
716
721
  return true;
717
722
  }
723
+
724
+ /** Get inline hint text for slash commands with subcommand hints */
725
+ getInlineHint(lines: string[], cursorLine: number, cursorCol: number): string | null {
726
+ const currentLine = lines[cursorLine] || "";
727
+ const textBeforeCursor = currentLine.slice(0, cursorCol);
728
+
729
+ if (!textBeforeCursor.startsWith("/")) return null;
730
+
731
+ const spaceIndex = textBeforeCursor.indexOf(" ");
732
+ if (spaceIndex === -1) return null;
733
+
734
+ const commandName = textBeforeCursor.slice(1, spaceIndex);
735
+ const argumentText = textBeforeCursor.slice(spaceIndex + 1);
736
+
737
+ const command = this.#commands.find(cmd => {
738
+ const name = "name" in cmd ? cmd.name : cmd.value;
739
+ return name === commandName;
740
+ });
741
+
742
+ if (!command || !("getInlineHint" in command) || !command.getInlineHint) {
743
+ return null;
744
+ }
745
+
746
+ return command.getInlineHint(argumentText);
747
+ }
718
748
  }
@@ -263,6 +263,8 @@ export interface EditorTheme {
263
263
  selectList: SelectListTheme;
264
264
  symbols: SymbolTheme;
265
265
  editorPaddingX?: number;
266
+ /** Style function for inline hint/ghost text (dim text after cursor) */
267
+ hintStyle?: (text: string) => string;
266
268
  }
267
269
 
268
270
  export interface EditorTopBorder {
@@ -553,6 +555,10 @@ export class Editor implements Component, Focusable {
553
555
  const emitCursorMarker = this.focused && !this.#autocompleteState;
554
556
  const lineContentWidth = contentAreaWidth;
555
557
 
558
+ // Compute inline hint text (dim ghost text after cursor)
559
+ const inlineHint = this.#getInlineHint();
560
+ const hintStyle = this.#theme.hintStyle ?? ((t: string) => `\x1b[2m${t}\x1b[0m`);
561
+
556
562
  for (const layoutLine of visibleLayoutLines) {
557
563
  let displayText = layoutLine.text;
558
564
  let displayWidth = visibleWidth(layoutLine.text);
@@ -566,7 +572,13 @@ export class Editor implements Component, Focusable {
566
572
  if (marker) {
567
573
  const before = displayText.slice(0, layoutLine.cursorPos);
568
574
  const after = displayText.slice(layoutLine.cursorPos);
569
- displayText = before + marker + after;
575
+ if (after.length === 0 && inlineHint) {
576
+ const hintText = hintStyle(truncateToWidth(inlineHint, Math.max(0, lineContentWidth - displayWidth)));
577
+ displayText = before + marker + hintText;
578
+ displayWidth += visibleWidth(inlineHint);
579
+ } else {
580
+ displayText = before + marker + after;
581
+ }
570
582
  }
571
583
  } else if (hasCursor && !this.#useTerminalCursor) {
572
584
  const before = displayText.slice(0, layoutLine.cursorPos);
@@ -585,8 +597,15 @@ export class Editor implements Component, Focusable {
585
597
  // Cursor is at the end - add thin cursor glyph
586
598
  const cursorChar = this.#theme.symbols.inputCursor;
587
599
  const cursor = `\x1b[5m${cursorChar}\x1b[0m`;
588
- displayText = before + marker + cursor;
589
- displayWidth += visibleWidth(cursorChar);
600
+ if (inlineHint) {
601
+ const availWidth = Math.max(0, lineContentWidth - displayWidth - visibleWidth(cursorChar));
602
+ const hintText = hintStyle(truncateToWidth(inlineHint, availWidth));
603
+ displayText = before + marker + cursor + hintText;
604
+ displayWidth += visibleWidth(cursorChar) + Math.min(visibleWidth(inlineHint), availWidth);
605
+ } else {
606
+ displayText = before + marker + cursor;
607
+ displayWidth += visibleWidth(cursorChar);
608
+ }
590
609
  if (displayWidth > lineContentWidth && paddingX > 0) {
591
610
  cursorInPadding = true;
592
611
  }
@@ -2168,4 +2187,27 @@ https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19536643416/job/
2168
2187
  this.#autocompleteTimeout = undefined;
2169
2188
  }
2170
2189
  }
2190
+
2191
+ /**
2192
+ * Get inline hint text to show as dim ghost text after the cursor.
2193
+ * Checks selected autocomplete item's hint first, then falls back to provider.
2194
+ */
2195
+ #getInlineHint(): string | null {
2196
+ // Check selected autocomplete item for a hint
2197
+ if (this.#autocompleteState && this.#autocompleteList) {
2198
+ const selected = this.#autocompleteList.getSelectedItem();
2199
+ return selected?.hint ?? null;
2200
+ }
2201
+
2202
+ // Fall back to provider's getInlineHint
2203
+ if (this.#autocompleteProvider?.getInlineHint) {
2204
+ return this.#autocompleteProvider.getInlineHint(
2205
+ this.#state.lines,
2206
+ this.#state.cursorLine,
2207
+ this.#state.cursorCol,
2208
+ );
2209
+ }
2210
+
2211
+ return null;
2212
+ }
2171
2213
  }
@@ -7,6 +7,8 @@ export interface SelectItem {
7
7
  value: string;
8
8
  label: string;
9
9
  description?: string;
10
+ /** Dim hint text shown inline after cursor when this item is selected */
11
+ hint?: string;
10
12
  }
11
13
 
12
14
  export interface SelectListTheme {