pi-readseek 0.4.8 → 0.4.10
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 +0 -4
- package/package.json +2 -2
- package/src/edit-diff.ts +1 -2
- package/src/edit.ts +2 -1
- package/src/grep.ts +20 -2
- package/src/map-cache.ts +7 -4
- package/src/path-utils.ts +4 -4
- package/src/read.ts +1 -1
- package/src/write.ts +7 -9
package/README.md
CHANGED
|
@@ -53,7 +53,3 @@ information.
|
|
|
53
53
|
|
|
54
54
|
The upstream `@jarkkojs/readseek` packages are licensed separately as
|
|
55
55
|
`Apache-2.0 AND LGPL-2.1-or-later`.
|
|
56
|
-
|
|
57
|
-
`readseek` is originally derived from the source code of
|
|
58
|
-
[`pi-hashline-readmap`](https://github.com/coctostan/pi-hashline-readmap).
|
|
59
|
-
The relevant copyrights have been retained in [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-readseek",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"description": "Pi extension for readseek-backed hash-anchored read/edit/grep, structural code maps, structural search, and file exploration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node": ">=20.0.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@jarkkojs/readseek": "^0.4.
|
|
42
|
+
"@jarkkojs/readseek": "^0.4.13",
|
|
43
43
|
"diff": "^8.0.3",
|
|
44
44
|
"ignore": "^7.0.5",
|
|
45
45
|
"picomatch": "^4.0.4",
|
package/src/edit-diff.ts
CHANGED
|
@@ -14,6 +14,7 @@ export function detectLineEnding(content: string): "\r\n" | "\n" {
|
|
|
14
14
|
lf++;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
if (lf === 0 && crlf === 0) return "\n";
|
|
17
18
|
if (lf === 0) return "\r\n";
|
|
18
19
|
if (crlf === 0) return "\n";
|
|
19
20
|
return crlf > lf ? "\r\n" : "\n";
|
|
@@ -36,11 +37,9 @@ export function stripBom(content: string): { bom: string; text: string } {
|
|
|
36
37
|
* These cause line-count mismatches between normalizeToLF and external tools (ripgrep, wc).
|
|
37
38
|
*/
|
|
38
39
|
export function hasBareCarriageReturn(content: string): boolean {
|
|
39
|
-
// Remove all \r\n first, then check if any \r remains
|
|
40
40
|
return content.replace(/\r\n/g, "").includes("\r");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
44
43
|
const SINGLE_QUOTES_RE = /[\u2018\u2019\u201A\u201B]/g;
|
|
45
44
|
const DOUBLE_QUOTES_RE = /[\u201C\u201D\u201E\u201F]/g;
|
|
46
45
|
const UNICODE_SPACES_RE = /[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g;
|
package/src/edit.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
|
|
1
|
+
import { readFile as fsReadFile, writeFile as fsWriteFile } from "node:fs/promises";
|
|
2
2
|
|
|
3
3
|
import { createPatch } from "diff";
|
|
4
4
|
import { withFileMutationQueue, type ExtensionAPI, type EditToolDetails, type ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
|
|
@@ -277,6 +277,7 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
|
|
|
277
277
|
const replaceSymbolRanges: { start: number; end: number }[] = [];
|
|
278
278
|
const rsProbeResults: { type: "ok"; content: string; replacement: string; warnings: string[]; range: { start: number; end: number } }[] = [];
|
|
279
279
|
for (const rs of replaceSymbolEdits) {
|
|
280
|
+
throwIfAborted(signal);
|
|
280
281
|
const probe = await replaceSymbol({
|
|
281
282
|
filePath: absolutePath,
|
|
282
283
|
content: originalNormalized,
|
package/src/grep.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFile as fsReadFile, stat as fsStat } from "fs/promises";
|
|
2
|
-
import path from "path";
|
|
1
|
+
import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import type { ExtensionAPI, ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { createGrepTool } from "@earendil-works/pi-coding-agent";
|
|
@@ -315,6 +315,24 @@ export async function executeGrep(opts: ExecuteGrepOptions): Promise<any> {
|
|
|
315
315
|
let parsedCount = 0;
|
|
316
316
|
let candidateUnparsedCount = 0;
|
|
317
317
|
const candidateLinePattern = /^.+(?::|-)\d+(?::|-)\s/;
|
|
318
|
+
// Pre-read all matched files with bounded concurrency so the processing
|
|
319
|
+
// loop below hits the cache on every getFileLines call instead of
|
|
320
|
+
// serialising disk I/O.
|
|
321
|
+
if (!p.summary) {
|
|
322
|
+
const pathsToRead = new Set<string>();
|
|
323
|
+
for (const line of textBlock.text.split("\n")) {
|
|
324
|
+
throwIfAborted(signal);
|
|
325
|
+
const parsed = parseGrepOutputLine(line);
|
|
326
|
+
if (parsed && Number.isFinite(parsed.lineNumber) && parsed.lineNumber >= 1) {
|
|
327
|
+
pathsToRead.add(toAbsolutePath(parsed.displayPath));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const CONCURRENCY = 8;
|
|
331
|
+
const pathList = [...pathsToRead];
|
|
332
|
+
for (let i = 0; i < pathList.length; i += CONCURRENCY) {
|
|
333
|
+
await Promise.all(pathList.slice(i, i + CONCURRENCY).map((p) => getFileLines(p)));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
318
336
|
|
|
319
337
|
const addSummaryMatch = (displayPath: string, absolutePath: string) => {
|
|
320
338
|
let group = groupsByPath.get(displayPath);
|
package/src/map-cache.ts
CHANGED
|
@@ -29,11 +29,15 @@ function getMapCacheState(): MapCacheGlobalState {
|
|
|
29
29
|
globalObject[MAP_CACHE_STATE_KEY] = state;
|
|
30
30
|
return state;
|
|
31
31
|
}
|
|
32
|
+
// Move an existing entry to the most-recently-used position (Map insertion-order tail).
|
|
33
|
+
function touchMapEntry<K, V>(map: Map<K, V>, key: K, value: V): void {
|
|
34
|
+
map.delete(key);
|
|
35
|
+
map.set(key, value);
|
|
36
|
+
}
|
|
32
37
|
|
|
33
38
|
function rememberInMemory(absPath: string, entry: CacheEntry): void {
|
|
34
39
|
const state = getMapCacheState();
|
|
35
|
-
|
|
36
|
-
state.cache.set(absPath, entry);
|
|
40
|
+
touchMapEntry(state.cache, absPath, entry);
|
|
37
41
|
if (state.cache.size > state.maxSize) {
|
|
38
42
|
const oldestKey = state.cache.keys().next().value;
|
|
39
43
|
if (oldestKey !== undefined) state.cache.delete(oldestKey);
|
|
@@ -55,8 +59,7 @@ export async function getOrGenerateMap(absPath: string): Promise<FileMap | null>
|
|
|
55
59
|
const state = getMapCacheState();
|
|
56
60
|
const cached = state.cache.get(absPath);
|
|
57
61
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
58
|
-
state.cache
|
|
59
|
-
state.cache.set(absPath, cached);
|
|
62
|
+
touchMapEntry(state.cache, absPath, cached);
|
|
60
63
|
return cached.map;
|
|
61
64
|
}
|
|
62
65
|
const inflight = state.inflight.get(absPath);
|
package/src/path-utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { isAbsolute, resolve as resolvePath } from "path";
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { isAbsolute, resolve as resolvePath } from "node:path";
|
|
3
3
|
|
|
4
4
|
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
5
5
|
|
|
@@ -13,8 +13,8 @@ function normalizeAtPrefix(filePath: string): string {
|
|
|
13
13
|
|
|
14
14
|
function expandPath(filePath: string): string {
|
|
15
15
|
const normalized = normalizeUnicodeSpaces(normalizeAtPrefix(filePath));
|
|
16
|
-
if (normalized === "~") return
|
|
17
|
-
if (normalized.startsWith("~/")) return
|
|
16
|
+
if (normalized === "~") return homedir();
|
|
17
|
+
if (normalized.startsWith("~/")) return homedir() + normalized.slice(1);
|
|
18
18
|
return normalized;
|
|
19
19
|
}
|
|
20
20
|
|
package/src/read.ts
CHANGED
package/src/write.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, relative } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { withFileMutationQueue, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -92,10 +92,9 @@ export interface WriteResult extends WriteDiffFields {
|
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function readPreviousTextForDiff(filePath: string): string {
|
|
95
|
+
async function readPreviousTextForDiff(filePath: string): Promise<string> {
|
|
96
96
|
try {
|
|
97
|
-
|
|
98
|
-
const previous = readFileSync(filePath);
|
|
97
|
+
const previous = await readFile(filePath);
|
|
99
98
|
if (looksLikeBinary(previous)) return "";
|
|
100
99
|
return previous.toString("utf-8");
|
|
101
100
|
} catch {
|
|
@@ -223,25 +222,24 @@ export async function executeWrite(opts: {
|
|
|
223
222
|
},
|
|
224
223
|
};
|
|
225
224
|
}
|
|
226
|
-
const previousContent = readPreviousTextForDiff(filePath);
|
|
227
|
-
const existedBeforeWrite =
|
|
225
|
+
const previousContent = await readPreviousTextForDiff(filePath);
|
|
226
|
+
const existedBeforeWrite = await access(filePath).then(() => true, () => false);
|
|
228
227
|
|
|
229
228
|
// Create parent directories
|
|
230
229
|
try {
|
|
231
|
-
|
|
230
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
232
231
|
} catch (err: any) {
|
|
233
232
|
err.__phase = "mkdir";
|
|
234
233
|
throw err;
|
|
235
234
|
}
|
|
236
235
|
// Write file
|
|
237
236
|
try {
|
|
238
|
-
|
|
237
|
+
await writeFile(filePath, content, "utf-8");
|
|
239
238
|
} catch (err: any) {
|
|
240
239
|
err.__phase = "write";
|
|
241
240
|
throw err;
|
|
242
241
|
}
|
|
243
242
|
|
|
244
|
-
|
|
245
243
|
// Binary detection
|
|
246
244
|
if (looksLikeBinary(Buffer.from(content, "utf-8"))) {
|
|
247
245
|
warnings.push("File content appears to be binary.");
|