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 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.8",
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.11",
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
- if (state.cache.has(absPath)) state.cache.delete(absPath);
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.delete(absPath);
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 * as os from "os";
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 os.homedir();
17
- if (normalized.startsWith("~/")) return os.homedir() + normalized.slice(1);
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
@@ -1,4 +1,4 @@
1
- import { readFile as fsReadFile } from "fs/promises";
1
+ import { readFile as fsReadFile } from "node:fs/promises";
2
2
 
3
3
  import type { ExtensionAPI, ToolRenderResultOptions, AgentToolResult } from "@earendil-works/pi-coding-agent";
4
4
  import {
package/src/write.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
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
- if (!existsSync(filePath)) return "";
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 = existsSync(filePath);
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
- mkdirSync(dirname(filePath), { recursive: true });
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
- writeFileSync(filePath, content, "utf-8");
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.");