@xynogen/pix-search 0.2.2 → 0.3.2
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/README.md +19 -3
- package/package.json +3 -2
- package/src/editor.ts +5 -3
- package/src/extension.ts +13 -5
- package/src/picker.ts +3 -0
- package/src/recency.ts +12 -28
- package/src/rg.ts +19 -10
package/README.md
CHANGED
|
@@ -32,6 +32,22 @@ boundary `@` opens the picker.
|
|
|
32
32
|
pi install npm:@xynogen/pix-search
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Standalone
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
> Standalone and opt-in. [`@xynogen/pix-core`](https://www.npmjs.com/package/@xynogen/pix-core) does not bundle it. It replaces Pi's built-in `@` autocomplete. It wraps the installed editor, so it works with `pix-display` in either load order.
|
|
36
|
+
|
|
37
|
+
## Full distro
|
|
38
|
+
|
|
39
|
+
This package is part of [Pix](https://github.com/xynogen/pix-mono). The installer sets up Pi and the full distro. See [Install](https://github.com/xynogen/pix-mono#install) for the notes for each OS.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Linux / macOS
|
|
43
|
+
curl -fsSL https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.sh | sh
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
# Windows
|
|
48
|
+
irm https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.ps1 | iex
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-search",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Pi extension — @ file picker overlay with fuzzy + git recency ranking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@vscode/ripgrep": "^1.18.0",
|
|
52
|
-
"@xynogen/pix-pretty": "^1.
|
|
52
|
+
"@xynogen/pix-pretty": "^1.29.0",
|
|
53
|
+
"@xynogen/pix-runtime": "^0.12.0"
|
|
53
54
|
}
|
|
54
55
|
}
|
package/src/editor.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/** Add the @ picker to an editor without replacing its paste state or rendering. */
|
|
2
2
|
import type { CustomEditor } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import type { TUI } from "@earendil-works/pi-tui";
|
|
4
|
+
import { chipTag } from "@xynogen/pix-pretty/chips";
|
|
5
|
+
import { getErrorMessage } from "@xynogen/pix-pretty/utils";
|
|
4
6
|
|
|
5
|
-
/** Picked paths
|
|
7
|
+
/** Picked paths use the shared `<path>` chip tag. pix-display renders it as a chip. */
|
|
6
8
|
export function pathToken(path: string): string {
|
|
7
|
-
return
|
|
9
|
+
return `${chipTag.path(path)} `;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export type OpenPicker = () => Promise<string | null>;
|
|
@@ -28,7 +30,7 @@ export function attachPicker(
|
|
|
28
30
|
editor.insertTextAtCursor(path ? pathToken(path) : "@");
|
|
29
31
|
} catch (error) {
|
|
30
32
|
editor.insertTextAtCursor("@");
|
|
31
|
-
onError(`File picker failed: ${
|
|
33
|
+
onError(`File picker failed: ${getErrorMessage(error)}`);
|
|
32
34
|
} finally {
|
|
33
35
|
tui.requestRender();
|
|
34
36
|
}
|
package/src/extension.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { CustomEditor, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { warnBinaryMissing } from "@xynogen/pix-pretty/tool-status";
|
|
11
|
+
import { showTransientMessage } from "@xynogen/pix-pretty/transient-error";
|
|
10
12
|
import { attachPicker } from "./editor.ts";
|
|
11
13
|
import { FilePicker } from "./picker.ts";
|
|
12
14
|
import { withDirectories } from "./rank.ts";
|
|
@@ -21,9 +23,12 @@ export default function (pi: ExtensionAPI): void {
|
|
|
21
23
|
// Warm the git-recency cache in the background; picker reads whatever is
|
|
22
24
|
// ready when it opens (empty until the first git log returns).
|
|
23
25
|
let recency: RecencyMap = new Map();
|
|
24
|
-
void loadRecency(cwd).then(
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
void loadRecency(cwd).then(
|
|
27
|
+
(map) => {
|
|
28
|
+
recency = map;
|
|
29
|
+
},
|
|
30
|
+
(err: unknown) => warnBinaryMissing(ctx.ui, err),
|
|
31
|
+
);
|
|
27
32
|
|
|
28
33
|
// Open the picker overlay and resolve with the chosen path (or null).
|
|
29
34
|
const openPicker = () =>
|
|
@@ -58,9 +63,12 @@ export default function (pi: ExtensionAPI): void {
|
|
|
58
63
|
// ponytail: cursor access requires CustomEditor. Keep other editors intact;
|
|
59
64
|
// add a public cursor adapter if a non-CustomEditor integration is needed.
|
|
60
65
|
if (editor instanceof CustomEditor) {
|
|
61
|
-
attachPicker(editor, tui, openPicker, (message) =>
|
|
66
|
+
attachPicker(editor, tui, openPicker, (message) =>
|
|
67
|
+
showTransientMessage(ctx.ui, message, "error"),
|
|
68
|
+
);
|
|
62
69
|
} else {
|
|
63
|
-
|
|
70
|
+
showTransientMessage(
|
|
71
|
+
ctx.ui,
|
|
64
72
|
"pix-search: @ picker requires a CustomEditor; existing editor kept.",
|
|
65
73
|
"warning",
|
|
66
74
|
);
|
package/src/picker.ts
CHANGED
|
@@ -233,6 +233,9 @@ export class FilePicker {
|
|
|
233
233
|
lines: rows,
|
|
234
234
|
color: (s) => theme.fg("accent", s),
|
|
235
235
|
fg: (s) => theme.fg("text", s),
|
|
236
|
+
// Solid modal fill so the terminal doesn't bleed through, matching the
|
|
237
|
+
// gate/confirm/ask overlays. Skipped when the theme lacks bg (mock themes).
|
|
238
|
+
bg: theme.bg ? (s) => theme.bg?.("customMessageBg", s) ?? s : undefined,
|
|
236
239
|
});
|
|
237
240
|
this.cachedWidth = width;
|
|
238
241
|
return this.cachedLines;
|
package/src/recency.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Cached per session, refreshed on demand.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { runGit } from "@xynogen/pix-runtime/os";
|
|
8
8
|
|
|
9
9
|
const MAX_COMMITS = 200;
|
|
10
10
|
const DECAY = 0.97; // exponential decay per commit position
|
|
@@ -28,32 +28,16 @@ export function buildRecencyScores(gitOutput: string): RecencyMap {
|
|
|
28
28
|
return scores;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Recency scores for files in `cwd` (empty outside a repo, on abort, or on
|
|
33
|
+
* git failure). A missing git binary rejects with BinaryMissingError so the
|
|
34
|
+
* caller can show the install hint once.
|
|
35
|
+
*/
|
|
31
36
|
export async function loadRecency(cwd: string, signal?: AbortSignal): Promise<RecencyMap> {
|
|
32
|
-
return new
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const child = execFile(
|
|
39
|
-
"git",
|
|
40
|
-
["log", "--name-only", "--pretty=format:", `-n${MAX_COMMITS}`, "--diff-filter=ACMR"],
|
|
41
|
-
{ cwd, maxBuffer: 2 * 1024 * 1024, signal },
|
|
42
|
-
(err, stdout) => {
|
|
43
|
-
if (err || !stdout) {
|
|
44
|
-
resolve(new Map());
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
resolve(buildRecencyScores(stdout));
|
|
48
|
-
},
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
signal?.addEventListener(
|
|
52
|
-
"abort",
|
|
53
|
-
() => {
|
|
54
|
-
child.kill("SIGKILL");
|
|
55
|
-
},
|
|
56
|
-
{ once: true },
|
|
57
|
-
);
|
|
58
|
-
});
|
|
37
|
+
if (signal?.aborted) return new Map();
|
|
38
|
+
const stdout = await runGit(
|
|
39
|
+
["log", "--name-only", "--pretty=format:", `-n${MAX_COMMITS}`, "--diff-filter=ACMR"],
|
|
40
|
+
{ cwd, signal, timeoutMs: 15_000, maxBuffer: 2 * 1024 * 1024 },
|
|
41
|
+
);
|
|
42
|
+
return stdout ? buildRecencyScores(stdout) : new Map();
|
|
59
43
|
}
|
package/src/rg.ts
CHANGED
|
@@ -7,16 +7,25 @@
|
|
|
7
7
|
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { basename } from "node:path";
|
|
10
|
+
import { resolveTool } from "@xynogen/pix-runtime/binaries";
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
/**
|
|
13
|
+
* rg resolution: pix-runtime (binary.json → <agentDir>/bin, where Pi downloads
|
|
14
|
+
* rg → PATH). `@vscode/ripgrep`'s bundled copy is only the last fallback, for
|
|
15
|
+
* hosts where Pi has not fetched rg yet. Resolved lazily, once per process.
|
|
16
|
+
*/
|
|
17
|
+
let rgBin: string | undefined;
|
|
18
|
+
export function rgBinary(): string {
|
|
19
|
+
if (rgBin) return rgBin;
|
|
20
|
+
rgBin = resolveTool("rg")?.path;
|
|
21
|
+
if (!rgBin) {
|
|
22
|
+
try {
|
|
23
|
+
rgBin = (require("@vscode/ripgrep") as { rgPath: string }).rgPath;
|
|
24
|
+
} catch {
|
|
25
|
+
rgBin = "rg";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return rgBin;
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
const TIMEOUT_MS = 3_000;
|
|
@@ -28,7 +37,7 @@ function spawnRg(args: string[], cwd: string, signal: AbortSignal): Promise<stri
|
|
|
28
37
|
return;
|
|
29
38
|
}
|
|
30
39
|
|
|
31
|
-
const child = spawn(
|
|
40
|
+
const child = spawn(rgBinary(), args, {
|
|
32
41
|
cwd,
|
|
33
42
|
stdio: ["ignore", "pipe", "ignore"],
|
|
34
43
|
// ponytail: no env filtering — inherits .gitignore respect from rg defaults
|