@ff-labs/pi-fff 0.10.7-nightly.e8d80e4 → 0.11.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 +124 -26
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.11.0",
|
|
5
5
|
"description": "pi extension: FFF-powered fuzzy file and content search",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"typecheck": "tsc --noEmit"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@ff-labs/fff-bun": "0.
|
|
45
|
-
"@ff-labs/fff-node": "0.
|
|
44
|
+
"@ff-labs/fff-bun": "0.11.0",
|
|
45
|
+
"@ff-labs/fff-node": "0.11.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@earendil-works/pi-coding-agent": "*",
|
package/src/index.ts
CHANGED
|
@@ -14,7 +14,10 @@ import type {
|
|
|
14
14
|
import {
|
|
15
15
|
type AutocompleteItem,
|
|
16
16
|
type AutocompleteProvider,
|
|
17
|
+
type Component,
|
|
18
|
+
sliceByColumn,
|
|
17
19
|
Text,
|
|
20
|
+
visibleWidth,
|
|
18
21
|
} from "@earendil-works/pi-tui";
|
|
19
22
|
import type {
|
|
20
23
|
FileFinderApi,
|
|
@@ -655,23 +658,51 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
655
658
|
};
|
|
656
659
|
|
|
657
660
|
const pendingTools: (() => string)[] = [];
|
|
661
|
+
const registeredToolNames = new Set<string>();
|
|
662
|
+
// A renderer is attached to a concrete registered name. Keep that name outside
|
|
663
|
+
// row state so old fffgrep rows retain their title after mode changes to override.
|
|
664
|
+
const renderToolNames = new WeakMap<object, string>();
|
|
658
665
|
let toolsRegistered = false;
|
|
659
666
|
|
|
667
|
+
function getRenderToolName(context: object, fallback: string): string {
|
|
668
|
+
return renderToolNames.get(context) ?? fallback;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function registerTool<TParams extends TSchema, TDetails = unknown, TState = any>(
|
|
672
|
+
resolveName: () => string,
|
|
673
|
+
definition: PendingToolDefinition<TParams, TDetails, TState>,
|
|
674
|
+
): string {
|
|
675
|
+
const resolvedName = resolveName();
|
|
676
|
+
if (registeredToolNames.has(resolvedName)) return resolvedName;
|
|
677
|
+
|
|
678
|
+
const { promptGuidelines, renderCall, ...tool } = definition;
|
|
679
|
+
pi.registerTool({
|
|
680
|
+
...tool,
|
|
681
|
+
name: resolvedName,
|
|
682
|
+
label: resolvedName,
|
|
683
|
+
promptGuidelines: promptGuidelines?.(toolNames),
|
|
684
|
+
renderCall: renderCall
|
|
685
|
+
? (args, theme, context) => {
|
|
686
|
+
renderToolNames.set(context, resolvedName);
|
|
687
|
+
return renderCall(args, theme, context);
|
|
688
|
+
}
|
|
689
|
+
: undefined,
|
|
690
|
+
});
|
|
691
|
+
registeredToolNames.add(resolvedName);
|
|
692
|
+
return resolvedName;
|
|
693
|
+
}
|
|
694
|
+
|
|
660
695
|
function queueTool<TParams extends TSchema, TDetails = unknown, TState = any>(
|
|
661
696
|
resolveName: () => string,
|
|
662
697
|
definition: PendingToolDefinition<TParams, TDetails, TState>,
|
|
663
698
|
): void {
|
|
664
|
-
pendingTools.push(() =>
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
promptGuidelines: promptGuidelines?.(toolNames),
|
|
672
|
-
});
|
|
673
|
-
return resolvedName;
|
|
674
|
-
});
|
|
699
|
+
pendingTools.push(() => registerTool(resolveName, definition));
|
|
700
|
+
|
|
701
|
+
// Pi restores historical tool rows before session_start. Register the
|
|
702
|
+
// FFF-named tools now so their renderers resolve. Pi activates every
|
|
703
|
+
// newly registered tool, so registerPendingTools prunes the names the
|
|
704
|
+
// final mode did not select.
|
|
705
|
+
registerTool(resolveName, definition);
|
|
675
706
|
}
|
|
676
707
|
|
|
677
708
|
// Pi carries the active tool list across /reload, so names activated under a
|
|
@@ -829,7 +860,72 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
829
860
|
|
|
830
861
|
// --- Shared render helpers ---
|
|
831
862
|
|
|
832
|
-
|
|
863
|
+
class CollapsedText implements Component {
|
|
864
|
+
constructor(
|
|
865
|
+
private readonly preview: string,
|
|
866
|
+
private readonly suffix: string,
|
|
867
|
+
private readonly marker: string,
|
|
868
|
+
) {}
|
|
869
|
+
|
|
870
|
+
render(width: number): string[] {
|
|
871
|
+
const availableWidth = Math.max(1, width);
|
|
872
|
+
if (!this.suffix) {
|
|
873
|
+
if (visibleWidth(this.preview) <= availableWidth) return [this.preview];
|
|
874
|
+
const markerWidth = visibleWidth(this.marker);
|
|
875
|
+
if (markerWidth >= availableWidth) {
|
|
876
|
+
return [sliceByColumn(this.marker, 0, availableWidth, true)];
|
|
877
|
+
}
|
|
878
|
+
return [
|
|
879
|
+
`${sliceByColumn(this.preview, 0, availableWidth - markerWidth, true)}${this.marker}`,
|
|
880
|
+
];
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const suffixWidth = visibleWidth(this.suffix);
|
|
884
|
+
if (suffixWidth >= availableWidth) {
|
|
885
|
+
return [sliceByColumn(this.suffix, 0, availableWidth, true)];
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const previewWidth = availableWidth - suffixWidth - 1;
|
|
889
|
+
if (visibleWidth(this.preview) <= previewWidth) {
|
|
890
|
+
return [`${this.preview} ${this.suffix}`];
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const markerWidth = visibleWidth(this.marker);
|
|
894
|
+
return [
|
|
895
|
+
`${sliceByColumn(this.preview, 0, Math.max(0, previewWidth - markerWidth), true)}${this.marker} ${this.suffix}`,
|
|
896
|
+
];
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
invalidate(): void {}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Pi wraps returned components in its own click-to-expand MouseRegion and passes
|
|
903
|
+
// the toggled state via options.expanded, so no custom mouse handling is needed.
|
|
904
|
+
const renderCompactTextResult = (
|
|
905
|
+
result: { content?: { type: string; text?: string }[] },
|
|
906
|
+
options: { expanded?: boolean },
|
|
907
|
+
theme: any,
|
|
908
|
+
context: any,
|
|
909
|
+
): Component => {
|
|
910
|
+
const output = result.content?.find((c) => c.type === "text")?.text?.trim() ?? "";
|
|
911
|
+
if (!output) return new Text(theme.fg("muted", "No output"), 0, 0);
|
|
912
|
+
|
|
913
|
+
const lines = output.split("\n");
|
|
914
|
+
const color = context.isError ? "error" : "toolOutput";
|
|
915
|
+
if (options.expanded) {
|
|
916
|
+
return new Text(lines.map((line) => theme.fg(color, line)).join("\n"), 0, 0);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const suffix =
|
|
920
|
+
lines.length > 1 ? theme.fg("muted", `... (${lines.length - 1} more lines)`) : "";
|
|
921
|
+
return new CollapsedText(
|
|
922
|
+
theme.fg(color, lines[0] ?? ""),
|
|
923
|
+
suffix,
|
|
924
|
+
theme.fg("muted", "..."),
|
|
925
|
+
);
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
const renderPreviewResult = (
|
|
833
929
|
result: { content?: { type: string; text?: string }[] },
|
|
834
930
|
options: { expanded?: boolean },
|
|
835
931
|
theme: any,
|
|
@@ -1036,23 +1132,24 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
1036
1132
|
},
|
|
1037
1133
|
|
|
1038
1134
|
renderCall(args, theme, context) {
|
|
1039
|
-
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
1040
1135
|
const pattern = args?.pattern ?? "";
|
|
1041
1136
|
const path = args?.path ?? ".";
|
|
1042
1137
|
let content =
|
|
1043
|
-
theme.fg("toolTitle", theme.bold(toolNames.grep)) +
|
|
1138
|
+
theme.fg("toolTitle", theme.bold(getRenderToolName(context, toolNames.grep))) +
|
|
1044
1139
|
" " +
|
|
1045
1140
|
theme.fg("accent", `/${pattern}/`) +
|
|
1046
1141
|
theme.fg("toolOutput", ` in ${path}`);
|
|
1047
|
-
|
|
1048
|
-
|
|
1142
|
+
const options: string[] = [];
|
|
1143
|
+
if (args?.limit !== undefined) options.push(`limit ${args.limit}`);
|
|
1144
|
+
if (args?.context !== undefined) options.push(`context ${args.context}`);
|
|
1145
|
+
if (options.length > 0)
|
|
1146
|
+
content += theme.fg("toolOutput", ` (${options.join(", ")})`);
|
|
1049
1147
|
if (args?.cursor) content += theme.fg("muted", ` (page)`);
|
|
1050
|
-
|
|
1051
|
-
return text;
|
|
1148
|
+
return new CollapsedText(content, "", theme.fg("muted", "..."));
|
|
1052
1149
|
},
|
|
1053
1150
|
|
|
1054
1151
|
renderResult(result, options, theme, context) {
|
|
1055
|
-
return
|
|
1152
|
+
return renderCompactTextResult(result, options, theme, context);
|
|
1056
1153
|
},
|
|
1057
1154
|
});
|
|
1058
1155
|
|
|
@@ -1179,23 +1276,21 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
1179
1276
|
},
|
|
1180
1277
|
|
|
1181
1278
|
renderCall(args, theme, context) {
|
|
1182
|
-
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
1183
1279
|
const pattern = args?.pattern ?? "";
|
|
1184
1280
|
const path = args?.path ?? ".";
|
|
1185
1281
|
let content =
|
|
1186
|
-
theme.fg("toolTitle", theme.bold(toolNames.find)) +
|
|
1282
|
+
theme.fg("toolTitle", theme.bold(getRenderToolName(context, toolNames.find))) +
|
|
1187
1283
|
" " +
|
|
1188
1284
|
theme.fg("accent", pattern) +
|
|
1189
1285
|
theme.fg("toolOutput", ` in ${path}`);
|
|
1190
1286
|
if (args?.limit !== undefined)
|
|
1191
1287
|
content += theme.fg("toolOutput", ` (limit ${args.limit})`);
|
|
1192
1288
|
if (args?.cursor) content += theme.fg("muted", ` (page)`);
|
|
1193
|
-
|
|
1194
|
-
return text;
|
|
1289
|
+
return new CollapsedText(content, "", theme.fg("muted", "..."));
|
|
1195
1290
|
},
|
|
1196
1291
|
|
|
1197
1292
|
renderResult(result, options, theme, context) {
|
|
1198
|
-
return
|
|
1293
|
+
return renderCompactTextResult(result, options, theme, context);
|
|
1199
1294
|
},
|
|
1200
1295
|
});
|
|
1201
1296
|
|
|
@@ -1287,7 +1382,10 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
1287
1382
|
const patterns = args?.patterns ?? [];
|
|
1288
1383
|
const constraints = args?.constraints;
|
|
1289
1384
|
let content =
|
|
1290
|
-
theme.fg(
|
|
1385
|
+
theme.fg(
|
|
1386
|
+
"toolTitle",
|
|
1387
|
+
theme.bold(getRenderToolName(context, toolNames.multiGrep)),
|
|
1388
|
+
) +
|
|
1291
1389
|
" " +
|
|
1292
1390
|
theme.fg("accent", patterns.map((p: string) => `"${p}"`).join(", "));
|
|
1293
1391
|
if (constraints) content += theme.fg("toolOutput", ` (${constraints})`);
|
|
@@ -1297,7 +1395,7 @@ export default function fffExtension(pi: ExtensionAPI) {
|
|
|
1297
1395
|
},
|
|
1298
1396
|
|
|
1299
1397
|
renderResult(result, options, theme, context) {
|
|
1300
|
-
return
|
|
1398
|
+
return renderPreviewResult(result, options, theme, context, 15);
|
|
1301
1399
|
},
|
|
1302
1400
|
});
|
|
1303
1401
|
} // end if (enableMultiGrep)
|