@f5-sales-demo/pi-tui 21.13.2 → 21.14.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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## Unreleased
6
+
7
+ ### Fixed
8
+
9
+ - Kept `@` directory completion open after Tab or Enter without inserting a trailing space ([#3724](https://github.com/f5-sales-demo/xcsh/issues/3724)).
10
+
5
11
  ## [20.3.0] - 2026-08-04
6
12
 
7
13
  <!-- textlint-disable terminology -->
@@ -274,7 +280,7 @@
274
280
 
275
281
  ### Fixed
276
282
 
277
- - Fixed terminal scrolling when displaying overlays after rendering large content, preventing hundreds of blank lines from being output
283
+ - Fixed terminal scrolling when displaying overlays after rendering large content, preventing hundreds of empty lines from being output
278
284
 
279
285
  ## [11.3.0] - 2026-02-06
280
286
 
@@ -307,7 +313,7 @@
307
313
 
308
314
  ### Fixed
309
315
 
310
- - Fixed rendering of extra blank lines when content shrinks by improving cursor positioning logic during line deletion
316
+ - Fixed rendering of extra empty lines when content shrinks by improving cursor positioning logic during line deletion
311
317
  - Fixed cursor display position in Input component when scrolling horizontally through long text
312
318
  - Fixed Kitty keyboard protocol disable sequence to use safe write method, preventing potential output buffering issues
313
319
  - Fixed unnecessary full-screen redraws when changes occur in out-of-view components (e.g., spinners), reducing terminal scroll events and improving performance on slower connections
@@ -430,7 +436,7 @@
430
436
  - Improved blockquote rendering with text wrapping in Markdown component
431
437
  - Restructured terminal capabilities from interface-based to class-based model
432
438
  - Improved table column width calculation with word-aware wrapping
433
- - Refactored text utilities to use native WASM implementations for strings >256 chars with JS fast path
439
+ - Refactored text utilities to use native Wasm implementations for strings >256 chars with JS fast path
434
440
 
435
441
  ### Fixed
436
442
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/pi-tui",
4
- "version": "21.13.2",
4
+ "version": "21.14.0",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -37,8 +37,8 @@
37
37
  "fmt": "biome format --write ."
38
38
  },
39
39
  "dependencies": {
40
- "@f5-sales-demo/pi-natives": "21.13.2",
41
- "@f5-sales-demo/pi-utils": "21.13.2",
40
+ "@f5-sales-demo/pi-natives": "21.14.0",
41
+ "@f5-sales-demo/pi-utils": "21.14.0",
42
42
  "marked": "^18.0"
43
43
  },
44
44
  "devDependencies": {
@@ -89,6 +89,11 @@ function parsePathPrefix(prefix: string): { rawPrefix: string; isAtPrefix: boole
89
89
  return { rawPrefix: prefix, isAtPrefix: false, isQuotedPrefix: false };
90
90
  }
91
91
 
92
+ /** Whether a completion value represents a directory, including quoted and Windows paths. */
93
+ export function isDirectoryCompletionValue(value: string): boolean {
94
+ return /[\\/]["']?$/.test(value);
95
+ }
96
+
92
97
  function buildCompletionValue(
93
98
  path: string,
94
99
  options: { isDirectory: boolean; isAtPrefix: boolean; isQuotedPrefix: boolean },
@@ -363,15 +368,16 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
363
368
 
364
369
  // Check if we're completing a file attachment (prefix starts with "@")
365
370
  if (prefix.startsWith("@")) {
366
- // This is a file attachment completion
367
- const newLine = `${beforePrefix + item.value} ${afterCursor}`;
371
+ // This is a file attachment completion. Keep directory paths open for traversal.
372
+ const suffix = isDirectoryCompletionValue(item.value) ? "" : " ";
373
+ const newLine = `${beforePrefix + item.value}${suffix}${afterCursor}`;
368
374
  const newLines = [...lines];
369
375
  newLines[cursorLine] = newLine;
370
376
 
371
377
  return {
372
378
  lines: newLines,
373
379
  cursorLine,
374
- cursorCol: beforePrefix.length + item.value.length + 1, // +1 for space
380
+ cursorCol: beforePrefix.length + item.value.length + suffix.length,
375
381
  };
376
382
  }
377
383
 
@@ -1,5 +1,9 @@
1
1
  import { $flag, getProjectDir, logger } from "@f5-sales-demo/pi-utils";
2
- import type { AutocompleteProvider, CombinedAutocompleteProvider } from "../autocomplete";
2
+ import {
3
+ type AutocompleteProvider,
4
+ type CombinedAutocompleteProvider,
5
+ isDirectoryCompletionValue,
6
+ } from "../autocomplete";
3
7
  import { BracketedPasteHandler } from "../bracketed-paste";
4
8
  import { getKeybindings, type KeybindingsManager } from "../keybindings";
5
9
  import { extractPrintableText, matchesKey } from "../keys";
@@ -1020,7 +1024,8 @@ export class Editor implements Component, Focusable {
1020
1024
  if (kb.matches(data, "tui.input.tab")) {
1021
1025
  const selected = this.#autocompleteList.getSelectedItem();
1022
1026
  if (selected && this.#autocompleteProvider) {
1023
- const shouldChainSlashCommandAutocomplete = this.#isSlashCommandNameAutocompleteSelection();
1027
+ const shouldChainAutocomplete =
1028
+ this.#isSlashCommandNameAutocompleteSelection() || isDirectoryCompletionValue(selected.value);
1024
1029
  const result = this.#autocompleteProvider.applyCompletion(
1025
1030
  this.#state.lines,
1026
1031
  this.#state.cursorLine,
@@ -1041,8 +1046,8 @@ export class Editor implements Component, Focusable {
1041
1046
 
1042
1047
  result.onApplied?.();
1043
1048
 
1044
- if (shouldChainSlashCommandAutocomplete && this.#isCompletedSlashCommandAtCursor()) {
1045
- void this.#tryTriggerAutocomplete();
1049
+ if (shouldChainAutocomplete) {
1050
+ queueMicrotask(() => void this.#tryTriggerAutocomplete());
1046
1051
  }
1047
1052
  }
1048
1053
  return;
@@ -1080,6 +1085,7 @@ export class Editor implements Component, Focusable {
1080
1085
  else if (kb.matches(data, "tui.input.submit") || data === "\n") {
1081
1086
  const selected = this.#autocompleteList.getSelectedItem();
1082
1087
  if (selected && this.#autocompleteProvider) {
1088
+ const shouldChainDirectoryCompletion = isDirectoryCompletionValue(selected.value);
1083
1089
  const result = this.#autocompleteProvider.applyCompletion(
1084
1090
  this.#state.lines,
1085
1091
  this.#state.cursorLine,
@@ -1099,6 +1105,10 @@ export class Editor implements Component, Focusable {
1099
1105
  }
1100
1106
 
1101
1107
  result.onApplied?.();
1108
+
1109
+ if (shouldChainDirectoryCompletion) {
1110
+ queueMicrotask(() => void this.#tryTriggerAutocomplete());
1111
+ }
1102
1112
  }
1103
1113
  return;
1104
1114
  }
@@ -2395,16 +2405,6 @@ export class Editor implements Component, Focusable {
2395
2405
  return textBeforeCursor.startsWith("/") && !textBeforeCursor.includes(" ");
2396
2406
  }
2397
2407
 
2398
- #isCompletedSlashCommandAtCursor(): boolean {
2399
- const currentLine = this.#state.lines[this.#state.cursorLine] || "";
2400
- if (this.#state.cursorCol !== currentLine.length) {
2401
- return false;
2402
- }
2403
-
2404
- const textBeforeCursor = currentLine.slice(0, this.#state.cursorCol).trimStart();
2405
- return /^\/\S+ $/.test(textBeforeCursor);
2406
- }
2407
-
2408
2408
  // Autocomplete methods
2409
2409
  async #tryTriggerAutocomplete(explicitTab: boolean = false): Promise<void> {
2410
2410
  if (!this.#autocompleteProvider) return;
@@ -2493,6 +2493,7 @@ https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19536643416/job/
2493
2493
  // If there's exactly one suggestion and this was an explicit Tab press, apply it immediately
2494
2494
  if (explicitTab && suggestions.items.length === 1) {
2495
2495
  const item = suggestions.items[0]!;
2496
+ const shouldChainDirectoryCompletion = isDirectoryCompletionValue(item.value);
2496
2497
  const result = this.#autocompleteProvider.applyCompletion(
2497
2498
  this.#state.lines,
2498
2499
  this.#state.cursorLine,
@@ -2508,6 +2509,9 @@ https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19536643416/job/
2508
2509
  if (this.onChange) {
2509
2510
  this.onChange(this.getText());
2510
2511
  }
2512
+ if (shouldChainDirectoryCompletion) {
2513
+ queueMicrotask(() => void this.#tryTriggerAutocomplete());
2514
+ }
2511
2515
  return;
2512
2516
  }
2513
2517