pi-readseek 0.3.13 → 0.3.14

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-readseek",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
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": {
package/src/grep.ts CHANGED
@@ -69,8 +69,8 @@ interface GrepParams {
69
69
  scopeContext?: number | string;
70
70
  }
71
71
 
72
- const MATCH_LINE_RE = /^(.*):(\d+): (.*)$/;
73
- const CONTEXT_LINE_RE = /^(.*)-(\d+)- (.*)$/;
72
+ const MATCH_LINE_RE = /^(.*?):(\d+): (.*)$/;
73
+ const CONTEXT_LINE_RE = /^(.*?)-(\d+)- (.*)$/;
74
74
 
75
75
  function parseGrepOutputLine(line: string):
76
76
  | { kind: "match"; displayPath: string; lineNumber: number; text: string }
@@ -406,8 +406,8 @@ export function registerGrepTool(pi: ExtensionAPI, options: GrepToolOptions = {}
406
406
  fileCache.set(absolutePath, lines);
407
407
  return lines;
408
408
  } catch {
409
- fileCache.set(absolutePath, []);
410
- return [];
409
+ fileCache.set(absolutePath, undefined);
410
+ return undefined;
411
411
  }
412
412
  };
413
413
 
package/src/map-cache.ts CHANGED
@@ -11,6 +11,7 @@ export const MAP_CACHE_MAX_SIZE = 500;
11
11
 
12
12
  interface MapCacheGlobalState {
13
13
  cache: Map<string, CacheEntry>;
14
+ inflight: Map<string, Promise<FileMap | null>>;
14
15
  maxSize: number;
15
16
  }
16
17
 
@@ -20,6 +21,7 @@ function getMapCacheState(): MapCacheGlobalState {
20
21
  const globalObject = globalThis as any;
21
22
  globalObject[MAP_CACHE_STATE_KEY] ??= {
22
23
  cache: new Map<string, CacheEntry>(),
24
+ inflight: new Map<string, Promise<FileMap | null>>(),
23
25
  maxSize: MAP_CACHE_MAX_SIZE,
24
26
  } satisfies MapCacheGlobalState;
25
27
  return globalObject[MAP_CACHE_STATE_KEY] as MapCacheGlobalState;
@@ -54,9 +56,20 @@ export async function getOrGenerateMap(absPath: string): Promise<FileMap | null>
54
56
  state.cache.set(absPath, cached);
55
57
  return cached.map;
56
58
  }
57
- const map = await generateMap(absPath);
58
- rememberInMemory(absPath, { mtimeMs, map });
59
- return map;
59
+ const inflight = state.inflight.get(absPath);
60
+ if (inflight) return inflight;
61
+
62
+ const generation = (async () => {
63
+ const map = await generateMap(absPath);
64
+ rememberInMemory(absPath, { mtimeMs, map });
65
+ return map;
66
+ })()
67
+ .catch(() => null)
68
+ .finally(() => {
69
+ state.inflight.delete(absPath);
70
+ });
71
+ state.inflight.set(absPath, generation);
72
+ return generation;
60
73
  } catch {
61
74
  return null;
62
75
  }
@@ -170,6 +170,18 @@ interface RunReadseekOptions {
170
170
 
171
171
  async function runReadseekRaw(args: string[], options: RunReadseekOptions = {}): Promise<string> {
172
172
  return new Promise<string>((resolve, reject) => {
173
+ let settled = false;
174
+ const fail = (error: Error): void => {
175
+ if (settled) return;
176
+ settled = true;
177
+ reject(error);
178
+ };
179
+ const succeed = (value: string): void => {
180
+ if (settled) return;
181
+ settled = true;
182
+ resolve(value);
183
+ };
184
+
173
185
  const stdin = options.stdin;
174
186
  const stdio: StdioOptions = [stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"];
175
187
  const child = spawn(readseekBinaryPath(), args, { stdio, signal: options.signal });
@@ -178,7 +190,7 @@ async function runReadseekRaw(args: string[], options: RunReadseekOptions = {}):
178
190
  const childStdin = child.stdin;
179
191
  if (!childStdout || !childStderr) {
180
192
  child.kill();
181
- reject(new Error("readseek stdio streams are unavailable"));
193
+ fail(new Error("readseek stdio streams are unavailable"));
182
194
  return;
183
195
  }
184
196
  const stdoutChunks: Buffer[] = [];
@@ -186,32 +198,33 @@ async function runReadseekRaw(args: string[], options: RunReadseekOptions = {}):
186
198
  let stdoutBytes = 0;
187
199
 
188
200
  childStdout.on("data", (chunk: Buffer) => {
201
+ if (settled) return;
189
202
  stdoutBytes += chunk.length;
190
203
  if (stdoutBytes > 32 * 1024 * 1024) {
191
204
  child.kill();
192
- reject(new Error("readseek output exceeded 32 MiB"));
205
+ fail(new Error("readseek output exceeded 32 MiB"));
193
206
  return;
194
207
  }
195
208
  stdoutChunks.push(chunk);
196
209
  });
197
210
  childStderr.on("data", (chunk: Buffer) => stderrChunks.push(chunk));
198
- child.on("error", (error: any) => reject(error));
211
+ child.on("error", (error: any) => fail(error));
199
212
  if (stdin !== undefined) {
200
213
  if (!childStdin) {
201
214
  child.kill();
202
- reject(new Error("readseek stdin stream is unavailable"));
215
+ fail(new Error("readseek stdin stream is unavailable"));
203
216
  return;
204
217
  }
205
218
  childStdin.on("error", (error: any) => {
206
- if (error?.code !== "EPIPE") reject(error);
219
+ if (error?.code !== "EPIPE") fail(error);
207
220
  });
208
221
  childStdin.end(stdin, "utf-8");
209
222
  }
210
223
  child.on("close", (code) => {
211
224
  const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
212
225
  const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
213
- if (code === 0) resolve(stdout);
214
- else reject(new Error((stderr || `readseek exited with status ${code}`).replace(/^error:\s*/i, "")));
226
+ if (code === 0) succeed(stdout);
227
+ else fail(new Error((stderr || `readseek exited with status ${code}`).replace(/^error:\s*/i, "")));
215
228
  });
216
229
  });
217
230
  }