@myagentroam/node 0.9.97 → 0.9.99
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/dist/ctags-binary.js +10 -0
- package/dist/service/code-navigation/c-family-path-specifier-resolver.js +40 -0
- package/dist/service/code-navigation/csharp-path-specifier-resolver.js +32 -0
- package/dist/service/code-navigation/ctags-index-storage.js +290 -0
- package/dist/service/code-navigation/ctags-query.js +140 -0
- package/dist/service/code-navigation/ecmascript-path-specifier-resolver.js +100 -0
- package/dist/service/code-navigation/file-reference-parser.js +28 -0
- package/dist/service/code-navigation/go-path-specifier-resolver.js +31 -0
- package/dist/service/code-navigation/jvm-path-specifier-resolver.js +28 -0
- package/dist/service/code-navigation/markdown-path-specifier-resolver.js +80 -0
- package/dist/service/code-navigation/navigation-resolution-definition.js +1 -0
- package/dist/service/code-navigation/navigation-resolution-registry.js +36 -0
- package/dist/service/code-navigation/navigation-target-validator.js +121 -0
- package/dist/service/code-navigation/path-specifier-resolver.js +26 -0
- package/dist/service/code-navigation/python-path-specifier-resolver.js +41 -0
- package/dist/service/code-navigation/rust-path-specifier-resolver.js +31 -0
- package/dist/service/code-navigation/stylesheet-path-specifier-resolver.js +61 -0
- package/dist/service/workspace-code-navigation-service.js +82 -698
- package/package.json +4 -3
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
-
import { constants
|
|
4
|
-
import { access,
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { access, open, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
5
4
|
import { homedir } from 'node:os';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { relative, resolve, sep } from 'node:path';
|
|
6
|
+
import { isNavigationSymbol } from '@myagentroam/code-navigation';
|
|
7
|
+
import { supportsSymbolNavigationPath } from '@myagentroam/code-navigation/symbol-navigation';
|
|
8
|
+
import { workspaceFileReferenceSchema } from '@myagentroam/protocol';
|
|
9
|
+
import { bundledCtagsPath, BUNDLED_CTAGS_VERSION, repairBundledCtagsExecutable } from '../ctags-binary.js';
|
|
9
10
|
import { nodeLog } from '../operational.js';
|
|
10
11
|
import { normalizeWorkspacePath } from '../workspace.js';
|
|
12
|
+
import { pathSpecifierResolver } from './code-navigation/navigation-resolution-registry.js';
|
|
13
|
+
import { reparseFileReference } from './code-navigation/file-reference-parser.js';
|
|
14
|
+
import { acquireBuild, buildIncrementalTags, ensurePrivateDirectory, manifestEntry, readManifest, removeInactiveGenerations, runCtags, sourceDelta, sourceFingerprint, tagsAvailable, tagsPath } from './code-navigation/ctags-index-storage.js';
|
|
15
|
+
import { compareTagNames, firstTagOffset, parseTag, readTagLine } from './code-navigation/ctags-query.js';
|
|
16
|
+
import { compareTargets, containsSymbol, sourceFileFingerprint, targetPreview, validatePathTarget, validateSymbolTarget } from './code-navigation/navigation-target-validator.js';
|
|
11
17
|
const INDEX_SCHEMA_VERSION = 1;
|
|
12
18
|
const INDEX_OPTIONS_VERSION = 2;
|
|
13
|
-
const BUILD_TIMEOUT_MS = 120_000;
|
|
14
|
-
const BUILD_QUEUE_LIMIT = 4;
|
|
15
|
-
const BUILD_QUEUE_TIMEOUT_MS = 120_000;
|
|
16
19
|
const MAX_SOURCE_BYTES = 4 * 1024 * 1024;
|
|
17
20
|
const MAX_INDEX_BYTES = 8 * 1024 * 1024 * 1024;
|
|
18
21
|
const MAX_TAGS_BYTES = 512 * 1024 * 1024;
|
|
@@ -23,64 +26,6 @@ const MAX_TAG_LINE_BYTES = 16 * 1024;
|
|
|
23
26
|
const READ_BLOCK_BYTES = 4096;
|
|
24
27
|
const MAX_TARGET_LINE_SCAN_BYTES = MAX_SOURCE_BYTES;
|
|
25
28
|
const MAX_TARGET_PREVIEW_CHARACTERS = 512;
|
|
26
|
-
const NAVIGABLE_EXTENSIONS = new Set([
|
|
27
|
-
'.c',
|
|
28
|
-
'.cc',
|
|
29
|
-
'.cpp',
|
|
30
|
-
'.cs',
|
|
31
|
-
'.cxx',
|
|
32
|
-
'.dart',
|
|
33
|
-
'.ex',
|
|
34
|
-
'.exs',
|
|
35
|
-
'.fs',
|
|
36
|
-
'.fsx',
|
|
37
|
-
'.go',
|
|
38
|
-
'.h',
|
|
39
|
-
'.hh',
|
|
40
|
-
'.hpp',
|
|
41
|
-
'.hrl',
|
|
42
|
-
'.hs',
|
|
43
|
-
'.hxx',
|
|
44
|
-
'.java',
|
|
45
|
-
'.js',
|
|
46
|
-
'.jsx',
|
|
47
|
-
'.kt',
|
|
48
|
-
'.kts',
|
|
49
|
-
'.lua',
|
|
50
|
-
'.m',
|
|
51
|
-
'.mm',
|
|
52
|
-
'.php',
|
|
53
|
-
'.pl',
|
|
54
|
-
'.pm',
|
|
55
|
-
'.py',
|
|
56
|
-
'.pyw',
|
|
57
|
-
'.r',
|
|
58
|
-
'.rb',
|
|
59
|
-
'.rs',
|
|
60
|
-
'.scala',
|
|
61
|
-
'.scm',
|
|
62
|
-
'.sh',
|
|
63
|
-
'.sql',
|
|
64
|
-
'.swift',
|
|
65
|
-
'.ts',
|
|
66
|
-
'.tsx',
|
|
67
|
-
'.mts',
|
|
68
|
-
'.cts',
|
|
69
|
-
'.vb'
|
|
70
|
-
]);
|
|
71
|
-
const RELATIVE_SOURCE_SPECIFIER_HANDLERS = [
|
|
72
|
-
{ importExtension: '.js', sourceExtensions: ['.ts', '.tsx'] },
|
|
73
|
-
{ importExtension: '.jsx', sourceExtensions: ['.tsx'] },
|
|
74
|
-
{ importExtension: '.mjs', sourceExtensions: ['.mts'] },
|
|
75
|
-
{ importExtension: '.cjs', sourceExtensions: ['.cts'] }
|
|
76
|
-
];
|
|
77
|
-
const EXTENSIONLESS_RELATIVE_SOURCE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.py'];
|
|
78
|
-
const DOTTED_PACKAGE_SPECIFIER_HANDLERS = [
|
|
79
|
-
{ packageSuffixes: ['.java', '.kt', '.kts'] },
|
|
80
|
-
{ packageSuffixes: ['.py', '.pyw', '/__init__.py'] }
|
|
81
|
-
];
|
|
82
|
-
let activeBuilds = 0;
|
|
83
|
-
const buildWaiters = [];
|
|
84
29
|
/**
|
|
85
30
|
* Builds a demand-driven, standard u-ctags declaration index. It never keeps
|
|
86
31
|
* a complete symbol table, watcher, or idle child process in memory.
|
|
@@ -100,7 +45,7 @@ export class WorkspaceCodeNavigationService {
|
|
|
100
45
|
async prepare(workspace, input) {
|
|
101
46
|
if (typeof input.path !== 'string' || input.path.length === 0 || input.path.length > 4096)
|
|
102
47
|
throw new Error('SYMBOL_DEFINITION_INVALID');
|
|
103
|
-
if (!
|
|
48
|
+
if (!isCtagsSourcePath(input.path))
|
|
104
49
|
return { state: 'READY' };
|
|
105
50
|
if (!(await this.available()))
|
|
106
51
|
return { state: 'UNAVAILABLE' };
|
|
@@ -143,17 +88,23 @@ export class WorkspaceCodeNavigationService {
|
|
|
143
88
|
async definition(workspace, input) {
|
|
144
89
|
const path = input.path;
|
|
145
90
|
const symbol = input.symbol;
|
|
146
|
-
const
|
|
91
|
+
const reference = input.reference === undefined
|
|
92
|
+
? undefined
|
|
93
|
+
: workspaceFileReferenceSchema.safeParse(input.reference);
|
|
147
94
|
if (typeof path !== 'string' ||
|
|
148
95
|
path.length === 0 ||
|
|
149
96
|
path.length > 4096 ||
|
|
150
|
-
(typeof symbol === 'string' &&
|
|
151
|
-
(typeof symbol !== 'string' &&
|
|
152
|
-
(typeof symbol === 'string' && !
|
|
153
|
-
(
|
|
97
|
+
(typeof symbol === 'string' && reference !== undefined) ||
|
|
98
|
+
(typeof symbol !== 'string' && reference === undefined) ||
|
|
99
|
+
(typeof symbol === 'string' && !isNavigationSymbol(path, symbol)) ||
|
|
100
|
+
(reference !== undefined && !reference.success))
|
|
154
101
|
throw new Error('SYMBOL_DEFINITION_INVALID');
|
|
155
|
-
if (
|
|
156
|
-
|
|
102
|
+
if (reference !== undefined) {
|
|
103
|
+
const parsedReference = reparseFileReference(path, reference.data);
|
|
104
|
+
if (parsedReference === undefined)
|
|
105
|
+
return emptyDefinitionResult({ state: 'READY' });
|
|
106
|
+
return this.resolvePath(workspace, path, parsedReference);
|
|
107
|
+
}
|
|
157
108
|
const requestedSymbol = symbol;
|
|
158
109
|
if (!(await this.available()))
|
|
159
110
|
return emptyDefinitionResult({ state: 'UNAVAILABLE' });
|
|
@@ -247,12 +198,22 @@ export class WorkspaceCodeNavigationService {
|
|
|
247
198
|
this.forceFreshCandidates.set(workspaceId, (this.forceFreshCandidates.get(workspaceId) ?? 0) + 1);
|
|
248
199
|
}
|
|
249
200
|
async available() {
|
|
201
|
+
const executable = this.executable();
|
|
250
202
|
try {
|
|
251
|
-
await access(
|
|
203
|
+
await access(executable, constants.X_OK);
|
|
252
204
|
return true;
|
|
253
205
|
}
|
|
254
206
|
catch {
|
|
255
|
-
|
|
207
|
+
if (this.options.ctagsExecutable !== undefined)
|
|
208
|
+
return false;
|
|
209
|
+
try {
|
|
210
|
+
await repairBundledCtagsExecutable(this.options.platform ?? process.platform, this.options.architecture ?? process.arch);
|
|
211
|
+
await access(executable, constants.X_OK);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
256
217
|
}
|
|
257
218
|
}
|
|
258
219
|
executable() {
|
|
@@ -276,7 +237,7 @@ export class WorkspaceCodeNavigationService {
|
|
|
276
237
|
const entries = candidates
|
|
277
238
|
.filter((entry) => entry.size !== null &&
|
|
278
239
|
entry.size <= MAX_SOURCE_BYTES &&
|
|
279
|
-
|
|
240
|
+
isCtagsSourcePath(entry.path) &&
|
|
280
241
|
!entry.path.includes('\n') &&
|
|
281
242
|
!entry.path.includes('\r'))
|
|
282
243
|
.sort((left, right) => left.path.localeCompare(right.path));
|
|
@@ -494,7 +455,7 @@ export class WorkspaceCodeNavigationService {
|
|
|
494
455
|
}
|
|
495
456
|
const valid = await Promise.all(records.map(async (record) => ({
|
|
496
457
|
record,
|
|
497
|
-
preview: await
|
|
458
|
+
preview: await validateSymbolTarget(input.root, record, symbol, input.revalidateSourceFingerprint ? input.sourceFiles[record.path] : undefined)
|
|
498
459
|
})));
|
|
499
460
|
return valid
|
|
500
461
|
.flatMap(({ record, preview }) => (preview === undefined ? [] : [{ ...record, preview }]))
|
|
@@ -525,7 +486,17 @@ export class WorkspaceCodeNavigationService {
|
|
|
525
486
|
return { matches: [], truncated: false };
|
|
526
487
|
}
|
|
527
488
|
}
|
|
528
|
-
async resolvePath(workspace, currentPath,
|
|
489
|
+
async resolvePath(workspace, currentPath, reference) {
|
|
490
|
+
const direct = await this.resolveDirectPathTargets(workspace, currentPath, reference);
|
|
491
|
+
if (direct.length > 0)
|
|
492
|
+
return {
|
|
493
|
+
state: 'READY',
|
|
494
|
+
targets: [],
|
|
495
|
+
references: [],
|
|
496
|
+
referenceTruncated: false,
|
|
497
|
+
paths: direct,
|
|
498
|
+
truncated: false
|
|
499
|
+
};
|
|
529
500
|
const location = await this.indexLocation(workspace);
|
|
530
501
|
this.locations.set(workspace.id, location);
|
|
531
502
|
this.forgotten.delete(location.directory);
|
|
@@ -556,7 +527,7 @@ export class WorkspaceCodeNavigationService {
|
|
|
556
527
|
mustBuild: !current || forceRefreshGeneration !== undefined,
|
|
557
528
|
forceRefreshGeneration
|
|
558
529
|
});
|
|
559
|
-
const resolved = await this.resolvePathTargets(input, currentPath,
|
|
530
|
+
const resolved = await this.resolvePathTargets(input, currentPath, reference);
|
|
560
531
|
return {
|
|
561
532
|
state: stale ? 'STALE' : 'READY',
|
|
562
533
|
targets: [],
|
|
@@ -566,22 +537,42 @@ export class WorkspaceCodeNavigationService {
|
|
|
566
537
|
truncated: input.truncated || resolved.truncated
|
|
567
538
|
};
|
|
568
539
|
}
|
|
569
|
-
async
|
|
540
|
+
async resolveDirectPathTargets(workspace, currentPath, reference) {
|
|
541
|
+
const handler = pathSpecifierResolver(reference.languageId, reference.value);
|
|
542
|
+
const candidates = handler?.directCandidates?.(currentPath, reference.value) ?? [];
|
|
543
|
+
const targets = await Promise.all(candidates.slice(0, MAX_RESULTS).map(async (path) => {
|
|
544
|
+
const target = await validatePathTarget(workspace.path, path, undefined, reference.anchor, handler?.anchorLine);
|
|
545
|
+
return target === undefined
|
|
546
|
+
? undefined
|
|
547
|
+
: {
|
|
548
|
+
path,
|
|
549
|
+
line: target.line,
|
|
550
|
+
kind: 'file',
|
|
551
|
+
...(target.preview.length === 0 ? {} : { preview: targetPreview(target.preview) }),
|
|
552
|
+
scope: undefined
|
|
553
|
+
};
|
|
554
|
+
}));
|
|
555
|
+
return targets.flatMap((target) => (target === undefined ? [] : [target]));
|
|
556
|
+
}
|
|
557
|
+
async resolvePathTargets(input, currentPath, reference) {
|
|
558
|
+
const handler = pathSpecifierResolver(reference.languageId, reference.value);
|
|
559
|
+
if (handler === undefined)
|
|
560
|
+
return { targets: [], truncated: false };
|
|
570
561
|
const targets = input.entries
|
|
571
562
|
.map((entry) => entry.path)
|
|
572
|
-
.filter((path) =>
|
|
573
|
-
.sort((left, right) =>
|
|
574
|
-
|
|
563
|
+
.filter((path) => handler.matches(path, currentPath, reference.value))
|
|
564
|
+
.sort((left, right) => handler.score(left, currentPath, reference.value) -
|
|
565
|
+
handler.score(right, currentPath, reference.value))
|
|
575
566
|
.slice(0, MAX_RESULTS);
|
|
576
567
|
const resolved = await Promise.all(targets.map(async (path) => {
|
|
577
|
-
const
|
|
578
|
-
return
|
|
568
|
+
const target = await validatePathTarget(input.root, path, input.revalidateSourceFingerprint ? input.sourceFiles[path] : undefined, reference.anchor, handler.anchorLine);
|
|
569
|
+
return target === undefined
|
|
579
570
|
? undefined
|
|
580
571
|
: {
|
|
581
572
|
path,
|
|
582
|
-
line:
|
|
573
|
+
line: target.line,
|
|
583
574
|
kind: 'file',
|
|
584
|
-
...(preview.length === 0 ? {} : { preview: targetPreview(preview) }),
|
|
575
|
+
...(target.preview.length === 0 ? {} : { preview: targetPreview(target.preview) }),
|
|
585
576
|
scope: undefined
|
|
586
577
|
};
|
|
587
578
|
}));
|
|
@@ -602,615 +593,8 @@ function emptyDefinitionResult(input) {
|
|
|
602
593
|
...(input.failureCode === undefined ? {} : { failureCode: input.failureCode })
|
|
603
594
|
};
|
|
604
595
|
}
|
|
605
|
-
function
|
|
606
|
-
|
|
607
|
-
.filter(([path, fingerprint]) => previous[path] !== fingerprint)
|
|
608
|
-
.map(([path]) => path);
|
|
609
|
-
const removed = Object.keys(previous).filter((path) => current[path] === undefined);
|
|
610
|
-
return { changed, removed };
|
|
611
|
-
}
|
|
612
|
-
function manifestEntry(path, fingerprint) {
|
|
613
|
-
const match = /^([0-9]+):(-1|[0-9]+(?:\.[0-9]+)?)$/u.exec(fingerprint);
|
|
614
|
-
if (match === null)
|
|
615
|
-
return undefined;
|
|
616
|
-
const size = Number(match[1]);
|
|
617
|
-
const modifiedAt = match[2] === '-1' ? null : Number(match[2]);
|
|
618
|
-
if (!Number.isSafeInteger(size) ||
|
|
619
|
-
size < 0 ||
|
|
620
|
-
(modifiedAt !== null && !Number.isFinite(modifiedAt)))
|
|
621
|
-
return undefined;
|
|
622
|
-
return {
|
|
623
|
-
path,
|
|
624
|
-
name: path.slice(path.lastIndexOf('/') + 1),
|
|
625
|
-
kind: 'FILE',
|
|
626
|
-
size,
|
|
627
|
-
modifiedAt
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
function sourceFileFingerprint(size, modifiedAt) {
|
|
631
|
-
return `${size}:${modifiedAt ?? -1}`;
|
|
632
|
-
}
|
|
633
|
-
function sourceFingerprint(entries) {
|
|
634
|
-
const hash = createHash('sha256');
|
|
635
|
-
for (const entry of entries)
|
|
636
|
-
hash.update(`${entry.path}\0${sourceFileFingerprint(entry.size ?? -1, entry.modifiedAt)}\0`, 'utf8');
|
|
637
|
-
return hash.digest('hex');
|
|
638
|
-
}
|
|
639
|
-
async function buildIncrementalTags(executable, root, previousTags, delta, staging, destination) {
|
|
640
|
-
const changedTags = resolve(staging, 'changed.tags');
|
|
641
|
-
if (delta.changed.length > 0) {
|
|
642
|
-
const list = resolve(staging, 'changed.files.list');
|
|
643
|
-
await writeFile(list, delta.changed.join('\n'), { encoding: 'utf8', mode: 0o600 });
|
|
644
|
-
const release = await acquireBuild();
|
|
645
|
-
try {
|
|
646
|
-
const ctagsMs = await runCtags(executable, root, list, changedTags);
|
|
647
|
-
await mergeTags(previousTags, changedTags, new Set([...delta.changed, ...delta.removed]), destination);
|
|
648
|
-
return ctagsMs;
|
|
649
|
-
}
|
|
650
|
-
finally {
|
|
651
|
-
release();
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
else
|
|
655
|
-
await writeFile(changedTags, '', { encoding: 'utf8', mode: 0o600 });
|
|
656
|
-
await mergeTags(previousTags, changedTags, new Set([...delta.changed, ...delta.removed]), destination);
|
|
657
|
-
return 0;
|
|
658
|
-
}
|
|
659
|
-
async function mergeTags(previous, changed, replacedPaths, destination) {
|
|
660
|
-
const output = createWriteStream(destination, { encoding: 'utf8', mode: 0o600 });
|
|
661
|
-
const oldLines = tagLines(previous)[Symbol.asyncIterator]();
|
|
662
|
-
const changedLines = tagLines(changed)[Symbol.asyncIterator]();
|
|
663
|
-
const write = async (line) => {
|
|
664
|
-
if (!output.write(`${line}\n`))
|
|
665
|
-
await new Promise((resolvePromise) => output.once('drain', resolvePromise));
|
|
666
|
-
};
|
|
667
|
-
let old = await oldLines.next();
|
|
668
|
-
while (!old.done && old.value.startsWith('!_TAG_')) {
|
|
669
|
-
await write(old.value);
|
|
670
|
-
old = await oldLines.next();
|
|
671
|
-
}
|
|
672
|
-
let nextChanged = await nextDataTag(changedLines);
|
|
673
|
-
while (!old.done || nextChanged !== undefined) {
|
|
674
|
-
while (!old.done && tagPath(old.value) !== undefined && replacedPaths.has(tagPath(old.value)))
|
|
675
|
-
old = await oldLines.next();
|
|
676
|
-
if (old.done) {
|
|
677
|
-
await write(nextChanged);
|
|
678
|
-
nextChanged = await nextDataTag(changedLines);
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
if (nextChanged === undefined || compareTagLines(old.value, nextChanged) <= 0) {
|
|
682
|
-
await write(old.value);
|
|
683
|
-
old = await oldLines.next();
|
|
684
|
-
}
|
|
685
|
-
else {
|
|
686
|
-
await write(nextChanged);
|
|
687
|
-
nextChanged = await nextDataTag(changedLines);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
await new Promise((resolvePromise, reject) => {
|
|
691
|
-
output.once('error', reject);
|
|
692
|
-
output.end(resolvePromise);
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
async function* tagLines(path) {
|
|
696
|
-
const input = createInterface({
|
|
697
|
-
input: createReadStream(path, { encoding: 'utf8' }),
|
|
698
|
-
crlfDelay: Infinity
|
|
699
|
-
});
|
|
700
|
-
for await (const line of input)
|
|
701
|
-
yield line;
|
|
702
|
-
}
|
|
703
|
-
async function nextDataTag(iterator) {
|
|
704
|
-
for (;;) {
|
|
705
|
-
const next = await iterator.next();
|
|
706
|
-
if (next.done)
|
|
707
|
-
return undefined;
|
|
708
|
-
if (!next.value.startsWith('!_TAG_'))
|
|
709
|
-
return next.value;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
function tagPath(line) {
|
|
713
|
-
const second = line.indexOf('\t');
|
|
714
|
-
if (second < 0)
|
|
715
|
-
return undefined;
|
|
716
|
-
const third = line.indexOf('\t', second + 1);
|
|
717
|
-
return third < 0 ? undefined : line.slice(second + 1, third);
|
|
718
|
-
}
|
|
719
|
-
function compareTagLines(left, right) {
|
|
720
|
-
const leftName = left.slice(0, left.indexOf('\t'));
|
|
721
|
-
const rightName = right.slice(0, right.indexOf('\t'));
|
|
722
|
-
return (compareTagNames(leftName, rightName) || Buffer.compare(Buffer.from(left), Buffer.from(right)));
|
|
723
|
-
}
|
|
724
|
-
async function acquireBuild() {
|
|
725
|
-
if (activeBuilds >= 1) {
|
|
726
|
-
if (buildWaiters.length >= BUILD_QUEUE_LIMIT)
|
|
727
|
-
throw new Error('SYMBOL_INDEX_BUILD_FAILED');
|
|
728
|
-
await new Promise((resolvePromise, reject) => {
|
|
729
|
-
const resume = () => {
|
|
730
|
-
clearTimeout(timer);
|
|
731
|
-
resolvePromise();
|
|
732
|
-
};
|
|
733
|
-
const timer = setTimeout(() => {
|
|
734
|
-
const index = buildWaiters.indexOf(resume);
|
|
735
|
-
if (index >= 0)
|
|
736
|
-
buildWaiters.splice(index, 1);
|
|
737
|
-
reject(new Error('SYMBOL_INDEX_BUILD_FAILED'));
|
|
738
|
-
}, BUILD_QUEUE_TIMEOUT_MS);
|
|
739
|
-
timer.unref();
|
|
740
|
-
buildWaiters.push(resume);
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
activeBuilds += 1;
|
|
744
|
-
return () => {
|
|
745
|
-
activeBuilds -= 1;
|
|
746
|
-
buildWaiters.shift()?.();
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
async function runCtags(executable, cwd, fileList, destination) {
|
|
750
|
-
const startedAt = Date.now();
|
|
751
|
-
await new Promise((resolvePromise, reject) => {
|
|
752
|
-
const child = spawn(executable, [
|
|
753
|
-
'--options=NONE',
|
|
754
|
-
'--output-format=u-ctags',
|
|
755
|
-
'--excmd=number',
|
|
756
|
-
'--sort=yes',
|
|
757
|
-
'--tag-relative=no',
|
|
758
|
-
'--pseudo-tags=-TAG_PROC_CWD',
|
|
759
|
-
'--fields=+nKS',
|
|
760
|
-
'--extras=+q',
|
|
761
|
-
'-L',
|
|
762
|
-
fileList,
|
|
763
|
-
'-f',
|
|
764
|
-
destination
|
|
765
|
-
], {
|
|
766
|
-
cwd,
|
|
767
|
-
detached: process.platform !== 'win32',
|
|
768
|
-
windowsHide: true,
|
|
769
|
-
stdio: ['ignore', 'ignore', 'pipe']
|
|
770
|
-
});
|
|
771
|
-
let settled = false;
|
|
772
|
-
const settle = (callback) => {
|
|
773
|
-
if (settled)
|
|
774
|
-
return;
|
|
775
|
-
settled = true;
|
|
776
|
-
clearTimeout(timer);
|
|
777
|
-
callback();
|
|
778
|
-
};
|
|
779
|
-
const terminate = () => {
|
|
780
|
-
if (child.pid !== undefined && process.platform !== 'win32') {
|
|
781
|
-
try {
|
|
782
|
-
process.kill(-child.pid, 'SIGKILL');
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
|
-
catch {
|
|
786
|
-
// Fall through to the direct process kill.
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
child.kill('SIGKILL');
|
|
790
|
-
};
|
|
791
|
-
const timer = setTimeout(() => {
|
|
792
|
-
terminate();
|
|
793
|
-
settle(() => reject(new Error('SYMBOL_INDEX_BUILD_FAILED')));
|
|
794
|
-
}, BUILD_TIMEOUT_MS);
|
|
795
|
-
timer.unref();
|
|
796
|
-
child.stderr.on('data', () => undefined);
|
|
797
|
-
child.once('error', () => settle(() => reject(new Error('SYMBOL_INDEX_BUILD_FAILED'))));
|
|
798
|
-
child.once('exit', (code) => settle(() => (code === 0 ? resolvePromise() : reject(new Error('SYMBOL_INDEX_BUILD_FAILED')))));
|
|
799
|
-
});
|
|
800
|
-
return Date.now() - startedAt;
|
|
801
|
-
}
|
|
802
|
-
async function ensurePrivateDirectory(path) {
|
|
803
|
-
await mkdir(path, { recursive: true, mode: 0o700 });
|
|
804
|
-
const metadata = await lstat(path);
|
|
805
|
-
if (!metadata.isDirectory() || metadata.isSymbolicLink())
|
|
806
|
-
throw new Error('SYMBOL_INDEX_UNAVAILABLE');
|
|
807
|
-
}
|
|
808
|
-
async function removeInactiveGenerations(directory, activeGeneration) {
|
|
809
|
-
const generations = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
810
|
-
await Promise.all(generations
|
|
811
|
-
.filter((entry) => entry.isDirectory() &&
|
|
812
|
-
entry.name !== activeGeneration &&
|
|
813
|
-
/^[0-9a-f-]{36}(?:\.staging)?$/u.test(entry.name))
|
|
814
|
-
.map((entry) => rm(resolve(directory, entry.name), { recursive: true, force: true })));
|
|
815
|
-
}
|
|
816
|
-
async function readManifest(path) {
|
|
817
|
-
try {
|
|
818
|
-
const value = JSON.parse(await readFile(path, 'utf8'));
|
|
819
|
-
const sourceFiles = validManifestSourceFiles(value.sourceFiles) ? value.sourceFiles : undefined;
|
|
820
|
-
const schemaVersion = value.schemaVersion;
|
|
821
|
-
const optionsVersion = value.optionsVersion;
|
|
822
|
-
return typeof value === 'object' &&
|
|
823
|
-
value !== null &&
|
|
824
|
-
typeof schemaVersion === 'number' &&
|
|
825
|
-
Number.isInteger(schemaVersion) &&
|
|
826
|
-
schemaVersion >= 0 &&
|
|
827
|
-
typeof optionsVersion === 'number' &&
|
|
828
|
-
Number.isInteger(optionsVersion) &&
|
|
829
|
-
optionsVersion >= 0 &&
|
|
830
|
-
typeof value.ctagsVersion === 'string' &&
|
|
831
|
-
typeof value.sourceFingerprint === 'string' &&
|
|
832
|
-
/^[a-f0-9]{64}$/u.test(value.sourceFingerprint) &&
|
|
833
|
-
typeof value.truncated === 'boolean' &&
|
|
834
|
-
typeof value.generation === 'string' &&
|
|
835
|
-
/^[0-9a-f-]{36}$/u.test(value.generation)
|
|
836
|
-
? {
|
|
837
|
-
schemaVersion,
|
|
838
|
-
optionsVersion,
|
|
839
|
-
ctagsVersion: value.ctagsVersion,
|
|
840
|
-
sourceFingerprint: value.sourceFingerprint,
|
|
841
|
-
...(sourceFiles === undefined ? {} : { sourceFiles }),
|
|
842
|
-
truncated: value.truncated,
|
|
843
|
-
generation: value.generation
|
|
844
|
-
}
|
|
845
|
-
: undefined;
|
|
846
|
-
}
|
|
847
|
-
catch {
|
|
848
|
-
return undefined;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
function validManifestSourceFiles(value) {
|
|
852
|
-
if (value === undefined)
|
|
853
|
-
return false;
|
|
854
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
855
|
-
return false;
|
|
856
|
-
const entries = Object.entries(value);
|
|
857
|
-
return (entries.length <= 100_000 &&
|
|
858
|
-
entries.every(([path, fingerprint]) => path.length > 0 &&
|
|
859
|
-
path.length <= 4096 &&
|
|
860
|
-
!path.includes('\0') &&
|
|
861
|
-
!path.includes('\\') &&
|
|
862
|
-
!path.startsWith('/') &&
|
|
863
|
-
!path.split('/').some((part) => part.length === 0 || part === '.' || part === '..') &&
|
|
864
|
-
typeof fingerprint === 'string' &&
|
|
865
|
-
/^[0-9]+:(?:-1|[0-9]+(?:\.[0-9]+)?)$/u.test(fingerprint)));
|
|
866
|
-
}
|
|
867
|
-
async function tagsPath(location, manifest) {
|
|
868
|
-
const path = resolve(location.generations, manifest.generation, 'tags');
|
|
869
|
-
if (relative(location.generations, path)
|
|
870
|
-
.split(sep)
|
|
871
|
-
.some((part) => part === '..'))
|
|
872
|
-
throw new Error('SYMBOL_INDEX_UNAVAILABLE');
|
|
873
|
-
return path;
|
|
874
|
-
}
|
|
875
|
-
async function tagsAvailable(path) {
|
|
876
|
-
try {
|
|
877
|
-
const metadata = await stat(path);
|
|
878
|
-
return metadata.isFile() && metadata.size <= MAX_TAGS_BYTES;
|
|
879
|
-
}
|
|
880
|
-
catch {
|
|
881
|
-
return false;
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
function isNavigablePath(path) {
|
|
885
|
-
const dot = path.lastIndexOf('.');
|
|
886
|
-
return dot >= 0 && NAVIGABLE_EXTENSIONS.has(path.slice(dot).toLowerCase());
|
|
887
|
-
}
|
|
888
|
-
function isSymbol(value) {
|
|
889
|
-
return value.length > 0 && value.length <= 512 && /^[\p{L}_$][\p{L}\p{N}_$]*$/u.test(value);
|
|
890
|
-
}
|
|
891
|
-
function isNavigableSpecifier(value) {
|
|
892
|
-
return (value.length > 0 &&
|
|
893
|
-
value.length <= 1024 &&
|
|
894
|
-
(/^(?:\.\.?\/)[A-Za-z0-9_./$-]+$/u.test(value) ||
|
|
895
|
-
/^[\p{L}_$][\p{L}\p{N}_$]*(?:\.[\p{L}_$][\p{L}\p{N}_$]*)+$/u.test(value) ||
|
|
896
|
-
/^(?:@?[A-Za-z0-9_$-]+)(?:\/[A-Za-z0-9_.$-]+)+$/u.test(value)));
|
|
897
|
-
}
|
|
898
|
-
function matchesSpecifier(path, currentPath, specifier) {
|
|
899
|
-
if (specifier.startsWith('.')) {
|
|
900
|
-
const resolved = resolveRelativeSpecifier(currentPath, specifier);
|
|
901
|
-
return (resolved !== undefined &&
|
|
902
|
-
relativeSpecifierCandidates(resolved).some((candidate) => path === candidate ||
|
|
903
|
-
path === `${candidate}/index.ts` ||
|
|
904
|
-
path === `${candidate}/index.tsx` ||
|
|
905
|
-
path === `${candidate}/index.js` ||
|
|
906
|
-
path === `${candidate}/index.jsx` ||
|
|
907
|
-
path === `${candidate}/__init__.py`));
|
|
908
|
-
}
|
|
909
|
-
if (specifier.includes('/'))
|
|
910
|
-
return sourceFileStem(path) === sourceFileStem(specifier);
|
|
911
|
-
if (specifier.includes('.')) {
|
|
912
|
-
const packagePath = specifier.replaceAll('.', '/');
|
|
913
|
-
return DOTTED_PACKAGE_SPECIFIER_HANDLERS.some((handler) => handler.packageSuffixes.some((suffix) => matchesPackagePath(path, `${packagePath}${suffix}`)));
|
|
914
|
-
}
|
|
915
|
-
return sourceFileStem(path) === sourceFileStem(specifier);
|
|
916
|
-
}
|
|
917
|
-
function matchesPackagePath(path, packagePath) {
|
|
918
|
-
return path === packagePath || path.endsWith(`/${packagePath}`);
|
|
919
|
-
}
|
|
920
|
-
function pathTargetScore(path, currentPath, specifier) {
|
|
921
|
-
if (!specifier.startsWith('.'))
|
|
922
|
-
return path.length;
|
|
923
|
-
const resolved = resolveRelativeSpecifier(currentPath, specifier);
|
|
924
|
-
if (resolved === undefined)
|
|
925
|
-
return Number.MAX_SAFE_INTEGER;
|
|
926
|
-
const candidates = relativeSpecifierCandidates(resolved);
|
|
927
|
-
const exact = candidates.indexOf(path);
|
|
928
|
-
if (exact >= 0)
|
|
929
|
-
return exact;
|
|
930
|
-
const index = candidates.findIndex((candidate) => path === `${candidate}/index.ts` ||
|
|
931
|
-
path === `${candidate}/index.tsx` ||
|
|
932
|
-
path === `${candidate}/index.js` ||
|
|
933
|
-
path === `${candidate}/index.jsx` ||
|
|
934
|
-
path === `${candidate}/__init__.py`);
|
|
935
|
-
return index >= 0 ? candidates.length + index : Number.MAX_SAFE_INTEGER;
|
|
936
|
-
}
|
|
937
|
-
function resolveRelativeSpecifier(currentPath, specifier) {
|
|
938
|
-
const resolved = posix.normalize(posix.join(posix.dirname(currentPath), specifier));
|
|
939
|
-
if (resolved.startsWith('../') || resolved === '..')
|
|
940
|
-
return undefined;
|
|
941
|
-
return resolved.replace(/^\.\//u, '');
|
|
942
|
-
}
|
|
943
|
-
function relativeSpecifierCandidates(path) {
|
|
944
|
-
const handler = RELATIVE_SOURCE_SPECIFIER_HANDLERS.find(({ importExtension }) => path.toLowerCase().endsWith(importExtension));
|
|
945
|
-
if (handler !== undefined) {
|
|
946
|
-
const sourceBase = path.slice(0, -handler.importExtension.length);
|
|
947
|
-
return [path, ...handler.sourceExtensions.map((extension) => `${sourceBase}${extension}`)];
|
|
948
|
-
}
|
|
949
|
-
return path.slice(path.lastIndexOf('/') + 1).includes('.')
|
|
950
|
-
? [path]
|
|
951
|
-
: [path, ...EXTENSIONLESS_RELATIVE_SOURCE_EXTENSIONS.map((extension) => `${path}${extension}`)];
|
|
952
|
-
}
|
|
953
|
-
function sourceFileStem(path) {
|
|
954
|
-
const name = path.slice(path.lastIndexOf('/') + 1);
|
|
955
|
-
return name.replace(/(?:\.d)?\.[^.]+$/u, '');
|
|
956
|
-
}
|
|
957
|
-
async function firstTagOffset(handle, size, symbol) {
|
|
958
|
-
let low = 0;
|
|
959
|
-
let high = size;
|
|
960
|
-
while (low < high) {
|
|
961
|
-
const middle = Math.floor((low + high) / 2);
|
|
962
|
-
const start = await findTagLineStart(handle, middle);
|
|
963
|
-
if (start === undefined) {
|
|
964
|
-
high = middle;
|
|
965
|
-
continue;
|
|
966
|
-
}
|
|
967
|
-
const line = await readTagLine(handle, start, size);
|
|
968
|
-
if (line === undefined) {
|
|
969
|
-
high = middle;
|
|
970
|
-
continue;
|
|
971
|
-
}
|
|
972
|
-
const tag = parseTag(line.text);
|
|
973
|
-
if (tag === undefined || compareTagNames(tag.name, symbol) < 0)
|
|
974
|
-
low = line.nextOffset;
|
|
975
|
-
else
|
|
976
|
-
high = line.start;
|
|
977
|
-
}
|
|
978
|
-
return low;
|
|
979
|
-
}
|
|
980
|
-
async function findTagLineStart(handle, offset) {
|
|
981
|
-
let cursor = offset;
|
|
982
|
-
let scanned = 0;
|
|
983
|
-
while (cursor > 0 && scanned < MAX_TAG_LINE_BYTES) {
|
|
984
|
-
const start = Math.max(0, cursor - Math.min(READ_BLOCK_BYTES, MAX_TAG_LINE_BYTES - scanned));
|
|
985
|
-
const chunk = Buffer.alloc(cursor - start);
|
|
986
|
-
const { bytesRead } = await handle.read(chunk, 0, chunk.length, start);
|
|
987
|
-
if (bytesRead === 0)
|
|
988
|
-
return undefined;
|
|
989
|
-
const newline = chunk.subarray(0, bytesRead).lastIndexOf(0x0a);
|
|
990
|
-
if (newline >= 0)
|
|
991
|
-
return start + newline + 1;
|
|
992
|
-
cursor = start;
|
|
993
|
-
scanned += bytesRead;
|
|
994
|
-
}
|
|
995
|
-
return cursor === 0 ? 0 : undefined;
|
|
996
|
-
}
|
|
997
|
-
async function readTagLine(handle, offset, size) {
|
|
998
|
-
if (offset >= size)
|
|
999
|
-
return undefined;
|
|
1000
|
-
let start = offset;
|
|
1001
|
-
if (start > 0) {
|
|
1002
|
-
const before = Buffer.alloc(1);
|
|
1003
|
-
await handle.read(before, 0, 1, start - 1);
|
|
1004
|
-
if (before[0] !== 0x0a) {
|
|
1005
|
-
const aligned = await findNewline(handle, start, size, MAX_TAG_LINE_BYTES);
|
|
1006
|
-
if (aligned === undefined)
|
|
1007
|
-
return undefined;
|
|
1008
|
-
start = aligned + 1;
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
if (start >= size)
|
|
1012
|
-
return undefined;
|
|
1013
|
-
const bytes = [];
|
|
1014
|
-
let total = 0;
|
|
1015
|
-
let cursor = start;
|
|
1016
|
-
while (cursor < size && total <= MAX_TAG_LINE_BYTES) {
|
|
1017
|
-
const chunk = Buffer.alloc(Math.min(READ_BLOCK_BYTES, size - cursor));
|
|
1018
|
-
const { bytesRead } = await handle.read(chunk, 0, chunk.length, cursor);
|
|
1019
|
-
if (bytesRead === 0)
|
|
1020
|
-
break;
|
|
1021
|
-
const content = chunk.subarray(0, bytesRead);
|
|
1022
|
-
const newline = content.indexOf(0x0a);
|
|
1023
|
-
const part = newline >= 0 ? content.subarray(0, newline) : content;
|
|
1024
|
-
total += part.length;
|
|
1025
|
-
if (total > MAX_TAG_LINE_BYTES)
|
|
1026
|
-
return undefined;
|
|
1027
|
-
bytes.push(part);
|
|
1028
|
-
if (newline >= 0)
|
|
1029
|
-
return {
|
|
1030
|
-
start,
|
|
1031
|
-
nextOffset: cursor + newline + 1,
|
|
1032
|
-
text: Buffer.concat(bytes, total).toString('utf8')
|
|
1033
|
-
};
|
|
1034
|
-
cursor += bytesRead;
|
|
1035
|
-
}
|
|
1036
|
-
return undefined;
|
|
1037
|
-
}
|
|
1038
|
-
async function findNewline(handle, offset, size, limit) {
|
|
1039
|
-
let cursor = offset;
|
|
1040
|
-
let remaining = limit;
|
|
1041
|
-
while (cursor < size && remaining > 0) {
|
|
1042
|
-
const chunk = Buffer.alloc(Math.min(READ_BLOCK_BYTES, size - cursor, remaining));
|
|
1043
|
-
const { bytesRead } = await handle.read(chunk, 0, chunk.length, cursor);
|
|
1044
|
-
if (bytesRead === 0)
|
|
1045
|
-
return undefined;
|
|
1046
|
-
const newline = chunk.subarray(0, bytesRead).indexOf(0x0a);
|
|
1047
|
-
if (newline >= 0)
|
|
1048
|
-
return cursor + newline;
|
|
1049
|
-
cursor += bytesRead;
|
|
1050
|
-
remaining -= bytesRead;
|
|
1051
|
-
}
|
|
1052
|
-
return undefined;
|
|
1053
|
-
}
|
|
1054
|
-
function parseTag(text) {
|
|
1055
|
-
if (text.length === 0 ||
|
|
1056
|
-
text.startsWith('!_TAG_') ||
|
|
1057
|
-
Buffer.byteLength(text) > MAX_TAG_LINE_BYTES)
|
|
1058
|
-
return undefined;
|
|
1059
|
-
const fields = text.split('\t');
|
|
1060
|
-
if (fields.length < 3)
|
|
1061
|
-
return undefined;
|
|
1062
|
-
const [name, path, excmd, ...extensions] = fields;
|
|
1063
|
-
if (name === undefined ||
|
|
1064
|
-
path === undefined ||
|
|
1065
|
-
excmd === undefined ||
|
|
1066
|
-
name.length === 0 ||
|
|
1067
|
-
path.length === 0 ||
|
|
1068
|
-
path.startsWith('/') ||
|
|
1069
|
-
path.includes('\\') ||
|
|
1070
|
-
path.split('/').some((part) => part === '' || part === '.' || part === '..'))
|
|
1071
|
-
return undefined;
|
|
1072
|
-
const match = /^([1-9][0-9]*);?"?$/u.exec(excmd);
|
|
1073
|
-
if (match === null)
|
|
1074
|
-
return undefined;
|
|
1075
|
-
const line = Number(match[1]);
|
|
1076
|
-
if (!Number.isSafeInteger(line) || line < 1)
|
|
1077
|
-
return undefined;
|
|
1078
|
-
let kind;
|
|
1079
|
-
let scope;
|
|
1080
|
-
for (const extension of extensions) {
|
|
1081
|
-
if (extension.length === 1)
|
|
1082
|
-
kind ??= extension;
|
|
1083
|
-
else if (extension.startsWith('kind:'))
|
|
1084
|
-
kind ??= extension.slice('kind:'.length);
|
|
1085
|
-
else if (extension.startsWith('scope:') ||
|
|
1086
|
-
extension.startsWith('class:') ||
|
|
1087
|
-
extension.startsWith('namespace:'))
|
|
1088
|
-
scope ??= extension.slice(extension.indexOf(':') + 1);
|
|
1089
|
-
}
|
|
1090
|
-
return { name, path, line, kind, scope };
|
|
1091
|
-
}
|
|
1092
|
-
async function validateTarget(root, target, symbol, expectedFingerprint) {
|
|
1093
|
-
const path = resolve(root, ...target.path.split('/'));
|
|
1094
|
-
const pathMetadata = await lstat(path).catch(() => undefined);
|
|
1095
|
-
if (pathMetadata === undefined ||
|
|
1096
|
-
!pathMetadata.isFile() ||
|
|
1097
|
-
pathMetadata.isSymbolicLink() ||
|
|
1098
|
-
pathMetadata.size > MAX_SOURCE_BYTES)
|
|
1099
|
-
return undefined;
|
|
1100
|
-
const actual = await realpath(path).catch(() => undefined);
|
|
1101
|
-
if (actual === undefined ||
|
|
1102
|
-
relative(root, actual)
|
|
1103
|
-
.split(sep)
|
|
1104
|
-
.some((part) => part === '..'))
|
|
1105
|
-
return undefined;
|
|
1106
|
-
const metadata = await lstat(actual).catch(() => undefined);
|
|
1107
|
-
if (metadata === undefined ||
|
|
1108
|
-
!metadata.isFile() ||
|
|
1109
|
-
metadata.isSymbolicLink() ||
|
|
1110
|
-
metadata.size > MAX_SOURCE_BYTES)
|
|
1111
|
-
return undefined;
|
|
1112
|
-
if (expectedFingerprint !== undefined &&
|
|
1113
|
-
sourceFileFingerprint(metadata.size, metadata.mtimeMs) !== expectedFingerprint)
|
|
1114
|
-
return undefined;
|
|
1115
|
-
const line = await lineAt(actual, target.line);
|
|
1116
|
-
return line !== undefined && containsSymbol(line, symbol) ? targetPreview(line) : undefined;
|
|
1117
|
-
}
|
|
1118
|
-
async function validatePathTarget(root, path, expectedFingerprint) {
|
|
1119
|
-
const requested = resolve(root, ...path.split('/'));
|
|
1120
|
-
const requestedMetadata = await lstat(requested).catch(() => undefined);
|
|
1121
|
-
if (requestedMetadata === undefined ||
|
|
1122
|
-
!requestedMetadata.isFile() ||
|
|
1123
|
-
requestedMetadata.isSymbolicLink() ||
|
|
1124
|
-
requestedMetadata.size > MAX_SOURCE_BYTES)
|
|
1125
|
-
return undefined;
|
|
1126
|
-
const actual = await realpath(requested).catch(() => undefined);
|
|
1127
|
-
if (actual === undefined ||
|
|
1128
|
-
relative(root, actual)
|
|
1129
|
-
.split(sep)
|
|
1130
|
-
.some((part) => part === '..'))
|
|
1131
|
-
return undefined;
|
|
1132
|
-
const metadata = await lstat(actual).catch(() => undefined);
|
|
1133
|
-
if (metadata === undefined ||
|
|
1134
|
-
!metadata.isFile() ||
|
|
1135
|
-
metadata.isSymbolicLink() ||
|
|
1136
|
-
metadata.size > MAX_SOURCE_BYTES ||
|
|
1137
|
-
(expectedFingerprint !== undefined &&
|
|
1138
|
-
sourceFileFingerprint(metadata.size, metadata.mtimeMs) !== expectedFingerprint))
|
|
1139
|
-
return undefined;
|
|
1140
|
-
return lineAt(actual, 1);
|
|
1141
|
-
}
|
|
1142
|
-
function targetPreview(line) {
|
|
1143
|
-
if (line.length <= MAX_TARGET_PREVIEW_CHARACTERS)
|
|
1144
|
-
return line;
|
|
1145
|
-
const end = line.charCodeAt(MAX_TARGET_PREVIEW_CHARACTERS - 1);
|
|
1146
|
-
const safeEnd = end >= 0xd800 && end <= 0xdbff
|
|
1147
|
-
? MAX_TARGET_PREVIEW_CHARACTERS - 1
|
|
1148
|
-
: MAX_TARGET_PREVIEW_CHARACTERS;
|
|
1149
|
-
return `${line.slice(0, safeEnd)}…`;
|
|
1150
|
-
}
|
|
1151
|
-
async function lineAt(path, targetLine) {
|
|
1152
|
-
const handle = await open(path, 'r');
|
|
1153
|
-
try {
|
|
1154
|
-
let offset = 0;
|
|
1155
|
-
let line = 1;
|
|
1156
|
-
let pending = Buffer.alloc(0);
|
|
1157
|
-
while (offset < MAX_TARGET_LINE_SCAN_BYTES) {
|
|
1158
|
-
const chunk = Buffer.alloc(READ_BLOCK_BYTES);
|
|
1159
|
-
const { bytesRead } = await handle.read(chunk, 0, chunk.length, offset);
|
|
1160
|
-
if (bytesRead === 0)
|
|
1161
|
-
return line === targetLine ? pending.toString('utf8') : undefined;
|
|
1162
|
-
offset += bytesRead;
|
|
1163
|
-
let content = pending.length === 0
|
|
1164
|
-
? chunk.subarray(0, bytesRead)
|
|
1165
|
-
: Buffer.concat([pending, chunk.subarray(0, bytesRead)]);
|
|
1166
|
-
let newline;
|
|
1167
|
-
while ((newline = content.indexOf(0x0a)) >= 0) {
|
|
1168
|
-
if (line === targetLine)
|
|
1169
|
-
return content.subarray(0, newline).toString('utf8');
|
|
1170
|
-
line += 1;
|
|
1171
|
-
content = content.subarray(newline + 1);
|
|
1172
|
-
}
|
|
1173
|
-
pending = content;
|
|
1174
|
-
}
|
|
1175
|
-
return line === targetLine ? pending.toString('utf8') : undefined;
|
|
1176
|
-
}
|
|
1177
|
-
finally {
|
|
1178
|
-
await handle.close();
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
function containsSymbol(line, symbol) {
|
|
1182
|
-
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
1183
|
-
return new RegExp(`(?:^|[^\\p{L}\\p{N}_$])${escaped}(?![\\p{L}\\p{N}_$])`, 'u').test(line);
|
|
1184
|
-
}
|
|
1185
|
-
function compareTagNames(left, right) {
|
|
1186
|
-
return Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8'));
|
|
1187
|
-
}
|
|
1188
|
-
function compareTargets(left, right, currentPath) {
|
|
1189
|
-
const leftScore = targetScore(left, currentPath);
|
|
1190
|
-
const rightScore = targetScore(right, currentPath);
|
|
1191
|
-
return (rightScore - leftScore ||
|
|
1192
|
-
left.path.localeCompare(right.path) ||
|
|
1193
|
-
left.line - right.line ||
|
|
1194
|
-
(left.kind ?? '').localeCompare(right.kind ?? ''));
|
|
1195
|
-
}
|
|
1196
|
-
function targetScore(target, currentPath) {
|
|
1197
|
-
if (target.path === currentPath)
|
|
1198
|
-
return 100;
|
|
1199
|
-
const currentDirectory = currentPath.includes('/')
|
|
1200
|
-
? currentPath.slice(0, currentPath.lastIndexOf('/'))
|
|
1201
|
-
: '';
|
|
1202
|
-
const targetDirectory = target.path.includes('/')
|
|
1203
|
-
? target.path.slice(0, target.path.lastIndexOf('/'))
|
|
1204
|
-
: '';
|
|
1205
|
-
return commonPathSegments(currentDirectory, targetDirectory);
|
|
1206
|
-
}
|
|
1207
|
-
function commonPathSegments(left, right) {
|
|
1208
|
-
const leftParts = left === '' ? [] : left.split('/');
|
|
1209
|
-
const rightParts = right === '' ? [] : right.split('/');
|
|
1210
|
-
let count = 0;
|
|
1211
|
-
while (count < leftParts.length && leftParts[count] === rightParts[count])
|
|
1212
|
-
count += 1;
|
|
1213
|
-
return count;
|
|
596
|
+
function isCtagsSourcePath(path) {
|
|
597
|
+
return supportsSymbolNavigationPath(path);
|
|
1214
598
|
}
|
|
1215
599
|
function buildFailure(error) {
|
|
1216
600
|
return error instanceof Error && error.message === 'SYMBOL_INDEX_LIMIT_EXCEEDED'
|