pi-readseek 0.3.8 → 0.3.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/package.json +2 -2
- package/src/edit-syntax-validate.ts +31 -16
- package/src/edit.ts +3 -13
- package/src/grep.ts +8 -72
- package/src/image-detect.ts +47 -0
- package/src/map-cache.ts +14 -87
- package/src/read.ts +20 -266
- package/src/readseek/mapper.ts +15 -39
- package/src/readseek-settings.ts +0 -13
- package/src/readseek-value.ts +36 -0
- package/src/sg.ts +13 -74
- package/src/tool-prompt-metadata.ts +1 -2
- package/src/tui-render-utils.ts +27 -0
- package/src/write.ts +0 -5
- package/src/binary-resolution.ts +0 -77
- package/src/find-parsers.ts +0 -89
- package/src/find-stat.ts +0 -36
- package/src/persistent-map-cache.ts +0 -230
- package/src/readseek/language-detect.ts +0 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-readseek",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.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.3.
|
|
42
|
+
"@jarkkojs/readseek": "^0.3.8",
|
|
43
43
|
"diff": "^8.0.3",
|
|
44
44
|
"ignore": "^7.0.5",
|
|
45
45
|
"picomatch": "^4.0.4",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Node as SyntaxNode, Parser as WasmParser, Tree } from "web-tree-sitter";
|
|
2
|
-
import {
|
|
3
|
-
import { getWasmParser } from "./readseek/parser-loader.js";
|
|
2
|
+
import { getWasmParser, type WasmLanguageId } from "./readseek/parser-loader.js";
|
|
4
3
|
import { reportParserError } from "./readseek/parser-errors.js";
|
|
5
4
|
|
|
6
5
|
export interface ValidateInput {
|
|
@@ -75,15 +74,31 @@ function dedupeSortLines(
|
|
|
75
74
|
return out.map((o) => o.key);
|
|
76
75
|
}
|
|
77
76
|
|
|
77
|
+
const EXTENSION_TO_LANGUAGE: Record<string, WasmLanguageId> = {
|
|
78
|
+
".rs": "rust",
|
|
79
|
+
".c": "cpp",
|
|
80
|
+
".cc": "cpp",
|
|
81
|
+
".cpp": "cpp",
|
|
82
|
+
".cxx": "cpp",
|
|
83
|
+
".h": "c-header",
|
|
84
|
+
".hpp": "c-header",
|
|
85
|
+
".hxx": "c-header",
|
|
86
|
+
".java": "java",
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
function detectWasmLang(filePath: string): WasmLanguageId | null {
|
|
90
|
+
for (const [ext, langId] of Object.entries(EXTENSION_TO_LANGUAGE)) {
|
|
91
|
+
if (filePath.toLowerCase().endsWith(ext)) return langId;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
export async function validateSyntaxRegression(
|
|
79
|
-
|
|
97
|
+
input: ValidateInput,
|
|
80
98
|
): Promise<ValidateResult | null> {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
const parser = await getWasmParser(lang.id);
|
|
99
|
+
const langId = detectWasmLang(input.filePath);
|
|
100
|
+
if (!langId) return null;
|
|
101
|
+
const parser = await getWasmParser(langId);
|
|
87
102
|
if (!parser) return null;
|
|
88
103
|
|
|
89
104
|
try {
|
|
@@ -109,12 +124,12 @@ export async function validateSyntaxRegression(
|
|
|
109
124
|
...afterStats.missing,
|
|
110
125
|
]);
|
|
111
126
|
return { errorLines, newErrorCount, newMissingCount };
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
} catch (err) {
|
|
128
|
+
reportParserError(`wasm:syntax-validate:${langId}:${err instanceof Error ? err.message : String(err)}`, err, {
|
|
129
|
+
context: `tree-sitter syntax validation failed for ${langId}`,
|
|
130
|
+
});
|
|
131
|
+
return null;
|
|
132
|
+
} finally {
|
|
133
|
+
parser.delete();
|
|
119
134
|
}
|
|
120
135
|
}
|
package/src/edit.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { resolveSyntaxValidateMode, type SyntaxValidateOptions } from "./syntax-
|
|
|
19
19
|
import { replaceSymbol } from "./replace-symbol.js";
|
|
20
20
|
import { buildEditPreviewKey, buildPendingEditPreviewData, resolvePendingDiffPreview, type PendingDiffPreviewResult } from "./pending-diff-preview.js";
|
|
21
21
|
import { buildDiffData, type DiffBlockRange } from "./diff-data.js";
|
|
22
|
-
import { clampLineToWidth, clampLinesToWidth,
|
|
22
|
+
import { clampLineToWidth, clampLinesToWidth, linkToolPath, resolveRenderResultContext, summaryLine } from "./tui-render-utils.js";
|
|
23
23
|
import { DiffPreviewComponent } from "./tui-diff-component.js";
|
|
24
24
|
|
|
25
25
|
import { resolveEditDiffDisplay } from "./readseek-settings.js";
|
|
@@ -86,11 +86,6 @@ type HashlineParams = Static<typeof hashlineEditSchema>;
|
|
|
86
86
|
const EDIT_PROMPT_METADATA = defineToolPromptMetadata({
|
|
87
87
|
promptUrl: new URL("../prompts/edit.md", import.meta.url),
|
|
88
88
|
promptSnippet: "Edit files using hash-verified anchors from read/grep/search/write",
|
|
89
|
-
promptGuidelines: [
|
|
90
|
-
"Use edit for changes to existing files; read or search first and copy fresh LINE:HASH anchors.",
|
|
91
|
-
"Prefer edit anchored set_line, replace_lines, and insert_after over shell rewrites.",
|
|
92
|
-
"Use edit replace only when anchored edits are impractical.",
|
|
93
|
-
],
|
|
94
89
|
});
|
|
95
90
|
|
|
96
91
|
function buildEditError(
|
|
@@ -644,13 +639,9 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
|
|
|
644
639
|
return textComponent;
|
|
645
640
|
},
|
|
646
641
|
renderResult(result: any, options: ToolRenderResultOptions, theme: any, ...rest: any[]) {
|
|
647
|
-
const
|
|
648
|
-
rest[0] ?? options ?? {};
|
|
649
|
-
const isPartial = context.isPartial ?? (options as any)?.isPartial ?? false;
|
|
650
|
-
const isError = context.isError ?? false;
|
|
642
|
+
const { isPartial, isError, expanded: baseExpanded, width, context } = resolveRenderResultContext(options, rest);
|
|
651
643
|
|
|
652
644
|
if (isPartial) {
|
|
653
|
-
const width = (context as any).width ?? (options as any)?.width;
|
|
654
645
|
return new Text(clampLinesToWidth([summaryLine("pending edit")], width).join("\n"), 0, 0);
|
|
655
646
|
}
|
|
656
647
|
|
|
@@ -678,8 +669,7 @@ export function registerEditTool(pi: ExtensionAPI, options: EditToolOptions = {}
|
|
|
678
669
|
semanticClassification: semanticClassification as any,
|
|
679
670
|
});
|
|
680
671
|
|
|
681
|
-
const expanded =
|
|
682
|
-
const width = (context as any).width ?? (options as any)?.width;
|
|
672
|
+
const expanded = baseExpanded || resolveEditDiffDisplay() === "expanded";
|
|
683
673
|
const diffData = (details as any).diffData;
|
|
684
674
|
const stats = diffData?.stats ?? { added: 0, removed: 0 };
|
|
685
675
|
let text = "";
|
package/src/grep.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
|
7
7
|
import { normalizeToLF, stripBom, hasBareCarriageReturn } from "./edit-diff.js";
|
|
8
8
|
import { looksLikeBinary } from "./binary-detect.js";
|
|
9
9
|
import { ensureHashInit, formatHashlineDisplay, escapeControlCharsForDisplay } from "./hashline.js";
|
|
10
|
-
import {
|
|
10
|
+
import { buildReadseekLine, buildToolErrorResult } from "./readseek-value.js";
|
|
11
11
|
import { buildGrepOutput } from "./grep-output.js";
|
|
12
12
|
|
|
13
13
|
import { getOrGenerateMap } from "./map-cache.js";
|
|
@@ -17,16 +17,11 @@ import { throwIfAborted } from "./runtime.js";
|
|
|
17
17
|
import { Text } from "@earendil-works/pi-tui";
|
|
18
18
|
import { formatGrepCallText, formatGrepResultText } from "./grep-render-helpers.js";
|
|
19
19
|
import { coerceObviousBase10Int } from "./coerce-obvious-int.js";
|
|
20
|
-
import { clampLineToWidth, clampLinesToWidth,
|
|
20
|
+
import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderToolLabel, resolveRenderResultContext, summaryLine } from "./tui-render-utils.js";
|
|
21
21
|
|
|
22
22
|
const GREP_PROMPT_METADATA = defineToolPromptMetadata({
|
|
23
23
|
promptUrl: new URL("../prompts/grep.md", import.meta.url),
|
|
24
24
|
promptSnippet: "Search file contents and return edit-ready hashline anchors",
|
|
25
|
-
promptGuidelines: [
|
|
26
|
-
"Use grep for text search across files instead of bash grep or rg.",
|
|
27
|
-
"Use grep summary mode when you only need matching files or counts.",
|
|
28
|
-
"Use search instead of grep when the query depends on code structure.",
|
|
29
|
-
],
|
|
30
25
|
});
|
|
31
26
|
|
|
32
27
|
const grepSchema = Type.Object({
|
|
@@ -314,76 +309,23 @@ export function registerGrepTool(pi: ExtensionAPI, options: GrepToolOptions = {}
|
|
|
314
309
|
const rawParams = params as GrepParams;
|
|
315
310
|
const context = coerceObviousBase10Int(rawParams.context, "context");
|
|
316
311
|
if (!context.ok) {
|
|
317
|
-
return
|
|
318
|
-
content: [{ type: "text", text: context.message }],
|
|
319
|
-
isError: true,
|
|
320
|
-
details: {
|
|
321
|
-
readseekValue: {
|
|
322
|
-
tool: "grep",
|
|
323
|
-
ok: false,
|
|
324
|
-
error: buildReadseekError("invalid-params-combo", context.message),
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
};
|
|
312
|
+
return buildToolErrorResult("grep", "invalid-params-combo", context.message);
|
|
328
313
|
}
|
|
329
314
|
const limit = coerceObviousBase10Int(rawParams.limit, "limit");
|
|
330
315
|
if (!limit.ok) {
|
|
331
|
-
return
|
|
332
|
-
content: [{ type: "text", text: limit.message }],
|
|
333
|
-
isError: true,
|
|
334
|
-
details: {
|
|
335
|
-
readseekValue: {
|
|
336
|
-
tool: "grep",
|
|
337
|
-
ok: false,
|
|
338
|
-
error: buildReadseekError("invalid-limit", limit.message),
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
};
|
|
316
|
+
return buildToolErrorResult("grep", "invalid-limit", limit.message);
|
|
342
317
|
}
|
|
343
318
|
const scopeContext = coerceObviousBase10Int(rawParams.scopeContext, "scopeContext");
|
|
344
319
|
if (!scopeContext.ok) {
|
|
345
|
-
return
|
|
346
|
-
content: [{ type: "text", text: scopeContext.message }],
|
|
347
|
-
isError: true,
|
|
348
|
-
details: {
|
|
349
|
-
readseekValue: {
|
|
350
|
-
tool: "grep",
|
|
351
|
-
ok: false,
|
|
352
|
-
error: buildReadseekError("invalid-params-combo", scopeContext.message),
|
|
353
|
-
},
|
|
354
|
-
},
|
|
355
|
-
};
|
|
320
|
+
return buildToolErrorResult("grep", "invalid-params-combo", scopeContext.message);
|
|
356
321
|
}
|
|
357
322
|
if (scopeContext.value !== undefined && rawParams.scope !== "symbol") {
|
|
358
323
|
const message = 'Invalid scopeContext: requires scope: "symbol". For normal surrounding-line context outside symbol scope, use the `context` parameter.';
|
|
359
|
-
return
|
|
360
|
-
content: [{
|
|
361
|
-
type: "text",
|
|
362
|
-
text: message,
|
|
363
|
-
}],
|
|
364
|
-
isError: true,
|
|
365
|
-
details: {
|
|
366
|
-
readseekValue: {
|
|
367
|
-
tool: "grep",
|
|
368
|
-
ok: false,
|
|
369
|
-
error: buildReadseekError("invalid-params-combo", message),
|
|
370
|
-
},
|
|
371
|
-
},
|
|
372
|
-
};
|
|
324
|
+
return buildToolErrorResult("grep", "invalid-params-combo", message);
|
|
373
325
|
}
|
|
374
326
|
if (scopeContext.value !== undefined && scopeContext.value < 0) {
|
|
375
327
|
const message = `Invalid scopeContext: expected a non-negative integer, received ${scopeContext.value}.`;
|
|
376
|
-
return
|
|
377
|
-
content: [{ type: "text", text: message }],
|
|
378
|
-
isError: true,
|
|
379
|
-
details: {
|
|
380
|
-
readseekValue: {
|
|
381
|
-
tool: "grep",
|
|
382
|
-
ok: false,
|
|
383
|
-
error: buildReadseekError("invalid-params-combo", message),
|
|
384
|
-
},
|
|
385
|
-
},
|
|
386
|
-
};
|
|
328
|
+
return buildToolErrorResult("grep", "invalid-params-combo", message);
|
|
387
329
|
}
|
|
388
330
|
const p: GrepParams = {
|
|
389
331
|
...rawParams,
|
|
@@ -716,13 +658,7 @@ if (p.scope === "symbol" && !summary) {
|
|
|
716
658
|
return new Text(clampLineToWidth(text, context.width), 0, 0);
|
|
717
659
|
},
|
|
718
660
|
renderResult(result: any, options: ToolRenderResultOptions, theme: any, ...rest: any[]) {
|
|
719
|
-
const
|
|
720
|
-
rest[0] ?? options ?? {};
|
|
721
|
-
const isPartial = context.isPartial ?? (options as any)?.isPartial ?? false;
|
|
722
|
-
const isError = context.isError ?? false;
|
|
723
|
-
const expanded = isRendererExpanded(options as any, context as any);
|
|
724
|
-
const cwd = context.cwd ?? process.cwd();
|
|
725
|
-
const width = (context as any).width ?? (options as any)?.width;
|
|
661
|
+
const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options, rest);
|
|
726
662
|
|
|
727
663
|
if (isPartial) return new Text(clampLinesToWidth([summaryLine("pending search")], width).join("\n"), 0, 0);
|
|
728
664
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
2
|
+
|
|
3
|
+
function startsWithBytes(buffer: Buffer, bytes: number[]): boolean {
|
|
4
|
+
return buffer.length >= bytes.length && bytes.every((byte, index) => buffer[index] === byte);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function startsWithAscii(buffer: Buffer, offset: number, text: string): boolean {
|
|
8
|
+
if (buffer.length < offset + text.length) return false;
|
|
9
|
+
for (let index = 0; index < text.length; index++) {
|
|
10
|
+
if (buffer[offset + index] !== text.charCodeAt(index)) return false;
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readUint32BE(buffer: Buffer, offset: number): number {
|
|
16
|
+
return (
|
|
17
|
+
((buffer[offset] ?? 0) * 0x1000000) +
|
|
18
|
+
((buffer[offset + 1] ?? 0) << 16) +
|
|
19
|
+
((buffer[offset + 2] ?? 0) << 8) +
|
|
20
|
+
(buffer[offset + 3] ?? 0)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isPng(buffer: Buffer): boolean {
|
|
25
|
+
return buffer.length >= 16 && readUint32BE(buffer, PNG_SIGNATURE.length) === 13 && startsWithAscii(buffer, 12, "IHDR");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isAnimatedPng(buffer: Buffer): boolean {
|
|
29
|
+
let offset = PNG_SIGNATURE.length;
|
|
30
|
+
while (offset + 8 <= buffer.length) {
|
|
31
|
+
const chunkLength = readUint32BE(buffer, offset);
|
|
32
|
+
const chunkTypeOffset = offset + 4;
|
|
33
|
+
if (startsWithAscii(buffer, chunkTypeOffset, "acTL")) return true;
|
|
34
|
+
if (startsWithAscii(buffer, chunkTypeOffset, "IDAT")) return false;
|
|
35
|
+
const nextOffset = offset + 8 + chunkLength + 4;
|
|
36
|
+
if (nextOffset <= offset || nextOffset > buffer.length) return false;
|
|
37
|
+
offset = nextOffset;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isSupportedImageBuffer(buffer: Buffer): boolean {
|
|
43
|
+
if (startsWithBytes(buffer, [0xff, 0xd8, 0xff])) return buffer[3] !== 0xf7;
|
|
44
|
+
if (startsWithBytes(buffer, PNG_SIGNATURE)) return isPng(buffer) && !isAnimatedPng(buffer);
|
|
45
|
+
if (startsWithAscii(buffer, 0, "GIF")) return true;
|
|
46
|
+
return startsWithAscii(buffer, 0, "RIFF") && startsWithAscii(buffer, 8, "WEBP");
|
|
47
|
+
}
|
package/src/map-cache.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
2
|
import type { FileMap } from "./readseek/types.js";
|
|
3
|
-
import { generateMap
|
|
4
|
-
|
|
5
|
-
computeKey,
|
|
6
|
-
contentHashFor64k,
|
|
7
|
-
readCached,
|
|
8
|
-
writeCached,
|
|
9
|
-
persistenceEnabled,
|
|
10
|
-
} from "./persistent-map-cache.js";
|
|
3
|
+
import { generateMap } from "./readseek/mapper.js";
|
|
4
|
+
|
|
11
5
|
interface CacheEntry {
|
|
12
6
|
mtimeMs: number;
|
|
13
|
-
contentHash: string;
|
|
14
7
|
map: FileMap | null;
|
|
15
8
|
}
|
|
9
|
+
|
|
16
10
|
export const MAP_CACHE_MAX_SIZE = 500;
|
|
17
11
|
|
|
18
12
|
interface MapCacheGlobalState {
|
|
@@ -41,21 +35,12 @@ function rememberInMemory(absPath: string, entry: CacheEntry): void {
|
|
|
41
35
|
}
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
async function stableContentHash(
|
|
45
|
-
absPath: string,
|
|
46
|
-
mtimeMs: number,
|
|
47
|
-
expectedHash: string,
|
|
48
|
-
): Promise<string | null> {
|
|
49
|
-
if (!expectedHash) return null;
|
|
50
|
-
const currentStat = await stat(absPath);
|
|
51
|
-
if (currentStat.mtimeMs !== mtimeMs) return null;
|
|
52
|
-
const currentHash = await contentHashFor64k(absPath);
|
|
53
|
-
if (!currentHash || currentHash !== expectedHash) return null;
|
|
54
|
-
return currentHash;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
38
|
/**
|
|
58
|
-
* Get or generate a structural file map, with mtime-based caching.
|
|
39
|
+
* Get or generate a structural file map, with mtime-based in-memory caching.
|
|
40
|
+
* Readseek already caches map results in `.readseek/maps/` with its own binary
|
|
41
|
+
* format. The in-memory layer here avoids redundant process spawns within a
|
|
42
|
+
* single session.
|
|
43
|
+
*
|
|
59
44
|
* Returns null on any failure — never throws.
|
|
60
45
|
*/
|
|
61
46
|
export async function getOrGenerateMap(absPath: string): Promise<FileMap | null> {
|
|
@@ -65,72 +50,14 @@ export async function getOrGenerateMap(absPath: string): Promise<FileMap | null>
|
|
|
65
50
|
const state = getMapCacheState();
|
|
66
51
|
const cached = state.cache.get(absPath);
|
|
67
52
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
state.cache.set(absPath, cached);
|
|
72
|
-
return cached.map;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (!persistenceEnabled()) {
|
|
76
|
-
const map = await generateMap(absPath);
|
|
77
|
-
const hash = await contentHashFor64k(absPath);
|
|
78
|
-
rememberInMemory(absPath, { mtimeMs, contentHash: hash, map });
|
|
79
|
-
return map;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
let preContentHash = "";
|
|
83
|
-
try {
|
|
84
|
-
preContentHash = await contentHashFor64k(absPath);
|
|
85
|
-
if (preContentHash) {
|
|
86
|
-
const key = computeKey(
|
|
87
|
-
absPath,
|
|
88
|
-
mtimeMs,
|
|
89
|
-
preContentHash,
|
|
90
|
-
READSEEK_MAPPER_IDENTITY.mapperName,
|
|
91
|
-
READSEEK_MAPPER_IDENTITY.mapperVersion,
|
|
92
|
-
);
|
|
93
|
-
const fromDisk = await readCached(key);
|
|
94
|
-
if (fromDisk) {
|
|
95
|
-
rememberInMemory(absPath, { mtimeMs, contentHash: preContentHash, map: fromDisk });
|
|
96
|
-
return fromDisk;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
} catch {
|
|
100
|
-
// fall through to regeneration on a disk-cache miss
|
|
101
|
-
}
|
|
102
|
-
const { map, mapperName, mapperVersion } = await generateMapWithIdentity(absPath);
|
|
103
|
-
const persistentIdentity = { mapperName, mapperVersion };
|
|
104
|
-
let stableHash: string | null = null;
|
|
105
|
-
let shouldRemember = true;
|
|
106
|
-
if (preContentHash) {
|
|
107
|
-
try {
|
|
108
|
-
stableHash = await stableContentHash(absPath, mtimeMs, preContentHash);
|
|
109
|
-
shouldRemember = stableHash !== null;
|
|
110
|
-
} catch {
|
|
111
|
-
shouldRemember = false;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (shouldRemember) {
|
|
115
|
-
const hash = stableHash ?? preContentHash ?? "";
|
|
116
|
-
rememberInMemory(absPath, { mtimeMs, contentHash: hash, map });
|
|
117
|
-
}
|
|
118
|
-
if (map && stableHash) {
|
|
119
|
-
try {
|
|
120
|
-
const key = computeKey(
|
|
121
|
-
absPath,
|
|
122
|
-
mtimeMs,
|
|
123
|
-
stableHash,
|
|
124
|
-
persistentIdentity.mapperName,
|
|
125
|
-
persistentIdentity.mapperVersion,
|
|
126
|
-
);
|
|
127
|
-
await writeCached(key, map);
|
|
128
|
-
} catch {
|
|
129
|
-
// never fail the caller on a cache-write miss
|
|
130
|
-
}
|
|
53
|
+
state.cache.delete(absPath);
|
|
54
|
+
state.cache.set(absPath, cached);
|
|
55
|
+
return cached.map;
|
|
131
56
|
}
|
|
57
|
+
const map = await generateMap(absPath);
|
|
58
|
+
rememberInMemory(absPath, { mtimeMs, map });
|
|
132
59
|
return map;
|
|
133
60
|
} catch {
|
|
134
61
|
return null;
|
|
135
62
|
}
|
|
136
|
-
}
|
|
63
|
+
}
|