@theia/ai-ide 1.72.0 → 1.72.2
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/lib/browser/workspace-functions.d.ts +27 -3
- package/lib/browser/workspace-functions.d.ts.map +1 -1
- package/lib/browser/workspace-functions.js +123 -140
- package/lib/browser/workspace-functions.js.map +1 -1
- package/lib/browser/workspace-functions.spec.js +323 -24
- package/lib/browser/workspace-functions.spec.js.map +1 -1
- package/package.json +24 -23
- package/src/browser/workspace-functions.spec.ts +385 -25
- package/src/browser/workspace-functions.ts +128 -195
|
@@ -20,6 +20,7 @@ import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
|
20
20
|
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
21
21
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
22
22
|
import { FileStat, FileOperationError, FileOperationResult } from '@theia/filesystem/lib/common/files';
|
|
23
|
+
import { FileSearchService } from '@theia/file-search/lib/common/file-search-service';
|
|
23
24
|
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
24
25
|
import {
|
|
25
26
|
FILE_CONTENT_FUNCTION_ID, GET_FILE_DIAGNOSTICS_ID,
|
|
@@ -59,7 +60,7 @@ export class WorkspaceFunctionScope {
|
|
|
59
60
|
@inject(EnvVariablesServer)
|
|
60
61
|
protected readonly envVariablesServer: EnvVariablesServer;
|
|
61
62
|
|
|
62
|
-
private gitignoreMatchers = new Map<string, ReturnType<typeof ignore
|
|
63
|
+
private gitignoreMatchers = new Map<string, ReturnType<typeof ignore> | undefined>();
|
|
63
64
|
private gitignoreWatchersInitialized = new Set<string>();
|
|
64
65
|
|
|
65
66
|
private _rootMapping: Map<string, URI> | undefined;
|
|
@@ -398,7 +399,9 @@ export class WorkspaceFunctionScope {
|
|
|
398
399
|
}
|
|
399
400
|
const uri = await this.toExternalUri(trimmed);
|
|
400
401
|
if (uri && uri.scheme === 'file') {
|
|
401
|
-
|
|
402
|
+
// Strip a trailing separator so an entry like `/foo/` still matches the
|
|
403
|
+
// directory `/foo` itself (URI.isEqualOrParent compares the last segment exactly).
|
|
404
|
+
result.push(WorkspaceFunctionScope.withoutTrailingSeparator(uri.normalizePath()));
|
|
402
405
|
}
|
|
403
406
|
}
|
|
404
407
|
return result;
|
|
@@ -473,6 +476,20 @@ export class WorkspaceFunctionScope {
|
|
|
473
476
|
return /^[A-Za-z]:\//.test(normalized);
|
|
474
477
|
}
|
|
475
478
|
|
|
479
|
+
/**
|
|
480
|
+
* Returns the URI without a trailing path separator (except for a root path). This makes
|
|
481
|
+
* directory comparisons via {@link URI.isEqualOrParent} insensitive to a trailing slash, so
|
|
482
|
+
* an allow-list entry such as `/foo/` matches the directory `/foo` itself, not only its
|
|
483
|
+
* children.
|
|
484
|
+
*/
|
|
485
|
+
static withoutTrailingSeparator(uri: URI): URI {
|
|
486
|
+
const path = uri.path.toString();
|
|
487
|
+
if (path.length > 1 && path.endsWith('/')) {
|
|
488
|
+
return uri.withPath(path.substring(0, path.length - 1));
|
|
489
|
+
}
|
|
490
|
+
return uri;
|
|
491
|
+
}
|
|
492
|
+
|
|
476
493
|
protected getHomeDirUri(): Promise<URI | undefined> {
|
|
477
494
|
if (!this.homeDirUri) {
|
|
478
495
|
this.homeDirUri = this.envVariablesServer.getHomeDirUri()
|
|
@@ -533,33 +550,43 @@ export class WorkspaceFunctionScope {
|
|
|
533
550
|
}
|
|
534
551
|
|
|
535
552
|
protected async isGitIgnored(stat: FileStat, workspaceRoot: URI): Promise<boolean> {
|
|
553
|
+
const matcher = await this.getGitignoreMatcher(workspaceRoot);
|
|
554
|
+
if (!matcher) {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
const relativePath = workspaceRoot.relative(stat.resource);
|
|
558
|
+
if (!relativePath) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
const relativePathStr = relativePath.toString() + (stat.isDirectory ? '/' : '');
|
|
562
|
+
return matcher.ignores(relativePathStr);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Returns the cached `.gitignore` matcher for the given root, reading the file at most
|
|
567
|
+
* once per root. A root with no (or unreadable) `.gitignore` is cached as `undefined`.
|
|
568
|
+
* The cache is invalidated by {@link initializeGitignoreWatcher} when the `.gitignore` is
|
|
569
|
+
* created, changed, or deleted, so individual exclusion checks need no filesystem RPC.
|
|
570
|
+
*/
|
|
571
|
+
protected async getGitignoreMatcher(workspaceRoot: URI): Promise<ReturnType<typeof ignore> | undefined> {
|
|
536
572
|
await this.initializeGitignoreWatcher(workspaceRoot);
|
|
537
573
|
|
|
538
574
|
const rootKey = workspaceRoot.toString();
|
|
539
|
-
|
|
575
|
+
if (this.gitignoreMatchers.has(rootKey)) {
|
|
576
|
+
return this.gitignoreMatchers.get(rootKey);
|
|
577
|
+
}
|
|
540
578
|
|
|
579
|
+
let matcher: ReturnType<typeof ignore> | undefined;
|
|
541
580
|
try {
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
if (!matcher) {
|
|
546
|
-
const gitignoreContent = await this.fileService.read(gitignoreUri);
|
|
547
|
-
matcher = ignore().add(gitignoreContent.value);
|
|
548
|
-
this.gitignoreMatchers.set(rootKey, matcher);
|
|
549
|
-
}
|
|
550
|
-
const relativePath = workspaceRoot.relative(stat.resource);
|
|
551
|
-
if (relativePath) {
|
|
552
|
-
const relativePathStr = relativePath.toString() + (stat.isDirectory ? '/' : '');
|
|
553
|
-
if (matcher.ignores(relativePathStr)) {
|
|
554
|
-
return true;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
581
|
+
const gitignoreUri = workspaceRoot.resolve(this.GITIGNORE_FILE_NAME);
|
|
582
|
+
const gitignoreContent = await this.fileService.read(gitignoreUri);
|
|
583
|
+
matcher = ignore().add(gitignoreContent.value);
|
|
558
584
|
} catch {
|
|
559
|
-
//
|
|
585
|
+
// No .gitignore (or it cannot be read): cache the absence so we don't retry on every check.
|
|
586
|
+
matcher = undefined;
|
|
560
587
|
}
|
|
561
|
-
|
|
562
|
-
return
|
|
588
|
+
this.gitignoreMatchers.set(rootKey, matcher);
|
|
589
|
+
return matcher;
|
|
563
590
|
}
|
|
564
591
|
}
|
|
565
592
|
|
|
@@ -653,17 +680,25 @@ export class GetWorkspaceDirectoryStructure implements ToolProvider {
|
|
|
653
680
|
const result: Record<string, unknown> = {};
|
|
654
681
|
|
|
655
682
|
if (stat && stat.isDirectory && stat.children) {
|
|
683
|
+
// Determine which child directories to include (the exclusion check may be async)...
|
|
684
|
+
const childDirs: URI[] = [];
|
|
656
685
|
for (const child of stat.children) {
|
|
657
686
|
if (cancellationToken?.isCancellationRequested) {
|
|
658
687
|
return { error: 'Operation cancelled by user' };
|
|
659
688
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
continue;
|
|
689
|
+
if (child.isDirectory && !(await this.workspaceScope.shouldExclude(child))) {
|
|
690
|
+
childDirs.push(child.resource);
|
|
663
691
|
}
|
|
664
|
-
const dirName = child.resource.path.base;
|
|
665
|
-
result[dirName] = await this.buildDirectoryStructure(child.resource, cancellationToken);
|
|
666
692
|
}
|
|
693
|
+
// ...then resolve their subtrees concurrently, so the traversal costs O(depth)
|
|
694
|
+
// round-trips instead of one serial round-trip per directory. Empty directories
|
|
695
|
+
// are preserved (they resolve to an empty object).
|
|
696
|
+
const subtrees = await Promise.all(
|
|
697
|
+
childDirs.map(childUri => this.buildDirectoryStructure(childUri, cancellationToken))
|
|
698
|
+
);
|
|
699
|
+
childDirs.forEach((childUri, index) => {
|
|
700
|
+
result[childUri.path.base] = subtrees[index];
|
|
701
|
+
});
|
|
667
702
|
}
|
|
668
703
|
|
|
669
704
|
return result;
|
|
@@ -1028,37 +1063,32 @@ export class GetWorkspaceFileList implements ToolProvider {
|
|
|
1028
1063
|
if (!stat || !stat.isDirectory) {
|
|
1029
1064
|
return JSON.stringify({ error: 'Directory not found' });
|
|
1030
1065
|
}
|
|
1031
|
-
return await this.listFilesDirectly(
|
|
1066
|
+
return await this.listFilesDirectly(stat, cancellationToken);
|
|
1032
1067
|
} catch (error) {
|
|
1033
1068
|
return JSON.stringify({ error: 'Directory not found' });
|
|
1034
1069
|
}
|
|
1035
1070
|
}
|
|
1036
1071
|
|
|
1037
|
-
private async listFilesDirectly(
|
|
1072
|
+
private async listFilesDirectly(stat: FileStat, cancellationToken?: CancellationToken): Promise<string> {
|
|
1038
1073
|
if (cancellationToken?.isCancellationRequested) {
|
|
1039
1074
|
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
1040
1075
|
}
|
|
1041
1076
|
|
|
1042
|
-
const stat = await this.fileService.resolve(uri);
|
|
1043
1077
|
const result: Record<string, 'directory' | 'file'> = {};
|
|
1044
1078
|
|
|
1045
|
-
if (
|
|
1046
|
-
|
|
1047
|
-
|
|
1079
|
+
if (await this.workspaceScope.shouldExclude(stat)) {
|
|
1080
|
+
return JSON.stringify(result);
|
|
1081
|
+
}
|
|
1082
|
+
// `stat` already carries one level of children from the caller's resolve, so no extra RPC.
|
|
1083
|
+
for (const child of stat.children ?? []) {
|
|
1084
|
+
if (cancellationToken?.isCancellationRequested) {
|
|
1085
|
+
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
1048
1086
|
}
|
|
1049
|
-
const children = await this.fileService.resolve(uri);
|
|
1050
|
-
if (children.children) {
|
|
1051
|
-
for (const child of children.children) {
|
|
1052
|
-
if (cancellationToken?.isCancellationRequested) {
|
|
1053
|
-
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
1054
|
-
}
|
|
1055
1087
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1059
|
-
result[child.resource.path.base] = child.isDirectory ? 'directory' : 'file';
|
|
1060
|
-
}
|
|
1088
|
+
if (await this.workspaceScope.shouldExclude(child)) {
|
|
1089
|
+
continue;
|
|
1061
1090
|
}
|
|
1091
|
+
result[child.resource.path.base] = child.isDirectory ? 'directory' : 'file';
|
|
1062
1092
|
}
|
|
1063
1093
|
|
|
1064
1094
|
return JSON.stringify(result);
|
|
@@ -1224,8 +1254,8 @@ export class FindFilesByPattern implements ToolProvider {
|
|
|
1224
1254
|
@inject(PreferenceService)
|
|
1225
1255
|
protected readonly preferences: PreferenceService;
|
|
1226
1256
|
|
|
1227
|
-
@inject(
|
|
1228
|
-
protected readonly
|
|
1257
|
+
@inject(FileSearchService)
|
|
1258
|
+
protected readonly fileSearchService: FileSearchService;
|
|
1229
1259
|
|
|
1230
1260
|
getTool(): ToolRequest {
|
|
1231
1261
|
return {
|
|
@@ -1239,8 +1269,6 @@ export class FindFilesByPattern implements ToolProvider {
|
|
|
1239
1269
|
'\'src/**/*.js\' for JavaScript files in the src directory. The function respects gitignore patterns and user exclusions, ' +
|
|
1240
1270
|
'returns workspace-relative paths (e.g., "my-project/src/index.ts") or absolute paths for external roots, ' +
|
|
1241
1271
|
'and limits results to 200 files maximum. ' +
|
|
1242
|
-
'Performance note: This traverses directories recursively which may be slow in large workspaces. ' +
|
|
1243
|
-
'For better performance, use specific subdirectory patterns (e.g., \'src/**/*.ts\' instead of \'**/*.ts\'). ' +
|
|
1244
1272
|
'Use this to find files by name/extension. Do NOT use this for searching file contents - use searchInWorkspace instead.',
|
|
1245
1273
|
parameters: {
|
|
1246
1274
|
type: 'object',
|
|
@@ -1249,8 +1277,7 @@ export class FindFilesByPattern implements ToolProvider {
|
|
|
1249
1277
|
type: 'string',
|
|
1250
1278
|
description: 'Glob pattern to match files against. ' +
|
|
1251
1279
|
'Examples: \'**/*.ts\' (all TypeScript files), \'src/**/*.js\' (JS files in src), ' +
|
|
1252
|
-
'\'**/*.{js,ts}\' (JS or TS files), \'**/test/**/*.spec.ts\' (test files).
|
|
1253
|
-
'Use specific subdirectory prefixes for better performance (e.g., \'packages/core/**/*.ts\' instead of \'**/*.ts\').'
|
|
1280
|
+
'\'**/*.{js,ts}\' (JS or TS files), \'**/test/**/*.spec.ts\' (test files).'
|
|
1254
1281
|
},
|
|
1255
1282
|
exclude: {
|
|
1256
1283
|
type: 'array',
|
|
@@ -1303,76 +1330,59 @@ export class FindFilesByPattern implements ToolProvider {
|
|
|
1303
1330
|
}
|
|
1304
1331
|
|
|
1305
1332
|
try {
|
|
1306
|
-
const patternMatcher = new Minimatch(pattern, { dot: false });
|
|
1307
|
-
const files: string[] = [];
|
|
1308
1333
|
const maxResults = 200;
|
|
1334
|
+
const useGitIgnore = this.preferences.get(CONSIDER_GITIGNORE_PREF, true);
|
|
1335
|
+
const userExcludes = this.preferences.get<string[]>(USER_EXCLUDE_PATTERN_PREF, []);
|
|
1336
|
+
const excludes = [...userExcludes, ...(excludePatterns ?? [])];
|
|
1309
1337
|
|
|
1338
|
+
// Resolve the set of roots to search and how each root's results should be rendered.
|
|
1339
|
+
const targets: { rootUri: URI; rootName?: string; external: boolean }[] = [];
|
|
1310
1340
|
if (searchRoot) {
|
|
1311
1341
|
const resolved = await this.workspaceScope.resolveToUri(searchRoot);
|
|
1312
1342
|
if (!resolved) {
|
|
1313
1343
|
return JSON.stringify({ error: `Invalid searchRoot: '${searchRoot}'` });
|
|
1314
1344
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
await this.workspaceScope.ensureAccessible(rootUri);
|
|
1318
|
-
|
|
1319
|
-
const ignorePatterns = isExternalRoot
|
|
1320
|
-
? this.preferences.get<string[]>(USER_EXCLUDE_PATTERN_PREF, [])
|
|
1321
|
-
: await this.buildIgnorePatterns(rootUri);
|
|
1322
|
-
const allExcludes = [...ignorePatterns];
|
|
1323
|
-
if (excludePatterns && excludePatterns.length > 0) {
|
|
1324
|
-
allExcludes.push(...excludePatterns);
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
if (cancellationToken?.isCancellationRequested) {
|
|
1328
|
-
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
const excludeMatchers = allExcludes.map(excludePattern => new Minimatch(excludePattern, { dot: true }));
|
|
1332
|
-
|
|
1333
|
-
await this.traverseDirectory(
|
|
1334
|
-
rootUri,
|
|
1335
|
-
rootUri,
|
|
1336
|
-
undefined,
|
|
1337
|
-
patternMatcher,
|
|
1338
|
-
excludeMatchers,
|
|
1339
|
-
files,
|
|
1340
|
-
maxResults,
|
|
1341
|
-
cancellationToken,
|
|
1342
|
-
isExternalRoot
|
|
1343
|
-
);
|
|
1345
|
+
await this.workspaceScope.ensureAccessible(resolved);
|
|
1346
|
+
targets.push({ rootUri: resolved, external: !this.workspaceScope.isInWorkspace(resolved) });
|
|
1344
1347
|
} else {
|
|
1345
1348
|
const rootMapping = this.workspaceScope.getRootMapping();
|
|
1346
1349
|
if (rootMapping.size === 0) {
|
|
1347
1350
|
return JSON.stringify({ error: 'No workspace has been opened yet' });
|
|
1348
1351
|
}
|
|
1349
|
-
|
|
1350
1352
|
for (const [rootName, rootUri] of rootMapping) {
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
if (files.length >= maxResults) {
|
|
1356
|
-
break;
|
|
1357
|
-
}
|
|
1353
|
+
targets.push({ rootUri, rootName, external: false });
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1358
1356
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1357
|
+
// Delegate the actual traversal to the backend ripgrep-based file search.
|
|
1358
|
+
// It runs natively on the backend filesystem (no per-directory RPC) and applies
|
|
1359
|
+
// include/exclude globs.
|
|
1360
|
+
const files: string[] = [];
|
|
1361
|
+
for (const target of targets) {
|
|
1362
|
+
if (cancellationToken?.isCancellationRequested) {
|
|
1363
|
+
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
1364
|
+
}
|
|
1365
|
+
if (files.length > maxResults) {
|
|
1366
|
+
break;
|
|
1367
|
+
}
|
|
1368
|
+
// `considerGitIgnore` is scoped to workspace roots (see its preference description),
|
|
1369
|
+
// so external allow-listed roots are searched with user/caller excludes only (plus
|
|
1370
|
+
// `.git`). Applying gitignore there would also leak the user's *global* gitignore
|
|
1371
|
+
// into an explicitly allow-listed directory and silently hide files.
|
|
1372
|
+
// Request one extra result across all roots so we can detect truncation.
|
|
1373
|
+
const matches = await this.fileSearchService.find('', {
|
|
1374
|
+
rootUris: [target.rootUri.toString()],
|
|
1375
|
+
includePatterns: [pattern],
|
|
1376
|
+
excludePatterns: target.external ? [...excludes, '.git'] : excludes,
|
|
1377
|
+
useGitIgnore: target.external ? false : useGitIgnore,
|
|
1378
|
+
fuzzyMatch: false,
|
|
1379
|
+
limit: maxResults - files.length + 1
|
|
1380
|
+
}, cancellationToken);
|
|
1381
|
+
for (const match of matches) {
|
|
1382
|
+
const display = this.toDisplayPath(new URI(match), target);
|
|
1383
|
+
if (display !== undefined) {
|
|
1384
|
+
files.push(display);
|
|
1363
1385
|
}
|
|
1364
|
-
const excludeMatchers = allExcludes.map(excludePattern => new Minimatch(excludePattern, { dot: true }));
|
|
1365
|
-
|
|
1366
|
-
await this.traverseDirectory(
|
|
1367
|
-
rootUri,
|
|
1368
|
-
rootUri,
|
|
1369
|
-
rootName,
|
|
1370
|
-
patternMatcher,
|
|
1371
|
-
excludeMatchers,
|
|
1372
|
-
files,
|
|
1373
|
-
maxResults,
|
|
1374
|
-
cancellationToken
|
|
1375
|
-
);
|
|
1376
1386
|
}
|
|
1377
1387
|
}
|
|
1378
1388
|
|
|
@@ -1380,12 +1390,10 @@ export class FindFilesByPattern implements ToolProvider {
|
|
|
1380
1390
|
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
1381
1391
|
}
|
|
1382
1392
|
|
|
1383
|
-
const result: { files: string[];
|
|
1393
|
+
const result: { files: string[]; truncated?: boolean } = {
|
|
1384
1394
|
files: files.slice(0, maxResults)
|
|
1385
1395
|
};
|
|
1386
|
-
|
|
1387
1396
|
if (files.length > maxResults) {
|
|
1388
|
-
result.totalFound = files.length;
|
|
1389
1397
|
result.truncated = true;
|
|
1390
1398
|
}
|
|
1391
1399
|
|
|
@@ -1396,94 +1404,19 @@ export class FindFilesByPattern implements ToolProvider {
|
|
|
1396
1404
|
}
|
|
1397
1405
|
}
|
|
1398
1406
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
const shouldConsiderGitIgnore = this.preferences.get(CONSIDER_GITIGNORE_PREF, false);
|
|
1408
|
-
if (shouldConsiderGitIgnore) {
|
|
1409
|
-
try {
|
|
1410
|
-
const gitignoreUri = workspaceRoot.resolve('.gitignore');
|
|
1411
|
-
const gitignoreContent = await this.fileService.read(gitignoreUri);
|
|
1412
|
-
const gitignoreLines = gitignoreContent.value
|
|
1413
|
-
.split('\n')
|
|
1414
|
-
.map(line => line.trim())
|
|
1415
|
-
.filter(line => line && !line.startsWith('#'));
|
|
1416
|
-
patterns.push(...gitignoreLines);
|
|
1417
|
-
} catch {
|
|
1418
|
-
// Gitignore file doesn't exist or can't be read, continue without it
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
return patterns;
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
private async traverseDirectory(
|
|
1426
|
-
currentUri: URI,
|
|
1427
|
-
searchRoot: URI,
|
|
1428
|
-
rootName: string | undefined,
|
|
1429
|
-
patternMatcher: Minimatch,
|
|
1430
|
-
excludeMatchers: Minimatch[],
|
|
1431
|
-
results: string[],
|
|
1432
|
-
maxResults: number,
|
|
1433
|
-
cancellationToken?: CancellationToken,
|
|
1434
|
-
emitAbsolutePaths = false
|
|
1435
|
-
): Promise<void> {
|
|
1436
|
-
if (cancellationToken?.isCancellationRequested || results.length >= maxResults) {
|
|
1437
|
-
return;
|
|
1407
|
+
/**
|
|
1408
|
+
* Renders a search-result URI in the format expected by the caller: an absolute
|
|
1409
|
+
* path for external roots, or a `<rootName>/<relativePath>` (or bare relative
|
|
1410
|
+
* path when no root name is available) for workspace roots.
|
|
1411
|
+
*/
|
|
1412
|
+
protected toDisplayPath(match: URI, target: { rootUri: URI; rootName?: string; external: boolean }): string | undefined {
|
|
1413
|
+
if (target.external) {
|
|
1414
|
+
return match.path.toString();
|
|
1438
1415
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
if (!stat || !stat.isDirectory || !stat.children) {
|
|
1443
|
-
return;
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
for (const child of stat.children) {
|
|
1447
|
-
if (cancellationToken?.isCancellationRequested || results.length >= maxResults) {
|
|
1448
|
-
break;
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
const relativePath = searchRoot.relative(child.resource)?.toString();
|
|
1452
|
-
if (!relativePath) {
|
|
1453
|
-
continue;
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
const shouldExclude = excludeMatchers.some(matcher => matcher.match(relativePath)) ||
|
|
1457
|
-
(await this.workspaceScope.shouldExclude(child));
|
|
1458
|
-
|
|
1459
|
-
if (shouldExclude) {
|
|
1460
|
-
continue;
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
if (child.isDirectory) {
|
|
1464
|
-
await this.traverseDirectory(
|
|
1465
|
-
child.resource,
|
|
1466
|
-
searchRoot,
|
|
1467
|
-
rootName,
|
|
1468
|
-
patternMatcher,
|
|
1469
|
-
excludeMatchers,
|
|
1470
|
-
results,
|
|
1471
|
-
maxResults,
|
|
1472
|
-
cancellationToken,
|
|
1473
|
-
emitAbsolutePaths
|
|
1474
|
-
);
|
|
1475
|
-
} else if (patternMatcher.match(relativePath)) {
|
|
1476
|
-
if (emitAbsolutePaths) {
|
|
1477
|
-
results.push(child.resource.path.toString());
|
|
1478
|
-
} else if (rootName) {
|
|
1479
|
-
results.push(`${rootName}/${relativePath}`);
|
|
1480
|
-
} else {
|
|
1481
|
-
results.push(relativePath);
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
} catch {
|
|
1486
|
-
// If we can't access a directory, skip it
|
|
1416
|
+
const relativePath = target.rootUri.relative(match)?.toString();
|
|
1417
|
+
if (relativePath === undefined) {
|
|
1418
|
+
return undefined;
|
|
1487
1419
|
}
|
|
1420
|
+
return target.rootName ? `${target.rootName}/${relativePath}` : relativePath;
|
|
1488
1421
|
}
|
|
1489
1422
|
}
|