@ff-labs/pi-fff 0.7.3-nightly.ff81719 → 0.8.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 +3 -3
- package/src/index.ts +40 -79
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ff-labs/pi-fff",
|
|
3
3
|
"public": true,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.0",
|
|
5
5
|
"description": "pi extension: FFF-powered fuzzy file and content search",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"@ff-labs/fff-node": "*"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@
|
|
47
|
-
"@
|
|
46
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
47
|
+
"@earendil-works/pi-tui": "*",
|
|
48
48
|
"@sinclair/typebox": "*"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
* @-mention autocomplete suggestions in the interactive editor.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ExtensionAPI } from "@
|
|
9
|
-
import { CustomEditor } from "@
|
|
8
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { CustomEditor } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import {
|
|
11
11
|
Text,
|
|
12
12
|
type AutocompleteItem,
|
|
13
13
|
type AutocompleteProvider,
|
|
14
|
-
} from "@
|
|
14
|
+
} from "@earendil-works/pi-tui";
|
|
15
15
|
import { Type } from "@sinclair/typebox";
|
|
16
16
|
import { FileFinder } from "@ff-labs/fff-node";
|
|
17
17
|
import type {
|
|
@@ -256,9 +256,7 @@ function createFffMentionProvider(
|
|
|
256
256
|
|
|
257
257
|
const query = prefix.startsWith('@"') ? prefix.slice(2) : prefix.slice(1);
|
|
258
258
|
const items = await getItems(query, options.signal);
|
|
259
|
-
return options.signal.aborted || items.length === 0
|
|
260
|
-
? null
|
|
261
|
-
: { items, prefix };
|
|
259
|
+
return options.signal.aborted || items.length === 0 ? null : { items, prefix };
|
|
262
260
|
},
|
|
263
261
|
applyCompletion(_lines, cursorLine, cursorCol, item, prefix) {
|
|
264
262
|
const currentLine = _lines[cursorLine] || "";
|
|
@@ -267,11 +265,7 @@ function createFffMentionProvider(
|
|
|
267
265
|
const newLine = before + item.value + after;
|
|
268
266
|
const newCursorCol = cursorCol - prefix.length + item.value.length;
|
|
269
267
|
return {
|
|
270
|
-
lines: [
|
|
271
|
-
..._lines.slice(0, cursorLine),
|
|
272
|
-
newLine,
|
|
273
|
-
..._lines.slice(cursorLine + 1),
|
|
274
|
-
],
|
|
268
|
+
lines: [..._lines.slice(0, cursorLine), newLine, ..._lines.slice(cursorLine + 1)],
|
|
275
269
|
cursorLine,
|
|
276
270
|
cursorCol: newCursorCol,
|
|
277
271
|
};
|
|
@@ -381,22 +375,20 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
381
375
|
const result = f.mixedSearch(query, { pageSize: MENTION_MAX_RESULTS });
|
|
382
376
|
if (!result.ok) return [];
|
|
383
377
|
|
|
384
|
-
return result.value.items
|
|
385
|
-
.
|
|
386
|
-
.map((mixed: MixedItem) => {
|
|
387
|
-
if (mixed.type === "directory") {
|
|
388
|
-
return {
|
|
389
|
-
value: buildAtCompletionValue(mixed.item.relativePath),
|
|
390
|
-
label: mixed.item.dirName,
|
|
391
|
-
description: mixed.item.relativePath,
|
|
392
|
-
};
|
|
393
|
-
}
|
|
378
|
+
return result.value.items.slice(0, MENTION_MAX_RESULTS).map((mixed: MixedItem) => {
|
|
379
|
+
if (mixed.type === "directory") {
|
|
394
380
|
return {
|
|
395
381
|
value: buildAtCompletionValue(mixed.item.relativePath),
|
|
396
|
-
label: mixed.item.
|
|
382
|
+
label: mixed.item.dirName,
|
|
397
383
|
description: mixed.item.relativePath,
|
|
398
384
|
};
|
|
399
|
-
}
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
value: buildAtCompletionValue(mixed.item.relativePath),
|
|
388
|
+
label: mixed.item.fileName,
|
|
389
|
+
description: mixed.item.relativePath,
|
|
390
|
+
};
|
|
391
|
+
});
|
|
400
392
|
}
|
|
401
393
|
|
|
402
394
|
// Editor wrapper that injects FFF @-mention autocomplete alongside base provider.
|
|
@@ -423,12 +415,8 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
423
415
|
if (mentionResult) return mentionResult;
|
|
424
416
|
// Fall back to base provider
|
|
425
417
|
return (
|
|
426
|
-
this.baseProvider?.getSuggestions(
|
|
427
|
-
|
|
428
|
-
cursorLine,
|
|
429
|
-
cursorCol,
|
|
430
|
-
options,
|
|
431
|
-
) ?? null
|
|
418
|
+
this.baseProvider?.getSuggestions(lines, cursorLine, cursorCol, options) ??
|
|
419
|
+
null
|
|
432
420
|
);
|
|
433
421
|
},
|
|
434
422
|
applyCompletion: (lines, cursorLine, cursorCol, item, prefix) => {
|
|
@@ -464,14 +452,12 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
464
452
|
) => void;
|
|
465
453
|
};
|
|
466
454
|
}) {
|
|
467
|
-
if (!shouldEnableMentions())
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
(tui
|
|
472
|
-
|
|
473
|
-
);
|
|
474
|
-
}
|
|
455
|
+
if (!shouldEnableMentions()) return;
|
|
456
|
+
|
|
457
|
+
ctx.ui.setEditorComponent(
|
|
458
|
+
(tui: any, theme: any, keybindings: any) =>
|
|
459
|
+
new FffEditor(tui, theme, keybindings),
|
|
460
|
+
);
|
|
475
461
|
}
|
|
476
462
|
|
|
477
463
|
// --- Flags / lifecycle ---
|
|
@@ -482,22 +468,20 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
482
468
|
});
|
|
483
469
|
|
|
484
470
|
pi.registerFlag("fff-frecency-db", {
|
|
485
|
-
description:
|
|
486
|
-
"Path to the frecency database (overrides FFF_FRECENCY_DB env)",
|
|
471
|
+
description: "Path to the frecency database (overrides FFF_FRECENCY_DB env)",
|
|
487
472
|
type: "string",
|
|
488
473
|
});
|
|
489
474
|
|
|
490
475
|
pi.registerFlag("fff-history-db", {
|
|
491
|
-
description:
|
|
492
|
-
"Path to the query history database (overrides FFF_HISTORY_DB env)",
|
|
476
|
+
description: "Path to the query history database (overrides FFF_HISTORY_DB env)",
|
|
493
477
|
type: "string",
|
|
494
478
|
});
|
|
495
479
|
|
|
496
480
|
pi.on("session_start", async (_event, ctx) => {
|
|
497
481
|
try {
|
|
498
482
|
activeCwd = ctx.cwd;
|
|
483
|
+
if (shouldEnableMentions()) applyEditorMode(ctx);
|
|
499
484
|
await ensureFinder(activeCwd);
|
|
500
|
-
applyEditorMode(ctx);
|
|
501
485
|
} catch (e: unknown) {
|
|
502
486
|
ctx.ui.notify(
|
|
503
487
|
`FFF init failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
@@ -519,20 +503,15 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
519
503
|
context: any,
|
|
520
504
|
maxLines = 15,
|
|
521
505
|
) => {
|
|
522
|
-
const text =
|
|
523
|
-
|
|
524
|
-
const output =
|
|
525
|
-
result.content?.find((c) => c.type === "text")?.text?.trim() ?? "";
|
|
506
|
+
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
507
|
+
const output = result.content?.find((c) => c.type === "text")?.text?.trim() ?? "";
|
|
526
508
|
if (!output) {
|
|
527
509
|
text.setText(theme.fg("muted", "No output"));
|
|
528
510
|
return text;
|
|
529
511
|
}
|
|
530
512
|
|
|
531
513
|
const lines = output.split("\n");
|
|
532
|
-
const displayLines = lines.slice(
|
|
533
|
-
0,
|
|
534
|
-
options.expanded ? lines.length : maxLines,
|
|
535
|
-
);
|
|
514
|
+
const displayLines = lines.slice(0, options.expanded ? lines.length : maxLines);
|
|
536
515
|
let content = `\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
|
|
537
516
|
if (lines.length > displayLines.length) {
|
|
538
517
|
content += theme.fg(
|
|
@@ -604,8 +583,7 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
604
583
|
// as a valid regex, otherwise plain literal. The fuzzy fallback below
|
|
605
584
|
// only kicks in for plain mode — regex queries are intentional.
|
|
606
585
|
const hasRegexSyntax =
|
|
607
|
-
params.pattern !==
|
|
608
|
-
params.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
586
|
+
params.pattern !== params.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
609
587
|
let mode: GrepMode = hasRegexSyntax ? "regex" : "plain";
|
|
610
588
|
if (mode === "regex") {
|
|
611
589
|
try {
|
|
@@ -677,14 +655,10 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
677
655
|
let output = formatGrepOutput(result);
|
|
678
656
|
const notices: string[] = [];
|
|
679
657
|
if (result.regexFallbackError) {
|
|
680
|
-
notices.push(
|
|
681
|
-
`Invalid regex: ${result.regexFallbackError}, used literal match`,
|
|
682
|
-
);
|
|
658
|
+
notices.push(`Invalid regex: ${result.regexFallbackError}, used literal match`);
|
|
683
659
|
}
|
|
684
660
|
if (result.nextCursor) {
|
|
685
|
-
notices.push(
|
|
686
|
-
`Continue with cursor="${storeCursor(result.nextCursor)}"`,
|
|
687
|
-
);
|
|
661
|
+
notices.push(`Continue with cursor="${storeCursor(result.nextCursor)}"`);
|
|
688
662
|
}
|
|
689
663
|
|
|
690
664
|
if (notices.length > 0) output += `\n\n[${notices.join(". ")}]`;
|
|
@@ -700,8 +674,7 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
700
674
|
},
|
|
701
675
|
|
|
702
676
|
renderCall(args, theme, context) {
|
|
703
|
-
const text =
|
|
704
|
-
(context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
677
|
+
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
705
678
|
const pattern = args?.pattern ?? "";
|
|
706
679
|
const path = args?.path ?? ".";
|
|
707
680
|
let content =
|
|
@@ -797,8 +770,7 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
797
770
|
// shown so far there's another page to fetch.
|
|
798
771
|
const shownSoFar = pageIndex * effectiveLimit + result.items.length;
|
|
799
772
|
const hasMore =
|
|
800
|
-
result.items.length >= effectiveLimit &&
|
|
801
|
-
result.totalMatched > shownSoFar;
|
|
773
|
+
result.items.length >= effectiveLimit && result.totalMatched > shownSoFar;
|
|
802
774
|
|
|
803
775
|
const notices: string[] = [];
|
|
804
776
|
if (formatted.weak && formatted.shownCount > 0)
|
|
@@ -832,8 +804,7 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
832
804
|
},
|
|
833
805
|
|
|
834
806
|
renderCall(args, theme, context) {
|
|
835
|
-
const text =
|
|
836
|
-
(context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
807
|
+
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
837
808
|
const pattern = args?.pattern ?? "";
|
|
838
809
|
const path = args?.path ?? ".";
|
|
839
810
|
let content =
|
|
@@ -866,9 +837,7 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
866
837
|
constraints: Type.Optional(
|
|
867
838
|
Type.String({ description: "File filter, e.g. '*.{ts,tsx} !test/'" }),
|
|
868
839
|
),
|
|
869
|
-
context: Type.Optional(
|
|
870
|
-
Type.Number({ description: "Context lines before+after" }),
|
|
871
|
-
),
|
|
840
|
+
context: Type.Optional(Type.Number({ description: "Context lines before+after" })),
|
|
872
841
|
limit: Type.Optional(
|
|
873
842
|
Type.Number({
|
|
874
843
|
description: `Max matches (default ${DEFAULT_GREP_LIMIT})`,
|
|
@@ -934,8 +903,7 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
934
903
|
},
|
|
935
904
|
|
|
936
905
|
renderCall(args, theme, context) {
|
|
937
|
-
const text =
|
|
938
|
-
(context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
906
|
+
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
939
907
|
const patterns = args?.patterns ?? [];
|
|
940
908
|
const constraints = args?.constraints;
|
|
941
909
|
let content =
|
|
@@ -957,8 +925,7 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
957
925
|
// --- commands ---
|
|
958
926
|
|
|
959
927
|
pi.registerCommand("fff-mode", {
|
|
960
|
-
description:
|
|
961
|
-
"Show or set FFF mode: /fff-mode [tools-and-ui | tools-only | override]",
|
|
928
|
+
description: "Show or set FFF mode: /fff-mode [tools-and-ui | tools-only | override]",
|
|
962
929
|
handler: async (args, ctx) => {
|
|
963
930
|
const arg = (args || "").trim();
|
|
964
931
|
|
|
@@ -967,19 +934,13 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
967
934
|
const mode = getMode();
|
|
968
935
|
const flag = pi.getFlag("fff-mode") ?? "unset";
|
|
969
936
|
const env = process.env.PI_FFF_MODE ?? "unset";
|
|
970
|
-
ctx.ui.notify(
|
|
971
|
-
`Current mode: '${mode}'\nFlag: ${flag}, Env: ${env}`,
|
|
972
|
-
"info",
|
|
973
|
-
);
|
|
937
|
+
ctx.ui.notify(`Current mode: '${mode}'\nFlag: ${flag}, Env: ${env}`, "info");
|
|
974
938
|
return;
|
|
975
939
|
}
|
|
976
940
|
|
|
977
941
|
// Validate and set mode
|
|
978
942
|
if (!VALID_MODES.includes(arg as FffMode)) {
|
|
979
|
-
ctx.ui.notify(
|
|
980
|
-
`Usage: /fff-mode [${VALID_MODES.join(" | ")}]`,
|
|
981
|
-
"warning",
|
|
982
|
-
);
|
|
943
|
+
ctx.ui.notify(`Usage: /fff-mode [${VALID_MODES.join(" | ")}]`, "warning");
|
|
983
944
|
return;
|
|
984
945
|
}
|
|
985
946
|
|