@d3ara1n/pi-hashline-edit 0.5.7 → 0.6.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/README.md +1 -0
- package/package.json +1 -1
- package/src/integration/grep-rg.test.ts +25 -0
- package/src/pi/grep-tool.ts +8 -4
- package/src/pi/grep.test.ts +13 -2
package/README.md
CHANGED
|
@@ -165,6 +165,7 @@ The `grep` override also covers the compound queries that otherwise push models
|
|
|
165
165
|
- `wordMatch` — whole words only (`rg -w`)
|
|
166
166
|
- `outputMode: "files"` / `"count"` — just the file paths (`rg -l`) or per-file counts + total (`grep -c`); `"files"` output pastes straight back as a `path` array
|
|
167
167
|
- `pattern` and `path` accept arrays — several patterns combined per `matchMode`, several search roots in one call
|
|
168
|
+
- `glob` accepts one pattern or an ordered array of ripgrep globs, e.g. `["*.ts", "*.md", "!**/*.test.ts"]` to include TypeScript and Markdown files but exclude tests
|
|
168
169
|
|
|
169
170
|
Filters run before the match limit counts, and context windows are rebuilt from surviving matches, so `limit` and `context` compose cleanly with `matchMode`/`excludePattern`.
|
|
170
171
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d3ara1n/pi-hashline-edit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Hashline-style file editing for pi — line-anchored edits verified by content hash, replacing oldText/newText matching",
|
|
6
6
|
"homepage": "https://github.com/d3ara1n/pi-extensions/tree/main/packages/pi-hashline-edit#readme",
|
|
@@ -46,6 +46,31 @@ test("real rg emits anchored matches from a temporary directory", {
|
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
test("real rg combines include and exclude globs in order", {
|
|
50
|
+
skip: rgPath === null,
|
|
51
|
+
}, async () => {
|
|
52
|
+
const directory = await mkdtemp(join(tmpdir(), "hl-grep-globs-"));
|
|
53
|
+
try {
|
|
54
|
+
for (const name of ["first.ts", "second.ts", "first.test.ts", "notes.md", "notes.txt"]) {
|
|
55
|
+
await writeFile(join(directory, name), "needle\n");
|
|
56
|
+
}
|
|
57
|
+
const tool = makeGrepOverrideWithBackend(directory, {
|
|
58
|
+
findRg: async () => rgPath,
|
|
59
|
+
delegate: async () => {
|
|
60
|
+
throw new Error("integration test must not invoke the built-in grep delegate");
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
const result: any = await tool.execute("0", {
|
|
64
|
+
pattern: "needle",
|
|
65
|
+
glob: ["*.ts", "*.md", "!**/*.test.ts"],
|
|
66
|
+
outputMode: "files",
|
|
67
|
+
}, undefined, undefined);
|
|
68
|
+
assert.deepEqual(result.content[0].text.split("\n").sort(), ["first.ts", "notes.md", "second.ts"]);
|
|
69
|
+
} finally {
|
|
70
|
+
await rm(directory, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
49
74
|
test("real rg and line filters honor explicit case settings and Unicode folding", {
|
|
50
75
|
skip: rgPath === null,
|
|
51
76
|
}, async () => {
|
package/src/pi/grep-tool.ts
CHANGED
|
@@ -140,7 +140,9 @@ const grepOverrideSchema = Type.Object({
|
|
|
140
140
|
"Directory or file to search (string or array of paths; default: current directory)",
|
|
141
141
|
})),
|
|
142
142
|
glob: Type.Optional(
|
|
143
|
-
Type.String(
|
|
143
|
+
Type.Union([Type.String(), Type.Array(Type.String())], {
|
|
144
|
+
description: "Filter files by glob pattern; pass an array for multiple filters and prefix exclusions with `!`, e.g. ['*.ts', '!**/*.test.ts']",
|
|
145
|
+
}),
|
|
144
146
|
),
|
|
145
147
|
ignoreCase: Type.Optional(
|
|
146
148
|
Type.Boolean({ description: "Case-insensitive search (default: false); applies to pattern and excludePattern." }),
|
|
@@ -340,7 +342,7 @@ export function makeGrepOverrideWithBackend(cwd: string, overrides: Partial<Grep
|
|
|
340
342
|
text += theme.fg("toolOutput", ` -v:${ex}`);
|
|
341
343
|
}
|
|
342
344
|
if (args?.wordMatch) text += theme.fg("toolOutput", " -w");
|
|
343
|
-
if (args?.glob) text += theme.fg("toolOutput", ` (${args.glob})`);
|
|
345
|
+
if (args?.glob) text += theme.fg("toolOutput", ` (${toArray(args.glob).join(", ")})`);
|
|
344
346
|
if (args?.outputMode && args.outputMode !== "content")
|
|
345
347
|
text += theme.fg("success", ` → ${args.outputMode}`);
|
|
346
348
|
if (args?.limit !== undefined) text += theme.fg("toolOutput", ` limit ${args.limit}`);
|
|
@@ -388,6 +390,7 @@ export function makeGrepOverrideWithBackend(cwd: string, overrides: Partial<Grep
|
|
|
388
390
|
params.excludePattern === undefined &&
|
|
389
391
|
params.outputMode === undefined &&
|
|
390
392
|
params.wordMatch === undefined &&
|
|
393
|
+
!Array.isArray(params.glob) &&
|
|
391
394
|
!Array.isArray(params.path);
|
|
392
395
|
|
|
393
396
|
const rgPath = await backend.findRg();
|
|
@@ -402,7 +405,8 @@ export function makeGrepOverrideWithBackend(cwd: string, overrides: Partial<Grep
|
|
|
402
405
|
const excludes = toArray(params.excludePattern);
|
|
403
406
|
const matchMode: "any" | "all" = params.matchMode ?? "any";
|
|
404
407
|
const outputMode: "content" | "files" | "count" = params.outputMode ?? "content";
|
|
405
|
-
const
|
|
408
|
+
const globs = toArray(params.glob);
|
|
409
|
+
const { ignoreCase, literal, wordMatch, limit } = params;
|
|
406
410
|
const searchPaths = (() => {
|
|
407
411
|
const raw = toArray(params.path);
|
|
408
412
|
return (raw.length ? raw : ["."]).map((p) => canonicalPath(cwd, p));
|
|
@@ -446,7 +450,7 @@ export function makeGrepOverrideWithBackend(cwd: string, overrides: Partial<Grep
|
|
|
446
450
|
if (ignoreCase) args.push("--ignore-case");
|
|
447
451
|
if (literal) args.push("--fixed-strings");
|
|
448
452
|
if (wordMatch) args.push("--word-regexp");
|
|
449
|
-
|
|
453
|
+
for (const glob of globs) args.push("--glob", glob);
|
|
450
454
|
for (const p of patterns) args.push("-e", p);
|
|
451
455
|
args.push("--", ...searchPaths);
|
|
452
456
|
|
package/src/pi/grep.test.ts
CHANGED
|
@@ -219,10 +219,13 @@ test("passes output flags and formats files and counts", async () => {
|
|
|
219
219
|
await writeFile(b, "foo a.b\n");
|
|
220
220
|
const fake = fakeBackend({ lines: [rgMatch(a, 1, "Foo a.b\n"), rgMatch(b, 1, "foo a.b\n")] });
|
|
221
221
|
|
|
222
|
-
const
|
|
222
|
+
const tool = makeGrepOverrideWithBackend(dir, fake.backend);
|
|
223
|
+
const globSchema: any = tool.parameters.properties.glob;
|
|
224
|
+
assert.deepEqual(globSchema.anyOf.map((option: any) => option.type), ["string", "array"]);
|
|
225
|
+
const files = await call(tool, {
|
|
223
226
|
pattern: ["Foo", "a.b"],
|
|
224
227
|
path: ["a.ts", "b.ts"],
|
|
225
|
-
glob: "*.ts",
|
|
228
|
+
glob: ["*.ts", "!**/*.test.ts"],
|
|
226
229
|
ignoreCase: true,
|
|
227
230
|
literal: true,
|
|
228
231
|
wordMatch: true,
|
|
@@ -239,6 +242,8 @@ test("passes output flags and formats files and counts", async () => {
|
|
|
239
242
|
"--word-regexp",
|
|
240
243
|
"--glob",
|
|
241
244
|
"*.ts",
|
|
245
|
+
"--glob",
|
|
246
|
+
"!**/*.test.ts",
|
|
242
247
|
"-e",
|
|
243
248
|
"Foo",
|
|
244
249
|
"-e",
|
|
@@ -337,6 +342,12 @@ test("delegates only safe fallbacks and rejects extended missing-rg requests", a
|
|
|
337
342
|
}),
|
|
338
343
|
/ripgrep \(rg\) not found/,
|
|
339
344
|
);
|
|
345
|
+
await call(tool, { pattern: "x", glob: "*.ts" });
|
|
346
|
+
assert.deepEqual(absent.delegates.at(-1)?.[1], { pattern: "x", glob: "*.ts" });
|
|
347
|
+
await assert.rejects(
|
|
348
|
+
call(tool, { pattern: "x", glob: ["*.ts", "!**/*.test.ts"] }),
|
|
349
|
+
/ripgrep \(rg\) not found/,
|
|
350
|
+
);
|
|
340
351
|
});
|
|
341
352
|
});
|
|
342
353
|
});
|