@codingame/monaco-vscode-search-service-override 4.3.1 → 4.4.0
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 +10 -2
- package/search.d.ts +8 -1
- package/search.js +3 -2
- package/vscode/src/vs/workbench/services/search/browser/searchService.js +185 -0
- package/vscode/src/vs/workbench/services/search/common/getFileResults.js +103 -0
- package/vscode/src/vs/workbench/services/search/worker/localFileSearch.js +262 -0
- package/worker.js +1 -0
- package/workers/localFileSearch.worker.js +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codingame/monaco-vscode-search-service-override",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "CodinGame",
|
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
"module": "index.js",
|
|
19
19
|
"types": "index.d.ts",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"vscode": "npm:@codingame/monaco-vscode-api@4.
|
|
21
|
+
"vscode": "npm:@codingame/monaco-vscode-api@4.4.0"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"default": "./index.js"
|
|
26
|
+
},
|
|
27
|
+
"./worker": {
|
|
28
|
+
"default": "./worker.js"
|
|
29
|
+
}
|
|
22
30
|
}
|
|
23
31
|
}
|
package/search.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { IEditorOverrideServices } from 'vscode/vscode/vs/editor/standalone/browser/standaloneServices';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
interface SearchServiceOverrideProps {
|
|
4
|
+
/**
|
|
5
|
+
* Is an `HTMLFileSystemProvider` is used as only provider for the `file` scheme directly (without overlay)
|
|
6
|
+
* Enable this option to enable searching local filesystem
|
|
7
|
+
*/
|
|
8
|
+
useHtmlFileSystemProvider: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare function getServiceOverride({ useHtmlFileSystemProvider }: SearchServiceOverrideProps): IEditorOverrideServices;
|
|
4
11
|
|
|
5
12
|
export { getServiceOverride as default };
|
package/search.js
CHANGED
|
@@ -5,12 +5,13 @@ import { ISearchViewModelWorkbenchService, SearchViewModelWorkbenchService } fro
|
|
|
5
5
|
import { ISearchHistoryService, SearchHistoryService } from 'vscode/vscode/vs/workbench/contrib/search/common/searchHistoryService';
|
|
6
6
|
import { IReplaceService } from 'vscode/vscode/vs/workbench/contrib/search/browser/replace';
|
|
7
7
|
import { ReplaceService } from './vscode/src/vs/workbench/contrib/search/browser/replaceService.js';
|
|
8
|
+
import { RemoteSearchService } from './vscode/src/vs/workbench/services/search/browser/searchService.js';
|
|
8
9
|
import './vscode/src/vs/workbench/contrib/search/browser/search.contribution.js';
|
|
9
10
|
import './vscode/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.js';
|
|
10
11
|
|
|
11
|
-
function getServiceOverride() {
|
|
12
|
+
function getServiceOverride({ useHtmlFileSystemProvider }) {
|
|
12
13
|
return {
|
|
13
|
-
[( ISearchService.toString())]: new SyncDescriptor(SearchService, [], true),
|
|
14
|
+
[( ISearchService.toString())]: useHtmlFileSystemProvider ? new SyncDescriptor(RemoteSearchService, [], true) : new SyncDescriptor(SearchService, [], true),
|
|
14
15
|
[( ISearchViewModelWorkbenchService.toString())]: new SyncDescriptor(SearchViewModelWorkbenchService, [], true),
|
|
15
16
|
[( ISearchHistoryService.toString())]: new SyncDescriptor(SearchHistoryService, [], true),
|
|
16
17
|
[( IReplaceService.toString())]: new SyncDescriptor(ReplaceService, [], true)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { __decorate, __param } from 'vscode/external/tslib/tslib.es6.js';
|
|
2
|
+
import { IModelService } from 'vscode/vscode/vs/editor/common/services/model';
|
|
3
|
+
import { IFileService } from 'vscode/vscode/vs/platform/files/common/files';
|
|
4
|
+
import { IInstantiationService } from 'vscode/vscode/vs/platform/instantiation/common/instantiation';
|
|
5
|
+
import { ILogService } from 'vscode/vscode/vs/platform/log/common/log';
|
|
6
|
+
import { ITelemetryService } from 'vscode/vscode/vs/platform/telemetry/common/telemetry';
|
|
7
|
+
import { IEditorService } from 'vscode/vscode/vs/workbench/services/editor/common/editorService';
|
|
8
|
+
import { IExtensionService } from 'vscode/vscode/vs/workbench/services/extensions/common/extensions';
|
|
9
|
+
import 'vscode/vscode/vs/workbench/services/search/common/search';
|
|
10
|
+
import { SearchService } from '../common/searchService.js';
|
|
11
|
+
import { IUriIdentityService } from 'vscode/vscode/vs/platform/uriIdentity/common/uriIdentity';
|
|
12
|
+
import { SimpleWorkerClient, logOnceWebWorkerWarning } from 'vscode/vscode/vs/base/common/worker/simpleWorker';
|
|
13
|
+
import { Disposable, DisposableStore } from 'vscode/vscode/vs/base/common/lifecycle';
|
|
14
|
+
import { DefaultWorkerFactory } from 'vscode/vscode/vs/base/browser/defaultWorkerFactory';
|
|
15
|
+
import { memoize } from 'vscode/vscode/vs/base/common/decorators';
|
|
16
|
+
import { Schemas } from 'vscode/vscode/vs/base/common/network';
|
|
17
|
+
import { URI } from 'vscode/vscode/vs/base/common/uri';
|
|
18
|
+
import { Emitter } from 'vscode/vscode/vs/base/common/event';
|
|
19
|
+
import { localizeWithPath } from 'vscode/vscode/vs/nls';
|
|
20
|
+
import { WebFileSystemAccess } from 'vscode/vscode/vs/platform/files/browser/webFileSystemAccess';
|
|
21
|
+
import { revive } from 'vscode/vscode/vs/base/common/marshalling';
|
|
22
|
+
import { TextSearchCompleteMessageType } from 'vscode/vscode/vs/workbench/services/search/common/searchExtTypes';
|
|
23
|
+
|
|
24
|
+
let RemoteSearchService = class RemoteSearchService extends SearchService {
|
|
25
|
+
constructor(modelService, editorService, telemetryService, logService, extensionService, fileService, instantiationService, uriIdentityService) {
|
|
26
|
+
super(modelService, editorService, telemetryService, logService, extensionService, fileService, uriIdentityService);
|
|
27
|
+
this.instantiationService = instantiationService;
|
|
28
|
+
const searchProvider = this.instantiationService.createInstance(LocalFileSearchWorkerClient);
|
|
29
|
+
this.registerSearchResultProvider(Schemas.file, 0 , searchProvider);
|
|
30
|
+
this.registerSearchResultProvider(Schemas.file, 1 , searchProvider);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
RemoteSearchService = ( __decorate([
|
|
34
|
+
( __param(0, IModelService)),
|
|
35
|
+
( __param(1, IEditorService)),
|
|
36
|
+
( __param(2, ITelemetryService)),
|
|
37
|
+
( __param(3, ILogService)),
|
|
38
|
+
( __param(4, IExtensionService)),
|
|
39
|
+
( __param(5, IFileService)),
|
|
40
|
+
( __param(6, IInstantiationService)),
|
|
41
|
+
( __param(7, IUriIdentityService))
|
|
42
|
+
], RemoteSearchService));
|
|
43
|
+
let LocalFileSearchWorkerClient = class LocalFileSearchWorkerClient extends Disposable {
|
|
44
|
+
constructor(fileService, uriIdentityService) {
|
|
45
|
+
super();
|
|
46
|
+
this.fileService = fileService;
|
|
47
|
+
this.uriIdentityService = uriIdentityService;
|
|
48
|
+
this._onDidReceiveTextSearchMatch = ( new Emitter());
|
|
49
|
+
this.onDidReceiveTextSearchMatch = this._onDidReceiveTextSearchMatch.event;
|
|
50
|
+
this.queryId = 0;
|
|
51
|
+
this._worker = null;
|
|
52
|
+
this._workerFactory = ( new DefaultWorkerFactory('localFileSearchWorker'));
|
|
53
|
+
}
|
|
54
|
+
sendTextSearchMatch(match, queryId) {
|
|
55
|
+
this._onDidReceiveTextSearchMatch.fire({ match, queryId });
|
|
56
|
+
}
|
|
57
|
+
get fileSystemProvider() {
|
|
58
|
+
return this.fileService.getProvider(Schemas.file);
|
|
59
|
+
}
|
|
60
|
+
async cancelQuery(queryId) {
|
|
61
|
+
const proxy = await this._getOrCreateWorker().getProxyObject();
|
|
62
|
+
proxy.cancelQuery(queryId);
|
|
63
|
+
}
|
|
64
|
+
async textSearch(query, onProgress, token) {
|
|
65
|
+
try {
|
|
66
|
+
const queryDisposables = ( new DisposableStore());
|
|
67
|
+
const proxy = await this._getOrCreateWorker().getProxyObject();
|
|
68
|
+
const results = [];
|
|
69
|
+
let limitHit = false;
|
|
70
|
+
await Promise.all(( query.folderQueries.map(async (fq) => {
|
|
71
|
+
const queryId = this.queryId++;
|
|
72
|
+
queryDisposables.add(token?.onCancellationRequested(e => this.cancelQuery(queryId)) || Disposable.None);
|
|
73
|
+
const handle = await this.fileSystemProvider.getHandle(fq.folder);
|
|
74
|
+
if (!handle || !WebFileSystemAccess.isFileSystemDirectoryHandle(handle)) {
|
|
75
|
+
console.error('Could not get directory handle for ', fq);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const reviveMatch = (result) => ({
|
|
79
|
+
resource: URI.revive(result.resource),
|
|
80
|
+
results: revive(result.results)
|
|
81
|
+
});
|
|
82
|
+
queryDisposables.add(this.onDidReceiveTextSearchMatch(e => {
|
|
83
|
+
if (e.queryId === queryId) {
|
|
84
|
+
onProgress?.(reviveMatch(e.match));
|
|
85
|
+
}
|
|
86
|
+
}));
|
|
87
|
+
const ignorePathCasing = this.uriIdentityService.extUri.ignorePathCasing(fq.folder);
|
|
88
|
+
const folderResults = await proxy.searchDirectory(handle, query, fq, ignorePathCasing, queryId);
|
|
89
|
+
for (const folderResult of folderResults.results) {
|
|
90
|
+
results.push(revive(folderResult));
|
|
91
|
+
}
|
|
92
|
+
if (folderResults.limitHit) {
|
|
93
|
+
limitHit = true;
|
|
94
|
+
}
|
|
95
|
+
})));
|
|
96
|
+
queryDisposables.dispose();
|
|
97
|
+
const result = { messages: [], results, limitHit };
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
console.error('Error performing web worker text search', e);
|
|
102
|
+
return {
|
|
103
|
+
results: [],
|
|
104
|
+
messages: [{
|
|
105
|
+
text: ( localizeWithPath(
|
|
106
|
+
'vs/workbench/services/search/browser/searchService',
|
|
107
|
+
'errorSearchText',
|
|
108
|
+
"Unable to search with Web Worker text searcher"
|
|
109
|
+
)), type: TextSearchCompleteMessageType.Warning
|
|
110
|
+
}],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async fileSearch(query, token) {
|
|
115
|
+
try {
|
|
116
|
+
const queryDisposables = ( new DisposableStore());
|
|
117
|
+
let limitHit = false;
|
|
118
|
+
const proxy = await this._getOrCreateWorker().getProxyObject();
|
|
119
|
+
const results = [];
|
|
120
|
+
await Promise.all(( query.folderQueries.map(async (fq) => {
|
|
121
|
+
const queryId = this.queryId++;
|
|
122
|
+
queryDisposables.add(token?.onCancellationRequested(e => this.cancelQuery(queryId)) || Disposable.None);
|
|
123
|
+
const handle = await this.fileSystemProvider.getHandle(fq.folder);
|
|
124
|
+
if (!handle || !WebFileSystemAccess.isFileSystemDirectoryHandle(handle)) {
|
|
125
|
+
console.error('Could not get directory handle for ', fq);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const caseSensitive = this.uriIdentityService.extUri.ignorePathCasing(fq.folder);
|
|
129
|
+
const folderResults = await proxy.listDirectory(handle, query, fq, caseSensitive, queryId);
|
|
130
|
+
for (const folderResult of folderResults.results) {
|
|
131
|
+
results.push({ resource: URI.joinPath(fq.folder, folderResult) });
|
|
132
|
+
}
|
|
133
|
+
if (folderResults.limitHit) {
|
|
134
|
+
limitHit = true;
|
|
135
|
+
}
|
|
136
|
+
})));
|
|
137
|
+
queryDisposables.dispose();
|
|
138
|
+
const result = { messages: [], results, limitHit };
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
console.error('Error performing web worker file search', e);
|
|
143
|
+
return {
|
|
144
|
+
results: [],
|
|
145
|
+
messages: [{
|
|
146
|
+
text: ( localizeWithPath(
|
|
147
|
+
'vs/workbench/services/search/browser/searchService',
|
|
148
|
+
'errorSearchFile',
|
|
149
|
+
"Unable to search with Web Worker file searcher"
|
|
150
|
+
)), type: TextSearchCompleteMessageType.Warning
|
|
151
|
+
}],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async clearCache(cacheKey) {
|
|
156
|
+
if (this.cache?.key === cacheKey) {
|
|
157
|
+
this.cache = undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
_getOrCreateWorker() {
|
|
161
|
+
if (!this._worker) {
|
|
162
|
+
try {
|
|
163
|
+
this._worker = this._register(( new SimpleWorkerClient(
|
|
164
|
+
this._workerFactory,
|
|
165
|
+
'vs/workbench/services/search/worker/localFileSearch',
|
|
166
|
+
this
|
|
167
|
+
)));
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
logOnceWebWorkerWarning(err);
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return this._worker;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
LocalFileSearchWorkerClient.__decorator = ( __decorate([
|
|
178
|
+
memoize
|
|
179
|
+
], LocalFileSearchWorkerClient.prototype, "fileSystemProvider", null));
|
|
180
|
+
LocalFileSearchWorkerClient = ( __decorate([
|
|
181
|
+
( __param(0, IFileService)),
|
|
182
|
+
( __param(1, IUriIdentityService))
|
|
183
|
+
], LocalFileSearchWorkerClient));
|
|
184
|
+
|
|
185
|
+
export { LocalFileSearchWorkerClient, RemoteSearchService };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Range } from 'vscode/vscode/vs/editor/common/core/range';
|
|
2
|
+
|
|
3
|
+
const getFileResults = (bytes, pattern, options) => {
|
|
4
|
+
let text;
|
|
5
|
+
if (bytes[0] === 0xff && bytes[1] === 0xfe) {
|
|
6
|
+
text = ( new TextDecoder('utf-16le')).decode(bytes);
|
|
7
|
+
}
|
|
8
|
+
else if (bytes[0] === 0xfe && bytes[1] === 0xff) {
|
|
9
|
+
text = ( new TextDecoder('utf-16be')).decode(bytes);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
text = ( new TextDecoder('utf8')).decode(bytes);
|
|
13
|
+
if (text.slice(0, 1000).includes('\uFFFD') && bytes.includes(0)) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const results = [];
|
|
18
|
+
const patternIndecies = [];
|
|
19
|
+
let patternMatch = null;
|
|
20
|
+
let remainingResultQuota = options.remainingResultQuota;
|
|
21
|
+
while (remainingResultQuota >= 0 && (patternMatch = pattern.exec(text))) {
|
|
22
|
+
patternIndecies.push({ matchStartIndex: patternMatch.index, matchedText: patternMatch[0] });
|
|
23
|
+
remainingResultQuota--;
|
|
24
|
+
}
|
|
25
|
+
if (patternIndecies.length) {
|
|
26
|
+
const contextLinesNeeded = ( new Set());
|
|
27
|
+
const resultLines = ( new Set());
|
|
28
|
+
const lineRanges = [];
|
|
29
|
+
const readLine = (lineNumber) => text.slice(lineRanges[lineNumber].start, lineRanges[lineNumber].end);
|
|
30
|
+
let prevLineEnd = 0;
|
|
31
|
+
let lineEndingMatch = null;
|
|
32
|
+
const lineEndRegex = /\r?\n/g;
|
|
33
|
+
while ((lineEndingMatch = lineEndRegex.exec(text))) {
|
|
34
|
+
lineRanges.push({ start: prevLineEnd, end: lineEndingMatch.index });
|
|
35
|
+
prevLineEnd = lineEndingMatch.index + lineEndingMatch[0].length;
|
|
36
|
+
}
|
|
37
|
+
if (prevLineEnd < text.length) {
|
|
38
|
+
lineRanges.push({ start: prevLineEnd, end: text.length });
|
|
39
|
+
}
|
|
40
|
+
let startLine = 0;
|
|
41
|
+
for (const { matchStartIndex, matchedText } of patternIndecies) {
|
|
42
|
+
if (remainingResultQuota < 0) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
while (Boolean(lineRanges[startLine + 1]) && matchStartIndex > lineRanges[startLine].end) {
|
|
46
|
+
startLine++;
|
|
47
|
+
}
|
|
48
|
+
let endLine = startLine;
|
|
49
|
+
while (Boolean(lineRanges[endLine + 1]) && matchStartIndex + matchedText.length > lineRanges[endLine].end) {
|
|
50
|
+
endLine++;
|
|
51
|
+
}
|
|
52
|
+
if (options.beforeContext) {
|
|
53
|
+
for (let contextLine = Math.max(0, startLine - options.beforeContext); contextLine < startLine; contextLine++) {
|
|
54
|
+
contextLinesNeeded.add(contextLine);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
let previewText = '';
|
|
58
|
+
let offset = 0;
|
|
59
|
+
for (let matchLine = startLine; matchLine <= endLine; matchLine++) {
|
|
60
|
+
let previewLine = readLine(matchLine);
|
|
61
|
+
if (options.previewOptions?.charsPerLine && previewLine.length > options.previewOptions.charsPerLine) {
|
|
62
|
+
offset = Math.max(matchStartIndex - lineRanges[startLine].start - 20, 0);
|
|
63
|
+
previewLine = previewLine.substr(offset, options.previewOptions.charsPerLine);
|
|
64
|
+
}
|
|
65
|
+
previewText += `${previewLine}\n`;
|
|
66
|
+
resultLines.add(matchLine);
|
|
67
|
+
}
|
|
68
|
+
const fileRange = ( new Range(
|
|
69
|
+
startLine,
|
|
70
|
+
matchStartIndex - lineRanges[startLine].start,
|
|
71
|
+
endLine,
|
|
72
|
+
matchStartIndex + matchedText.length - lineRanges[endLine].start
|
|
73
|
+
));
|
|
74
|
+
const previewRange = ( new Range(
|
|
75
|
+
0,
|
|
76
|
+
matchStartIndex - lineRanges[startLine].start - offset,
|
|
77
|
+
endLine - startLine,
|
|
78
|
+
matchStartIndex + matchedText.length - lineRanges[endLine].start - (endLine === startLine ? offset : 0)
|
|
79
|
+
));
|
|
80
|
+
const match = {
|
|
81
|
+
ranges: fileRange,
|
|
82
|
+
preview: { text: previewText, matches: previewRange },
|
|
83
|
+
};
|
|
84
|
+
results.push(match);
|
|
85
|
+
if (options.afterContext) {
|
|
86
|
+
for (let contextLine = endLine + 1; contextLine <= Math.min(endLine + options.afterContext, lineRanges.length - 1); contextLine++) {
|
|
87
|
+
contextLinesNeeded.add(contextLine);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const contextLine of contextLinesNeeded) {
|
|
92
|
+
if (!( resultLines.has(contextLine))) {
|
|
93
|
+
results.push({
|
|
94
|
+
text: readLine(contextLine),
|
|
95
|
+
lineNumber: contextLine + 1,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return results;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export { getFileResults };
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { parse, match } from 'vscode/vscode/vs/base/common/glob';
|
|
2
|
+
import { URI } from 'vscode/vscode/vs/base/common/uri';
|
|
3
|
+
import { relative } from 'vscode/vscode/vs/base/common/path';
|
|
4
|
+
import { CancellationTokenSource } from 'vscode/vscode/vs/base/common/cancellation';
|
|
5
|
+
import { getFileResults } from '../common/getFileResults.js';
|
|
6
|
+
import { IgnoreFile } from 'vscode/vscode/vs/workbench/services/search/common/ignoreFile';
|
|
7
|
+
import { createRegExp } from 'vscode/vscode/vs/base/common/strings';
|
|
8
|
+
import { Promises } from 'vscode/vscode/vs/base/common/async';
|
|
9
|
+
import { ExtUri } from 'vscode/vscode/vs/base/common/resources';
|
|
10
|
+
|
|
11
|
+
const time = async (name, task) => {
|
|
12
|
+
{
|
|
13
|
+
return task();
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
function create(host) {
|
|
17
|
+
return ( new LocalFileSearchSimpleWorker(host));
|
|
18
|
+
}
|
|
19
|
+
class LocalFileSearchSimpleWorker {
|
|
20
|
+
constructor(host) {
|
|
21
|
+
this.host = host;
|
|
22
|
+
this.cancellationTokens = ( new Map());
|
|
23
|
+
}
|
|
24
|
+
cancelQuery(queryId) {
|
|
25
|
+
this.cancellationTokens.get(queryId)?.cancel();
|
|
26
|
+
}
|
|
27
|
+
registerCancellationToken(queryId) {
|
|
28
|
+
const source = ( new CancellationTokenSource());
|
|
29
|
+
this.cancellationTokens.set(queryId, source);
|
|
30
|
+
return source;
|
|
31
|
+
}
|
|
32
|
+
async listDirectory(handle, query, folderQuery, ignorePathCasing, queryId) {
|
|
33
|
+
const revivedFolderQuery = reviveFolderQuery(folderQuery);
|
|
34
|
+
const extUri = ( new ExtUri(() => ignorePathCasing));
|
|
35
|
+
const token = this.registerCancellationToken(queryId);
|
|
36
|
+
const entries = [];
|
|
37
|
+
let limitHit = false;
|
|
38
|
+
let count = 0;
|
|
39
|
+
const max = query.maxResults || 512;
|
|
40
|
+
const filePatternMatcher = query.filePattern
|
|
41
|
+
? (name) => query.filePattern.split('').every(c => name.includes(c))
|
|
42
|
+
: (name) => true;
|
|
43
|
+
await time('listDirectory', () => this.walkFolderQuery(handle, reviveQueryProps(query), revivedFolderQuery, extUri, file => {
|
|
44
|
+
if (!filePatternMatcher(file.name)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
count++;
|
|
48
|
+
if (max && count > max) {
|
|
49
|
+
limitHit = true;
|
|
50
|
+
token.cancel();
|
|
51
|
+
}
|
|
52
|
+
return entries.push(file.path);
|
|
53
|
+
}, token.token));
|
|
54
|
+
return {
|
|
55
|
+
results: entries,
|
|
56
|
+
limitHit
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async searchDirectory(handle, query, folderQuery, ignorePathCasing, queryId) {
|
|
60
|
+
const revivedQuery = reviveFolderQuery(folderQuery);
|
|
61
|
+
const extUri = ( new ExtUri(() => ignorePathCasing));
|
|
62
|
+
return time('searchInFiles', async () => {
|
|
63
|
+
const token = this.registerCancellationToken(queryId);
|
|
64
|
+
const results = [];
|
|
65
|
+
const pattern = createSearchRegExp(query.contentPattern);
|
|
66
|
+
const onGoingProcesses = [];
|
|
67
|
+
let resultCount = 0;
|
|
68
|
+
const limitHit = false;
|
|
69
|
+
const processFile = async (file) => {
|
|
70
|
+
if (token.token.isCancellationRequested) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const contents = await file.resolve();
|
|
74
|
+
if (token.token.isCancellationRequested) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const bytes = ( new Uint8Array(contents));
|
|
78
|
+
const fileResults = getFileResults(bytes, pattern, {
|
|
79
|
+
afterContext: query.afterContext ?? 0,
|
|
80
|
+
beforeContext: query.beforeContext ?? 0,
|
|
81
|
+
previewOptions: query.previewOptions,
|
|
82
|
+
remainingResultQuota: query.maxResults ? (query.maxResults - resultCount) : 10000,
|
|
83
|
+
});
|
|
84
|
+
if (fileResults.length) {
|
|
85
|
+
resultCount += fileResults.length;
|
|
86
|
+
if (query.maxResults && resultCount > query.maxResults) {
|
|
87
|
+
token.cancel();
|
|
88
|
+
}
|
|
89
|
+
const match = {
|
|
90
|
+
resource: URI.joinPath(revivedQuery.folder, file.path),
|
|
91
|
+
results: fileResults,
|
|
92
|
+
};
|
|
93
|
+
this.host.sendTextSearchMatch(match, queryId);
|
|
94
|
+
results.push(match);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
await time('walkFolderToResolve', () => this.walkFolderQuery(handle, reviveQueryProps(query), revivedQuery, extUri, async (file) => onGoingProcesses.push(processFile(file)), token.token));
|
|
98
|
+
await time('resolveOngoingProcesses', () => Promise.all(onGoingProcesses));
|
|
99
|
+
return {
|
|
100
|
+
results,
|
|
101
|
+
limitHit,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async walkFolderQuery(handle, queryProps, folderQuery, extUri, onFile, token) {
|
|
106
|
+
const folderExcludes = parse(folderQuery.excludePattern ?? {}, { trimForExclusions: true });
|
|
107
|
+
const isFolderExcluded = (path, basename, hasSibling) => {
|
|
108
|
+
path = path.slice(1);
|
|
109
|
+
if (folderExcludes(path, basename, hasSibling)) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (pathExcludedInQuery(queryProps, path)) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
};
|
|
117
|
+
const isFileIncluded = (path, basename, hasSibling) => {
|
|
118
|
+
path = path.slice(1);
|
|
119
|
+
if (folderExcludes(path, basename, hasSibling)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if (!pathIncludedInQuery(queryProps, path, extUri)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
};
|
|
127
|
+
const processFile = (file, prior) => {
|
|
128
|
+
const resolved = {
|
|
129
|
+
type: 'file',
|
|
130
|
+
name: file.name,
|
|
131
|
+
path: prior,
|
|
132
|
+
resolve: () => file.getFile().then(r => r.arrayBuffer())
|
|
133
|
+
};
|
|
134
|
+
return resolved;
|
|
135
|
+
};
|
|
136
|
+
const isFileSystemDirectoryHandle = (handle) => {
|
|
137
|
+
return handle.kind === 'directory';
|
|
138
|
+
};
|
|
139
|
+
const isFileSystemFileHandle = (handle) => {
|
|
140
|
+
return handle.kind === 'file';
|
|
141
|
+
};
|
|
142
|
+
const processDirectory = async (directory, prior, ignoreFile) => {
|
|
143
|
+
if (!folderQuery.disregardIgnoreFiles) {
|
|
144
|
+
const ignoreFiles = await Promise.all([
|
|
145
|
+
directory.getFileHandle('.gitignore').catch(e => undefined),
|
|
146
|
+
directory.getFileHandle('.ignore').catch(e => undefined),
|
|
147
|
+
]);
|
|
148
|
+
await Promise.all(( ignoreFiles.map(async (file) => {
|
|
149
|
+
if (!file) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const ignoreContents = ( new TextDecoder('utf8')).decode(( new Uint8Array(await (await file.getFile()).arrayBuffer())));
|
|
153
|
+
ignoreFile = ( new IgnoreFile(ignoreContents, prior, ignoreFile));
|
|
154
|
+
})));
|
|
155
|
+
}
|
|
156
|
+
const entries = Promises.withAsyncBody(async (c) => {
|
|
157
|
+
const files = [];
|
|
158
|
+
const dirs = [];
|
|
159
|
+
const entries = [];
|
|
160
|
+
const sibilings = ( new Set());
|
|
161
|
+
for await (const entry of directory.entries()) {
|
|
162
|
+
entries.push(entry);
|
|
163
|
+
sibilings.add(entry[0]);
|
|
164
|
+
}
|
|
165
|
+
for (const [basename, handle] of entries) {
|
|
166
|
+
if (token.isCancellationRequested) {
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
const path = prior + basename;
|
|
170
|
+
if (ignoreFile && !ignoreFile.isPathIncludedInTraversal(path, handle.kind === 'directory')) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const hasSibling = (query) => ( sibilings.has(query));
|
|
174
|
+
if (isFileSystemDirectoryHandle(handle) && !isFolderExcluded(path, basename, hasSibling)) {
|
|
175
|
+
dirs.push(processDirectory(handle, path + '/', ignoreFile));
|
|
176
|
+
}
|
|
177
|
+
else if (isFileSystemFileHandle(handle) && isFileIncluded(path, basename, hasSibling)) {
|
|
178
|
+
files.push(processFile(handle, path));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
c([...(await Promise.all(dirs)), ...files]);
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
type: 'dir',
|
|
185
|
+
name: directory.name,
|
|
186
|
+
entries
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
const resolveDirectory = async (directory, onFile) => {
|
|
190
|
+
if (token.isCancellationRequested) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
await Promise.all(( (await directory.entries)
|
|
194
|
+
.sort((a, b) => -(a.type === 'dir' ? 0 : 1) + (b.type === 'dir' ? 0 : 1))
|
|
195
|
+
.map(async (entry) => {
|
|
196
|
+
if (entry.type === 'dir') {
|
|
197
|
+
return resolveDirectory(entry, onFile);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
return onFile(entry);
|
|
201
|
+
}
|
|
202
|
+
})));
|
|
203
|
+
};
|
|
204
|
+
const processed = await time('process', () => processDirectory(handle, '/'));
|
|
205
|
+
await time('resolve', () => resolveDirectory(processed, onFile));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function createSearchRegExp(options) {
|
|
209
|
+
return createRegExp(options.pattern, !!options.isRegExp, {
|
|
210
|
+
wholeWord: options.isWordMatch,
|
|
211
|
+
global: true,
|
|
212
|
+
matchCase: options.isCaseSensitive,
|
|
213
|
+
multiline: true,
|
|
214
|
+
unicode: true,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function reviveFolderQuery(folderQuery) {
|
|
218
|
+
return {
|
|
219
|
+
...folderQuery,
|
|
220
|
+
folder: URI.revive(folderQuery.folder),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function reviveQueryProps(queryProps) {
|
|
224
|
+
return {
|
|
225
|
+
...queryProps,
|
|
226
|
+
extraFileResources: queryProps.extraFileResources?.map(r => URI.revive(r)),
|
|
227
|
+
folderQueries: ( queryProps.folderQueries.map(fq => reviveFolderQuery(fq))),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function pathExcludedInQuery(queryProps, fsPath) {
|
|
231
|
+
if (queryProps.excludePattern && match(queryProps.excludePattern, fsPath)) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
function pathIncludedInQuery(queryProps, path, extUri) {
|
|
237
|
+
if (queryProps.excludePattern && match(queryProps.excludePattern, path)) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
if (queryProps.includePattern || queryProps.usingSearchPaths) {
|
|
241
|
+
if (queryProps.includePattern && match(queryProps.includePattern, path)) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
if (queryProps.usingSearchPaths) {
|
|
245
|
+
return !!queryProps.folderQueries && ( queryProps.folderQueries.some(fq => {
|
|
246
|
+
const searchPath = fq.folder;
|
|
247
|
+
const uri = URI.file(path);
|
|
248
|
+
if (extUri.isEqualOrParent(uri, searchPath)) {
|
|
249
|
+
const relPath = relative(searchPath.path, uri.path);
|
|
250
|
+
return !fq.includePattern || !!match(fq.includePattern, relPath);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export { LocalFileSearchSimpleWorker, create };
|
package/worker.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './workers/localFileSearch.worker.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { create } from '../vscode/src/vs/workbench/services/search/worker/localFileSearch.js';
|
|
2
|
+
import { SimpleWorkerServer } from 'vscode/vscode/vs/base/common/worker/simpleWorker';
|
|
3
|
+
|
|
4
|
+
const simpleWorker = new SimpleWorkerServer((msg) => {
|
|
5
|
+
globalThis.postMessage(msg);
|
|
6
|
+
}, create);
|
|
7
|
+
globalThis.onmessage = (e) => {
|
|
8
|
+
simpleWorker.onmessage(e.data);
|
|
9
|
+
};
|