@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
|
@@ -4,7 +4,9 @@ import { CancellationToken, PreferenceService, URI } from '@theia/core';
|
|
|
4
4
|
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
5
5
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
6
6
|
import { FileStat } from '@theia/filesystem/lib/common/files';
|
|
7
|
+
import { FileSearchService } from '@theia/file-search/lib/common/file-search-service';
|
|
7
8
|
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
9
|
+
import ignore from 'ignore';
|
|
8
10
|
import { MonacoWorkspace } from '@theia/monaco/lib/browser/monaco-workspace';
|
|
9
11
|
import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
|
|
10
12
|
import { ProblemManager } from '@theia/markers/lib/browser';
|
|
@@ -125,6 +127,13 @@ export declare class WorkspaceFunctionScope {
|
|
|
125
127
|
* platform: POSIX `/foo`, UNC `//host/share`, or Windows drive `C:/foo`.
|
|
126
128
|
*/
|
|
127
129
|
static isAbsolutePath(normalized: string): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Returns the URI without a trailing path separator (except for a root path). This makes
|
|
132
|
+
* directory comparisons via {@link URI.isEqualOrParent} insensitive to a trailing slash, so
|
|
133
|
+
* an allow-list entry such as `/foo/` matches the directory `/foo` itself, not only its
|
|
134
|
+
* children.
|
|
135
|
+
*/
|
|
136
|
+
static withoutTrailingSeparator(uri: URI): URI;
|
|
128
137
|
protected getHomeDirUri(): Promise<URI | undefined>;
|
|
129
138
|
/**
|
|
130
139
|
* Whether the already-separator-normalized path contains `..` as a path
|
|
@@ -135,6 +144,13 @@ export declare class WorkspaceFunctionScope {
|
|
|
135
144
|
shouldExclude(stat: FileStat): Promise<boolean>;
|
|
136
145
|
protected isUserExcluded(fileName: string, userExcludePatterns: string[]): boolean;
|
|
137
146
|
protected isGitIgnored(stat: FileStat, workspaceRoot: URI): Promise<boolean>;
|
|
147
|
+
/**
|
|
148
|
+
* Returns the cached `.gitignore` matcher for the given root, reading the file at most
|
|
149
|
+
* once per root. A root with no (or unreadable) `.gitignore` is cached as `undefined`.
|
|
150
|
+
* The cache is invalidated by {@link initializeGitignoreWatcher} when the `.gitignore` is
|
|
151
|
+
* created, changed, or deleted, so individual exclusion checks need no filesystem RPC.
|
|
152
|
+
*/
|
|
153
|
+
protected getGitignoreMatcher(workspaceRoot: URI): Promise<ReturnType<typeof ignore> | undefined>;
|
|
138
154
|
}
|
|
139
155
|
export declare class GetWorkspaceDirectoryStructure implements ToolProvider {
|
|
140
156
|
static ID: string;
|
|
@@ -185,10 +201,18 @@ export declare class FindFilesByPattern implements ToolProvider {
|
|
|
185
201
|
static ID: string;
|
|
186
202
|
protected readonly workspaceScope: WorkspaceFunctionScope;
|
|
187
203
|
protected readonly preferences: PreferenceService;
|
|
188
|
-
protected readonly
|
|
204
|
+
protected readonly fileSearchService: FileSearchService;
|
|
189
205
|
getTool(): ToolRequest;
|
|
190
206
|
private findFiles;
|
|
191
|
-
|
|
192
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Renders a search-result URI in the format expected by the caller: an absolute
|
|
209
|
+
* path for external roots, or a `<rootName>/<relativePath>` (or bare relative
|
|
210
|
+
* path when no root name is available) for workspace roots.
|
|
211
|
+
*/
|
|
212
|
+
protected toDisplayPath(match: URI, target: {
|
|
213
|
+
rootUri: URI;
|
|
214
|
+
rootName?: string;
|
|
215
|
+
external: boolean;
|
|
216
|
+
}): string | undefined;
|
|
193
217
|
}
|
|
194
218
|
//# sourceMappingURL=workspace-functions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-functions.d.ts","sourceRoot":"","sources":["../../src/browser/workspace-functions.ts"],"names":[],"mappings":"AAeA,OAAO,EAAyB,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,0DAA0D,CAAC;AACtG,OAAO,EAAE,iBAAiB,EAAkB,iBAAiB,EAAE,GAAG,EAAQ,MAAM,aAAa,CAAC;AAC9F,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE1E,OAAO,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AACzE,OAAO,EAAE,QAAQ,EAA2C,MAAM,oCAAoC,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"workspace-functions.d.ts","sourceRoot":"","sources":["../../src/browser/workspace-functions.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@ const env_variables_1 = require("@theia/core/lib/common/env-variables");
|
|
|
9
9
|
const inversify_1 = require("@theia/core/shared/inversify");
|
|
10
10
|
const file_service_1 = require("@theia/filesystem/lib/browser/file-service");
|
|
11
11
|
const files_1 = require("@theia/filesystem/lib/common/files");
|
|
12
|
+
const file_search_service_1 = require("@theia/file-search/lib/common/file-search-service");
|
|
12
13
|
const browser_1 = require("@theia/workspace/lib/browser");
|
|
13
14
|
const workspace_functions_1 = require("../common/workspace-functions");
|
|
14
15
|
const toolcall_utils_1 = require("@theia/ai-chat-ui/lib/browser/chat-response-renderer/toolcall-utils");
|
|
@@ -310,7 +311,9 @@ let WorkspaceFunctionScope = WorkspaceFunctionScope_1 = class WorkspaceFunctionS
|
|
|
310
311
|
}
|
|
311
312
|
const uri = await this.toExternalUri(trimmed);
|
|
312
313
|
if (uri && uri.scheme === 'file') {
|
|
313
|
-
|
|
314
|
+
// Strip a trailing separator so an entry like `/foo/` still matches the
|
|
315
|
+
// directory `/foo` itself (URI.isEqualOrParent compares the last segment exactly).
|
|
316
|
+
result.push(WorkspaceFunctionScope_1.withoutTrailingSeparator(uri.normalizePath()));
|
|
314
317
|
}
|
|
315
318
|
}
|
|
316
319
|
return result;
|
|
@@ -381,6 +384,19 @@ let WorkspaceFunctionScope = WorkspaceFunctionScope_1 = class WorkspaceFunctionS
|
|
|
381
384
|
}
|
|
382
385
|
return /^[A-Za-z]:\//.test(normalized);
|
|
383
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Returns the URI without a trailing path separator (except for a root path). This makes
|
|
389
|
+
* directory comparisons via {@link URI.isEqualOrParent} insensitive to a trailing slash, so
|
|
390
|
+
* an allow-list entry such as `/foo/` matches the directory `/foo` itself, not only its
|
|
391
|
+
* children.
|
|
392
|
+
*/
|
|
393
|
+
static withoutTrailingSeparator(uri) {
|
|
394
|
+
const path = uri.path.toString();
|
|
395
|
+
if (path.length > 1 && path.endsWith('/')) {
|
|
396
|
+
return uri.withPath(path.substring(0, path.length - 1));
|
|
397
|
+
}
|
|
398
|
+
return uri;
|
|
399
|
+
}
|
|
384
400
|
getHomeDirUri() {
|
|
385
401
|
if (!this.homeDirUri) {
|
|
386
402
|
this.homeDirUri = this.envVariablesServer.getHomeDirUri()
|
|
@@ -429,31 +445,41 @@ let WorkspaceFunctionScope = WorkspaceFunctionScope_1 = class WorkspaceFunctionS
|
|
|
429
445
|
return userExcludePatterns.some(pattern => new minimatch_1.Minimatch(pattern, { dot: true }).match(fileName));
|
|
430
446
|
}
|
|
431
447
|
async isGitIgnored(stat, workspaceRoot) {
|
|
448
|
+
const matcher = await this.getGitignoreMatcher(workspaceRoot);
|
|
449
|
+
if (!matcher) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
const relativePath = workspaceRoot.relative(stat.resource);
|
|
453
|
+
if (!relativePath) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
const relativePathStr = relativePath.toString() + (stat.isDirectory ? '/' : '');
|
|
457
|
+
return matcher.ignores(relativePathStr);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Returns the cached `.gitignore` matcher for the given root, reading the file at most
|
|
461
|
+
* once per root. A root with no (or unreadable) `.gitignore` is cached as `undefined`.
|
|
462
|
+
* The cache is invalidated by {@link initializeGitignoreWatcher} when the `.gitignore` is
|
|
463
|
+
* created, changed, or deleted, so individual exclusion checks need no filesystem RPC.
|
|
464
|
+
*/
|
|
465
|
+
async getGitignoreMatcher(workspaceRoot) {
|
|
432
466
|
await this.initializeGitignoreWatcher(workspaceRoot);
|
|
433
467
|
const rootKey = workspaceRoot.toString();
|
|
434
|
-
|
|
468
|
+
if (this.gitignoreMatchers.has(rootKey)) {
|
|
469
|
+
return this.gitignoreMatchers.get(rootKey);
|
|
470
|
+
}
|
|
471
|
+
let matcher;
|
|
435
472
|
try {
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
if (!matcher) {
|
|
440
|
-
const gitignoreContent = await this.fileService.read(gitignoreUri);
|
|
441
|
-
matcher = (0, ignore_1.default)().add(gitignoreContent.value);
|
|
442
|
-
this.gitignoreMatchers.set(rootKey, matcher);
|
|
443
|
-
}
|
|
444
|
-
const relativePath = workspaceRoot.relative(stat.resource);
|
|
445
|
-
if (relativePath) {
|
|
446
|
-
const relativePathStr = relativePath.toString() + (stat.isDirectory ? '/' : '');
|
|
447
|
-
if (matcher.ignores(relativePathStr)) {
|
|
448
|
-
return true;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
473
|
+
const gitignoreUri = workspaceRoot.resolve(this.GITIGNORE_FILE_NAME);
|
|
474
|
+
const gitignoreContent = await this.fileService.read(gitignoreUri);
|
|
475
|
+
matcher = (0, ignore_1.default)().add(gitignoreContent.value);
|
|
452
476
|
}
|
|
453
477
|
catch {
|
|
454
|
-
//
|
|
478
|
+
// No .gitignore (or it cannot be read): cache the absence so we don't retry on every check.
|
|
479
|
+
matcher = undefined;
|
|
455
480
|
}
|
|
456
|
-
|
|
481
|
+
this.gitignoreMatchers.set(rootKey, matcher);
|
|
482
|
+
return matcher;
|
|
457
483
|
}
|
|
458
484
|
};
|
|
459
485
|
exports.WorkspaceFunctionScope = WorkspaceFunctionScope;
|
|
@@ -565,16 +591,23 @@ let GetWorkspaceDirectoryStructure = class GetWorkspaceDirectoryStructure {
|
|
|
565
591
|
const stat = await this.fileService.resolve(uri);
|
|
566
592
|
const result = {};
|
|
567
593
|
if (stat && stat.isDirectory && stat.children) {
|
|
594
|
+
// Determine which child directories to include (the exclusion check may be async)...
|
|
595
|
+
const childDirs = [];
|
|
568
596
|
for (const child of stat.children) {
|
|
569
597
|
if (cancellationToken?.isCancellationRequested) {
|
|
570
598
|
return { error: 'Operation cancelled by user' };
|
|
571
599
|
}
|
|
572
|
-
if (
|
|
573
|
-
|
|
600
|
+
if (child.isDirectory && !(await this.workspaceScope.shouldExclude(child))) {
|
|
601
|
+
childDirs.push(child.resource);
|
|
574
602
|
}
|
|
575
|
-
const dirName = child.resource.path.base;
|
|
576
|
-
result[dirName] = await this.buildDirectoryStructure(child.resource, cancellationToken);
|
|
577
603
|
}
|
|
604
|
+
// ...then resolve their subtrees concurrently, so the traversal costs O(depth)
|
|
605
|
+
// round-trips instead of one serial round-trip per directory. Empty directories
|
|
606
|
+
// are preserved (they resolve to an empty object).
|
|
607
|
+
const subtrees = await Promise.all(childDirs.map(childUri => this.buildDirectoryStructure(childUri, cancellationToken)));
|
|
608
|
+
childDirs.forEach((childUri, index) => {
|
|
609
|
+
result[childUri.path.base] = subtrees[index];
|
|
610
|
+
});
|
|
578
611
|
}
|
|
579
612
|
return result;
|
|
580
613
|
}
|
|
@@ -928,34 +961,29 @@ let GetWorkspaceFileList = class GetWorkspaceFileList {
|
|
|
928
961
|
if (!stat || !stat.isDirectory) {
|
|
929
962
|
return JSON.stringify({ error: 'Directory not found' });
|
|
930
963
|
}
|
|
931
|
-
return await this.listFilesDirectly(
|
|
964
|
+
return await this.listFilesDirectly(stat, cancellationToken);
|
|
932
965
|
}
|
|
933
966
|
catch (error) {
|
|
934
967
|
return JSON.stringify({ error: 'Directory not found' });
|
|
935
968
|
}
|
|
936
969
|
}
|
|
937
|
-
async listFilesDirectly(
|
|
970
|
+
async listFilesDirectly(stat, cancellationToken) {
|
|
938
971
|
if (cancellationToken?.isCancellationRequested) {
|
|
939
972
|
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
940
973
|
}
|
|
941
|
-
const stat = await this.fileService.resolve(uri);
|
|
942
974
|
const result = {};
|
|
943
|
-
if (
|
|
944
|
-
|
|
945
|
-
|
|
975
|
+
if (await this.workspaceScope.shouldExclude(stat)) {
|
|
976
|
+
return JSON.stringify(result);
|
|
977
|
+
}
|
|
978
|
+
// `stat` already carries one level of children from the caller's resolve, so no extra RPC.
|
|
979
|
+
for (const child of stat.children ?? []) {
|
|
980
|
+
if (cancellationToken?.isCancellationRequested) {
|
|
981
|
+
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
946
982
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
for (const child of children.children) {
|
|
950
|
-
if (cancellationToken?.isCancellationRequested) {
|
|
951
|
-
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
952
|
-
}
|
|
953
|
-
if (await this.workspaceScope.shouldExclude(child)) {
|
|
954
|
-
continue;
|
|
955
|
-
}
|
|
956
|
-
result[child.resource.path.base] = child.isDirectory ? 'directory' : 'file';
|
|
957
|
-
}
|
|
983
|
+
if (await this.workspaceScope.shouldExclude(child)) {
|
|
984
|
+
continue;
|
|
958
985
|
}
|
|
986
|
+
result[child.resource.path.base] = child.isDirectory ? 'directory' : 'file';
|
|
959
987
|
}
|
|
960
988
|
return JSON.stringify(result);
|
|
961
989
|
}
|
|
@@ -1132,8 +1160,6 @@ let FindFilesByPattern = class FindFilesByPattern {
|
|
|
1132
1160
|
'\'src/**/*.js\' for JavaScript files in the src directory. The function respects gitignore patterns and user exclusions, ' +
|
|
1133
1161
|
'returns workspace-relative paths (e.g., "my-project/src/index.ts") or absolute paths for external roots, ' +
|
|
1134
1162
|
'and limits results to 200 files maximum. ' +
|
|
1135
|
-
'Performance note: This traverses directories recursively which may be slow in large workspaces. ' +
|
|
1136
|
-
'For better performance, use specific subdirectory patterns (e.g., \'src/**/*.ts\' instead of \'**/*.ts\'). ' +
|
|
1137
1163
|
'Use this to find files by name/extension. Do NOT use this for searching file contents - use searchInWorkspace instead.',
|
|
1138
1164
|
parameters: {
|
|
1139
1165
|
type: 'object',
|
|
@@ -1142,8 +1168,7 @@ let FindFilesByPattern = class FindFilesByPattern {
|
|
|
1142
1168
|
type: 'string',
|
|
1143
1169
|
description: 'Glob pattern to match files against. ' +
|
|
1144
1170
|
'Examples: \'**/*.ts\' (all TypeScript files), \'src/**/*.js\' (JS files in src), ' +
|
|
1145
|
-
'\'**/*.{js,ts}\' (JS or TS files), \'**/test/**/*.spec.ts\' (test files).
|
|
1146
|
-
'Use specific subdirectory prefixes for better performance (e.g., \'packages/core/**/*.ts\' instead of \'**/*.ts\').'
|
|
1171
|
+
'\'**/*.{js,ts}\' (JS or TS files), \'**/test/**/*.spec.ts\' (test files).'
|
|
1147
1172
|
},
|
|
1148
1173
|
exclude: {
|
|
1149
1174
|
type: 'array',
|
|
@@ -1190,29 +1215,19 @@ let FindFilesByPattern = class FindFilesByPattern {
|
|
|
1190
1215
|
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
1191
1216
|
}
|
|
1192
1217
|
try {
|
|
1193
|
-
const patternMatcher = new minimatch_1.Minimatch(pattern, { dot: false });
|
|
1194
|
-
const files = [];
|
|
1195
1218
|
const maxResults = 200;
|
|
1219
|
+
const useGitIgnore = this.preferences.get(workspace_preferences_1.CONSIDER_GITIGNORE_PREF, true);
|
|
1220
|
+
const userExcludes = this.preferences.get(workspace_preferences_1.USER_EXCLUDE_PATTERN_PREF, []);
|
|
1221
|
+
const excludes = [...userExcludes, ...(excludePatterns ?? [])];
|
|
1222
|
+
// Resolve the set of roots to search and how each root's results should be rendered.
|
|
1223
|
+
const targets = [];
|
|
1196
1224
|
if (searchRoot) {
|
|
1197
1225
|
const resolved = await this.workspaceScope.resolveToUri(searchRoot);
|
|
1198
1226
|
if (!resolved) {
|
|
1199
1227
|
return JSON.stringify({ error: `Invalid searchRoot: '${searchRoot}'` });
|
|
1200
1228
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
await this.workspaceScope.ensureAccessible(rootUri);
|
|
1204
|
-
const ignorePatterns = isExternalRoot
|
|
1205
|
-
? this.preferences.get(workspace_preferences_1.USER_EXCLUDE_PATTERN_PREF, [])
|
|
1206
|
-
: await this.buildIgnorePatterns(rootUri);
|
|
1207
|
-
const allExcludes = [...ignorePatterns];
|
|
1208
|
-
if (excludePatterns && excludePatterns.length > 0) {
|
|
1209
|
-
allExcludes.push(...excludePatterns);
|
|
1210
|
-
}
|
|
1211
|
-
if (cancellationToken?.isCancellationRequested) {
|
|
1212
|
-
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
1213
|
-
}
|
|
1214
|
-
const excludeMatchers = allExcludes.map(excludePattern => new minimatch_1.Minimatch(excludePattern, { dot: true }));
|
|
1215
|
-
await this.traverseDirectory(rootUri, rootUri, undefined, patternMatcher, excludeMatchers, files, maxResults, cancellationToken, isExternalRoot);
|
|
1229
|
+
await this.workspaceScope.ensureAccessible(resolved);
|
|
1230
|
+
targets.push({ rootUri: resolved, external: !this.workspaceScope.isInWorkspace(resolved) });
|
|
1216
1231
|
}
|
|
1217
1232
|
else {
|
|
1218
1233
|
const rootMapping = this.workspaceScope.getRootMapping();
|
|
@@ -1220,19 +1235,38 @@ let FindFilesByPattern = class FindFilesByPattern {
|
|
|
1220
1235
|
return JSON.stringify({ error: 'No workspace has been opened yet' });
|
|
1221
1236
|
}
|
|
1222
1237
|
for (const [rootName, rootUri] of rootMapping) {
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1238
|
+
targets.push({ rootUri, rootName, external: false });
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
// Delegate the actual traversal to the backend ripgrep-based file search.
|
|
1242
|
+
// It runs natively on the backend filesystem (no per-directory RPC) and applies
|
|
1243
|
+
// include/exclude globs.
|
|
1244
|
+
const files = [];
|
|
1245
|
+
for (const target of targets) {
|
|
1246
|
+
if (cancellationToken?.isCancellationRequested) {
|
|
1247
|
+
return JSON.stringify({ error: 'Operation cancelled by user' });
|
|
1248
|
+
}
|
|
1249
|
+
if (files.length > maxResults) {
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
// `considerGitIgnore` is scoped to workspace roots (see its preference description),
|
|
1253
|
+
// so external allow-listed roots are searched with user/caller excludes only (plus
|
|
1254
|
+
// `.git`). Applying gitignore there would also leak the user's *global* gitignore
|
|
1255
|
+
// into an explicitly allow-listed directory and silently hide files.
|
|
1256
|
+
// Request one extra result across all roots so we can detect truncation.
|
|
1257
|
+
const matches = await this.fileSearchService.find('', {
|
|
1258
|
+
rootUris: [target.rootUri.toString()],
|
|
1259
|
+
includePatterns: [pattern],
|
|
1260
|
+
excludePatterns: target.external ? [...excludes, '.git'] : excludes,
|
|
1261
|
+
useGitIgnore: target.external ? false : useGitIgnore,
|
|
1262
|
+
fuzzyMatch: false,
|
|
1263
|
+
limit: maxResults - files.length + 1
|
|
1264
|
+
}, cancellationToken);
|
|
1265
|
+
for (const match of matches) {
|
|
1266
|
+
const display = this.toDisplayPath(new core_1.URI(match), target);
|
|
1267
|
+
if (display !== undefined) {
|
|
1268
|
+
files.push(display);
|
|
1233
1269
|
}
|
|
1234
|
-
const excludeMatchers = allExcludes.map(excludePattern => new minimatch_1.Minimatch(excludePattern, { dot: true }));
|
|
1235
|
-
await this.traverseDirectory(rootUri, rootUri, rootName, patternMatcher, excludeMatchers, files, maxResults, cancellationToken);
|
|
1236
1270
|
}
|
|
1237
1271
|
}
|
|
1238
1272
|
if (cancellationToken?.isCancellationRequested) {
|
|
@@ -1242,7 +1276,6 @@ let FindFilesByPattern = class FindFilesByPattern {
|
|
|
1242
1276
|
files: files.slice(0, maxResults)
|
|
1243
1277
|
};
|
|
1244
1278
|
if (files.length > maxResults) {
|
|
1245
|
-
result.totalFound = files.length;
|
|
1246
1279
|
result.truncated = true;
|
|
1247
1280
|
}
|
|
1248
1281
|
return JSON.stringify(result);
|
|
@@ -1251,70 +1284,20 @@ let FindFilesByPattern = class FindFilesByPattern {
|
|
|
1251
1284
|
return JSON.stringify({ error: `Failed to find files: ${error.message}` });
|
|
1252
1285
|
}
|
|
1253
1286
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
try {
|
|
1263
|
-
const gitignoreUri = workspaceRoot.resolve('.gitignore');
|
|
1264
|
-
const gitignoreContent = await this.fileService.read(gitignoreUri);
|
|
1265
|
-
const gitignoreLines = gitignoreContent.value
|
|
1266
|
-
.split('\n')
|
|
1267
|
-
.map(line => line.trim())
|
|
1268
|
-
.filter(line => line && !line.startsWith('#'));
|
|
1269
|
-
patterns.push(...gitignoreLines);
|
|
1270
|
-
}
|
|
1271
|
-
catch {
|
|
1272
|
-
// Gitignore file doesn't exist or can't be read, continue without it
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
return patterns;
|
|
1276
|
-
}
|
|
1277
|
-
async traverseDirectory(currentUri, searchRoot, rootName, patternMatcher, excludeMatchers, results, maxResults, cancellationToken, emitAbsolutePaths = false) {
|
|
1278
|
-
if (cancellationToken?.isCancellationRequested || results.length >= maxResults) {
|
|
1279
|
-
return;
|
|
1280
|
-
}
|
|
1281
|
-
try {
|
|
1282
|
-
const stat = await this.fileService.resolve(currentUri);
|
|
1283
|
-
if (!stat || !stat.isDirectory || !stat.children) {
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
for (const child of stat.children) {
|
|
1287
|
-
if (cancellationToken?.isCancellationRequested || results.length >= maxResults) {
|
|
1288
|
-
break;
|
|
1289
|
-
}
|
|
1290
|
-
const relativePath = searchRoot.relative(child.resource)?.toString();
|
|
1291
|
-
if (!relativePath) {
|
|
1292
|
-
continue;
|
|
1293
|
-
}
|
|
1294
|
-
const shouldExclude = excludeMatchers.some(matcher => matcher.match(relativePath)) ||
|
|
1295
|
-
(await this.workspaceScope.shouldExclude(child));
|
|
1296
|
-
if (shouldExclude) {
|
|
1297
|
-
continue;
|
|
1298
|
-
}
|
|
1299
|
-
if (child.isDirectory) {
|
|
1300
|
-
await this.traverseDirectory(child.resource, searchRoot, rootName, patternMatcher, excludeMatchers, results, maxResults, cancellationToken, emitAbsolutePaths);
|
|
1301
|
-
}
|
|
1302
|
-
else if (patternMatcher.match(relativePath)) {
|
|
1303
|
-
if (emitAbsolutePaths) {
|
|
1304
|
-
results.push(child.resource.path.toString());
|
|
1305
|
-
}
|
|
1306
|
-
else if (rootName) {
|
|
1307
|
-
results.push(`${rootName}/${relativePath}`);
|
|
1308
|
-
}
|
|
1309
|
-
else {
|
|
1310
|
-
results.push(relativePath);
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Renders a search-result URI in the format expected by the caller: an absolute
|
|
1289
|
+
* path for external roots, or a `<rootName>/<relativePath>` (or bare relative
|
|
1290
|
+
* path when no root name is available) for workspace roots.
|
|
1291
|
+
*/
|
|
1292
|
+
toDisplayPath(match, target) {
|
|
1293
|
+
if (target.external) {
|
|
1294
|
+
return match.path.toString();
|
|
1314
1295
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1296
|
+
const relativePath = target.rootUri.relative(match)?.toString();
|
|
1297
|
+
if (relativePath === undefined) {
|
|
1298
|
+
return undefined;
|
|
1317
1299
|
}
|
|
1300
|
+
return target.rootName ? `${target.rootName}/${relativePath}` : relativePath;
|
|
1318
1301
|
}
|
|
1319
1302
|
};
|
|
1320
1303
|
exports.FindFilesByPattern = FindFilesByPattern;
|
|
@@ -1327,9 +1310,9 @@ tslib_1.__decorate([
|
|
|
1327
1310
|
tslib_1.__metadata("design:type", Object)
|
|
1328
1311
|
], FindFilesByPattern.prototype, "preferences", void 0);
|
|
1329
1312
|
tslib_1.__decorate([
|
|
1330
|
-
(0, inversify_1.inject)(
|
|
1331
|
-
tslib_1.__metadata("design:type",
|
|
1332
|
-
], FindFilesByPattern.prototype, "
|
|
1313
|
+
(0, inversify_1.inject)(file_search_service_1.FileSearchService),
|
|
1314
|
+
tslib_1.__metadata("design:type", Object)
|
|
1315
|
+
], FindFilesByPattern.prototype, "fileSearchService", void 0);
|
|
1333
1316
|
exports.FindFilesByPattern = FindFilesByPattern = FindFilesByPattern_1 = tslib_1.__decorate([
|
|
1334
1317
|
(0, inversify_1.injectable)()
|
|
1335
1318
|
], FindFilesByPattern);
|