@theia/file-search 1.45.0 → 1.46.0-next.72
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/README.md +30 -30
- package/lib/browser/file-search-frontend-module.d.ts +3 -3
- package/lib/browser/file-search-frontend-module.js +33 -33
- package/lib/browser/quick-file-open-contribution.d.ts +10 -10
- package/lib/browser/quick-file-open-contribution.js +75 -75
- package/lib/browser/quick-file-open.d.ts +79 -79
- package/lib/browser/quick-file-open.js +380 -380
- package/lib/common/file-search-service.d.ts +30 -30
- package/lib/common/file-search-service.js +21 -21
- package/lib/node/file-search-backend-module.d.ts +3 -3
- package/lib/node/file-search-backend-module.js +25 -25
- package/lib/node/file-search-service-impl.d.ts +15 -15
- package/lib/node/file-search-service-impl.js +196 -196
- package/lib/node/file-search-service-impl.js.map +1 -1
- package/lib/node/file-search-service-impl.spec.d.ts +1 -1
- package/lib/node/file-search-service-impl.spec.js +195 -195
- package/package.json +8 -8
- package/src/browser/file-search-frontend-module.ts +37 -37
- package/src/browser/quick-file-open-contribution.ts +66 -66
- package/src/browser/quick-file-open.ts +387 -387
- package/src/common/file-search-service.ts +52 -52
- package/src/node/file-search-backend-module.ts +30 -30
- package/src/node/file-search-service-impl.spec.ts +230 -230
- package/src/node/file-search-service-impl.ts +187 -187
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
// *****************************************************************************
|
|
2
|
-
// Copyright (C) 2017 TypeFox and others.
|
|
3
|
-
//
|
|
4
|
-
// This program and the accompanying materials are made available under the
|
|
5
|
-
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
//
|
|
8
|
-
// This Source Code may also be made available under the following Secondary
|
|
9
|
-
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
// with the GNU Classpath Exception which is available at
|
|
12
|
-
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
//
|
|
14
|
-
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import { CancellationToken } from '@theia/core';
|
|
18
|
-
|
|
19
|
-
export const fileSearchServicePath = '/services/search';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* The JSON-RPC file search service interface.
|
|
23
|
-
*/
|
|
24
|
-
export interface FileSearchService {
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* finds files by a given search pattern.
|
|
28
|
-
* @return the matching file uris
|
|
29
|
-
*/
|
|
30
|
-
find(searchPattern: string, options: FileSearchService.Options, cancellationToken?: CancellationToken): Promise<string[]>;
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export const FileSearchService = Symbol('FileSearchService');
|
|
35
|
-
export namespace FileSearchService {
|
|
36
|
-
export interface BaseOptions {
|
|
37
|
-
useGitIgnore?: boolean
|
|
38
|
-
includePatterns?: string[]
|
|
39
|
-
excludePatterns?: string[]
|
|
40
|
-
}
|
|
41
|
-
export interface RootOptions {
|
|
42
|
-
[rootUri: string]: BaseOptions
|
|
43
|
-
}
|
|
44
|
-
export interface Options extends BaseOptions {
|
|
45
|
-
rootUris?: string[]
|
|
46
|
-
rootOptions?: RootOptions
|
|
47
|
-
fuzzyMatch?: boolean
|
|
48
|
-
limit?: number
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const WHITESPACE_QUERY_SEPARATOR = /\s+/;
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2017 TypeFox and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { CancellationToken } from '@theia/core';
|
|
18
|
+
|
|
19
|
+
export const fileSearchServicePath = '/services/search';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The JSON-RPC file search service interface.
|
|
23
|
+
*/
|
|
24
|
+
export interface FileSearchService {
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* finds files by a given search pattern.
|
|
28
|
+
* @return the matching file uris
|
|
29
|
+
*/
|
|
30
|
+
find(searchPattern: string, options: FileSearchService.Options, cancellationToken?: CancellationToken): Promise<string[]>;
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const FileSearchService = Symbol('FileSearchService');
|
|
35
|
+
export namespace FileSearchService {
|
|
36
|
+
export interface BaseOptions {
|
|
37
|
+
useGitIgnore?: boolean
|
|
38
|
+
includePatterns?: string[]
|
|
39
|
+
excludePatterns?: string[]
|
|
40
|
+
}
|
|
41
|
+
export interface RootOptions {
|
|
42
|
+
[rootUri: string]: BaseOptions
|
|
43
|
+
}
|
|
44
|
+
export interface Options extends BaseOptions {
|
|
45
|
+
rootUris?: string[]
|
|
46
|
+
rootOptions?: RootOptions
|
|
47
|
+
fuzzyMatch?: boolean
|
|
48
|
+
limit?: number
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const WHITESPACE_QUERY_SEPARATOR = /\s+/;
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
// *****************************************************************************
|
|
2
|
-
// Copyright (C) 2017 TypeFox and others.
|
|
3
|
-
//
|
|
4
|
-
// This program and the accompanying materials are made available under the
|
|
5
|
-
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
//
|
|
8
|
-
// This Source Code may also be made available under the following Secondary
|
|
9
|
-
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
// with the GNU Classpath Exception which is available at
|
|
12
|
-
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
//
|
|
14
|
-
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import { ContainerModule } from '@theia/core/shared/inversify';
|
|
18
|
-
import { ConnectionHandler, RpcConnectionHandler } from '@theia/core/lib/common';
|
|
19
|
-
import { FileSearchServiceImpl } from './file-search-service-impl';
|
|
20
|
-
import { fileSearchServicePath, FileSearchService } from '../common/file-search-service';
|
|
21
|
-
|
|
22
|
-
export default new ContainerModule(bind => {
|
|
23
|
-
|
|
24
|
-
bind(FileSearchService).to(FileSearchServiceImpl).inSingletonScope();
|
|
25
|
-
bind(ConnectionHandler).toDynamicValue(ctx =>
|
|
26
|
-
new RpcConnectionHandler(fileSearchServicePath, () =>
|
|
27
|
-
ctx.container.get(FileSearchService)
|
|
28
|
-
)
|
|
29
|
-
).inSingletonScope();
|
|
30
|
-
});
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2017 TypeFox and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { ContainerModule } from '@theia/core/shared/inversify';
|
|
18
|
+
import { ConnectionHandler, RpcConnectionHandler } from '@theia/core/lib/common';
|
|
19
|
+
import { FileSearchServiceImpl } from './file-search-service-impl';
|
|
20
|
+
import { fileSearchServicePath, FileSearchService } from '../common/file-search-service';
|
|
21
|
+
|
|
22
|
+
export default new ContainerModule(bind => {
|
|
23
|
+
|
|
24
|
+
bind(FileSearchService).to(FileSearchServiceImpl).inSingletonScope();
|
|
25
|
+
bind(ConnectionHandler).toDynamicValue(ctx =>
|
|
26
|
+
new RpcConnectionHandler(fileSearchServicePath, () =>
|
|
27
|
+
ctx.container.get(FileSearchService)
|
|
28
|
+
)
|
|
29
|
+
).inSingletonScope();
|
|
30
|
+
});
|
|
@@ -1,230 +1,230 @@
|
|
|
1
|
-
// *****************************************************************************
|
|
2
|
-
// Copyright (C) 2017 TypeFox and others.
|
|
3
|
-
//
|
|
4
|
-
// This program and the accompanying materials are made available under the
|
|
5
|
-
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
//
|
|
8
|
-
// This Source Code may also be made available under the following Secondary
|
|
9
|
-
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
// with the GNU Classpath Exception which is available at
|
|
12
|
-
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
//
|
|
14
|
-
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import { expect } from 'chai';
|
|
18
|
-
import * as assert from 'assert';
|
|
19
|
-
import * as path from 'path';
|
|
20
|
-
import { FileSearchServiceImpl } from './file-search-service-impl';
|
|
21
|
-
import { FileUri } from '@theia/core/lib/node';
|
|
22
|
-
import { Container, ContainerModule } from '@theia/core/shared/inversify';
|
|
23
|
-
import { CancellationTokenSource } from '@theia/core';
|
|
24
|
-
import { bindLogger } from '@theia/core/lib/node/logger-backend-module';
|
|
25
|
-
import URI from '@theia/core/lib/common/uri';
|
|
26
|
-
import { FileSearchService } from '../common/file-search-service';
|
|
27
|
-
import { RawProcessFactory } from '@theia/process/lib/node';
|
|
28
|
-
|
|
29
|
-
const testContainer = new Container();
|
|
30
|
-
|
|
31
|
-
bindLogger(testContainer.bind.bind(testContainer));
|
|
32
|
-
testContainer.bind(RawProcessFactory).toConstantValue(() => {
|
|
33
|
-
throw new Error('should not be used anymore');
|
|
34
|
-
});
|
|
35
|
-
testContainer.load(new ContainerModule(bind => {
|
|
36
|
-
bind(FileSearchServiceImpl).toSelf().inSingletonScope();
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
describe('search-service', function (): void {
|
|
40
|
-
|
|
41
|
-
this.timeout(10000);
|
|
42
|
-
|
|
43
|
-
let service: FileSearchServiceImpl;
|
|
44
|
-
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
service = testContainer.get(FileSearchServiceImpl);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should fuzzy search this spec file', async () => {
|
|
50
|
-
const rootUri = FileUri.create(path.resolve(__dirname, '..')).toString();
|
|
51
|
-
const matches = await service.find('spc', { rootUris: [rootUri] });
|
|
52
|
-
const expectedFile = FileUri.create(__filename).path.base;
|
|
53
|
-
const testFile = matches.find(e => e.endsWith(expectedFile));
|
|
54
|
-
expect(testFile).to.not.be.undefined;
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it.skip('should respect nested .gitignore', async () => {
|
|
58
|
-
const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources')).toString();
|
|
59
|
-
const matches = await service.find('foo', { rootUris: [rootUri], fuzzyMatch: false });
|
|
60
|
-
|
|
61
|
-
expect(matches.find(match => match.endsWith('subdir1/sub-bar/foo.txt'))).to.be.undefined;
|
|
62
|
-
expect(matches.find(match => match.endsWith('subdir1/sub2/foo.txt'))).to.not.be.undefined;
|
|
63
|
-
expect(matches.find(match => match.endsWith('subdir1/foo.txt'))).to.not.be.undefined;
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should cancel searches', async () => {
|
|
67
|
-
const rootUri = FileUri.create(path.resolve(__dirname, '../../../../..')).toString();
|
|
68
|
-
const cancelTokenSource = new CancellationTokenSource();
|
|
69
|
-
cancelTokenSource.cancel();
|
|
70
|
-
const matches = await service.find('foo', { rootUris: [rootUri], fuzzyMatch: false }, cancelTokenSource.token);
|
|
71
|
-
|
|
72
|
-
expect(matches).to.be.empty;
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should perform file search across all folders in the workspace', async () => {
|
|
76
|
-
const dirA = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub-bar')).toString();
|
|
77
|
-
const dirB = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString();
|
|
78
|
-
|
|
79
|
-
const matches = await service.find('foo', { rootUris: [dirA, dirB] });
|
|
80
|
-
expect(matches).to.not.be.undefined;
|
|
81
|
-
expect(matches.length).to.eq(2);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe('search with glob', () => {
|
|
85
|
-
it('should support file searches with globs', async () => {
|
|
86
|
-
const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString();
|
|
87
|
-
|
|
88
|
-
const matches = await service.find('', { rootUris: [rootUri], includePatterns: ['**/*oo.*'] });
|
|
89
|
-
expect(matches).to.not.be.undefined;
|
|
90
|
-
expect(matches.length).to.eq(1);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should NOT support file searches with globs without the prefixed or trailing star (*)', async () => {
|
|
94
|
-
const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString();
|
|
95
|
-
|
|
96
|
-
const trailingMatches = await service.find('', { rootUris: [rootUri], includePatterns: ['*oo'] });
|
|
97
|
-
expect(trailingMatches).to.not.be.undefined;
|
|
98
|
-
expect(trailingMatches.length).to.eq(0);
|
|
99
|
-
|
|
100
|
-
const prefixedMatches = await service.find('', { rootUris: [rootUri], includePatterns: ['oo*'] });
|
|
101
|
-
expect(prefixedMatches).to.not.be.undefined;
|
|
102
|
-
expect(prefixedMatches.length).to.eq(0);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe('search with ignored patterns', () => {
|
|
107
|
-
it('should NOT ignore strings passed through the search options', async () => {
|
|
108
|
-
const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString();
|
|
109
|
-
|
|
110
|
-
const matches = await service.find('', { rootUris: [rootUri], includePatterns: ['**/*oo.*'], excludePatterns: ['foo'] });
|
|
111
|
-
expect(matches).to.not.be.undefined;
|
|
112
|
-
expect(matches.length).to.eq(1);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const ignoreGlobsUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString();
|
|
116
|
-
it('should ignore globs passed through the search options #1', () => assertIgnoreGlobs({
|
|
117
|
-
rootUris: [ignoreGlobsUri],
|
|
118
|
-
includePatterns: ['**/*oo.*'],
|
|
119
|
-
excludePatterns: ['*fo*']
|
|
120
|
-
}));
|
|
121
|
-
it('should ignore globs passed through the search options #2', () => assertIgnoreGlobs({
|
|
122
|
-
rootOptions: {
|
|
123
|
-
[ignoreGlobsUri]: {
|
|
124
|
-
includePatterns: ['**/*oo.*'],
|
|
125
|
-
excludePatterns: ['*fo*']
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}));
|
|
129
|
-
it('should ignore globs passed through the search options #3', () => assertIgnoreGlobs({
|
|
130
|
-
rootOptions: {
|
|
131
|
-
[ignoreGlobsUri]: {
|
|
132
|
-
includePatterns: ['**/*oo.*']
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
excludePatterns: ['*fo*']
|
|
136
|
-
}));
|
|
137
|
-
it('should ignore globs passed through the search options #4', () => assertIgnoreGlobs({
|
|
138
|
-
rootOptions: {
|
|
139
|
-
[ignoreGlobsUri]: {
|
|
140
|
-
excludePatterns: ['*fo*']
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
includePatterns: ['**/*oo.*']
|
|
144
|
-
}));
|
|
145
|
-
it('should ignore globs passed through the search options #5', () => assertIgnoreGlobs({
|
|
146
|
-
rootOptions: {
|
|
147
|
-
[ignoreGlobsUri]: {}
|
|
148
|
-
},
|
|
149
|
-
excludePatterns: ['*fo*'],
|
|
150
|
-
includePatterns: ['**/*oo.*']
|
|
151
|
-
}));
|
|
152
|
-
|
|
153
|
-
async function assertIgnoreGlobs(options: FileSearchService.Options): Promise<void> {
|
|
154
|
-
const matches = await service.find('', options);
|
|
155
|
-
expect(matches).to.not.be.undefined;
|
|
156
|
-
expect(matches.length).to.eq(0);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
describe('irrelevant absolute results', () => {
|
|
161
|
-
const rootUri = FileUri.create(path.resolve(__dirname, '../../../..'));
|
|
162
|
-
|
|
163
|
-
it('not fuzzy', async () => {
|
|
164
|
-
const searchPattern = 'package'; // package.json should produce a result.
|
|
165
|
-
const matches = await service.find(searchPattern, { rootUris: [rootUri.toString()], fuzzyMatch: false, useGitIgnore: true, limit: 200 });
|
|
166
|
-
expect(matches).not.empty;
|
|
167
|
-
for (const match of matches) {
|
|
168
|
-
const relativeUri = rootUri.relative(new URI(match));
|
|
169
|
-
assert.notStrictEqual(relativeUri, undefined);
|
|
170
|
-
const relativeMatch = relativeUri!.toString();
|
|
171
|
-
assert.notStrictEqual(relativeMatch.indexOf(searchPattern), -1, relativeMatch);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('fuzzy', async () => {
|
|
176
|
-
const matches = await service.find('shell', { rootUris: [rootUri.toString()], fuzzyMatch: true, useGitIgnore: true, limit: 200 });
|
|
177
|
-
expect(matches).not.empty;
|
|
178
|
-
for (const match of matches) {
|
|
179
|
-
const relativeUri = rootUri.relative(new URI(match));
|
|
180
|
-
assert.notStrictEqual(relativeUri, undefined);
|
|
181
|
-
const relativeMatch = relativeUri!.toString();
|
|
182
|
-
let position = 0;
|
|
183
|
-
for (const ch of 'shell') {
|
|
184
|
-
position = relativeMatch.toLowerCase().indexOf(ch, position);
|
|
185
|
-
assert.notStrictEqual(position, -1, `character "${ch}" not found in "${relativeMatch}"`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should not look into .git', async () => {
|
|
191
|
-
const matches = await service.find('master', { rootUris: [rootUri.toString()], fuzzyMatch: false, useGitIgnore: true, limit: 200 });
|
|
192
|
-
// `**/.git/refs/remotes/*/master` files should not be picked up
|
|
193
|
-
assert.deepStrictEqual([], matches);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('search with whitespaces', () => {
|
|
198
|
-
const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources')).toString();
|
|
199
|
-
|
|
200
|
-
it('should support file searches with whitespaces', async () => {
|
|
201
|
-
const matches = await service.find('foo sub', { rootUris: [rootUri], fuzzyMatch: true, useGitIgnore: true, limit: 200 });
|
|
202
|
-
|
|
203
|
-
expect(matches).to.be.length(2);
|
|
204
|
-
expect(matches[0].endsWith('subdir1/sub-bar/foo.txt'));
|
|
205
|
-
expect(matches[1].endsWith('subdir1/sub2/foo.txt'));
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it('should support fuzzy file searches with whitespaces', async () => {
|
|
209
|
-
const matchesExact = await service.find('foo sbd2', { rootUris: [rootUri], fuzzyMatch: false, useGitIgnore: true, limit: 200 });
|
|
210
|
-
const matchesFuzzy = await service.find('foo sbd2', { rootUris: [rootUri], fuzzyMatch: true, useGitIgnore: true, limit: 200 });
|
|
211
|
-
|
|
212
|
-
expect(matchesExact).to.be.length(0);
|
|
213
|
-
expect(matchesFuzzy).to.be.length(1);
|
|
214
|
-
expect(matchesFuzzy[0].endsWith('subdir1/sub2/foo.txt'));
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('should support file searches with whitespaces regardless of order', async () => {
|
|
218
|
-
const matchesA = await service.find('foo sub', { rootUris: [rootUri], fuzzyMatch: true, useGitIgnore: true, limit: 200 });
|
|
219
|
-
const matchesB = await service.find('sub foo', { rootUris: [rootUri], fuzzyMatch: true, useGitIgnore: true, limit: 200 });
|
|
220
|
-
|
|
221
|
-
expect(matchesA).to.not.be.empty;
|
|
222
|
-
expect(matchesB).to.not.be.empty;
|
|
223
|
-
expect(matchesA.length).to.equal(matchesB.length);
|
|
224
|
-
|
|
225
|
-
// Due to ripgrep parallelism we cannot deepEqual the matches since order is not guaranteed.
|
|
226
|
-
expect(matchesA).to.have.members(matchesB);
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
});
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2017 TypeFox and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { expect } from 'chai';
|
|
18
|
+
import * as assert from 'assert';
|
|
19
|
+
import * as path from 'path';
|
|
20
|
+
import { FileSearchServiceImpl } from './file-search-service-impl';
|
|
21
|
+
import { FileUri } from '@theia/core/lib/node';
|
|
22
|
+
import { Container, ContainerModule } from '@theia/core/shared/inversify';
|
|
23
|
+
import { CancellationTokenSource } from '@theia/core';
|
|
24
|
+
import { bindLogger } from '@theia/core/lib/node/logger-backend-module';
|
|
25
|
+
import URI from '@theia/core/lib/common/uri';
|
|
26
|
+
import { FileSearchService } from '../common/file-search-service';
|
|
27
|
+
import { RawProcessFactory } from '@theia/process/lib/node';
|
|
28
|
+
|
|
29
|
+
const testContainer = new Container();
|
|
30
|
+
|
|
31
|
+
bindLogger(testContainer.bind.bind(testContainer));
|
|
32
|
+
testContainer.bind(RawProcessFactory).toConstantValue(() => {
|
|
33
|
+
throw new Error('should not be used anymore');
|
|
34
|
+
});
|
|
35
|
+
testContainer.load(new ContainerModule(bind => {
|
|
36
|
+
bind(FileSearchServiceImpl).toSelf().inSingletonScope();
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
describe('search-service', function (): void {
|
|
40
|
+
|
|
41
|
+
this.timeout(10000);
|
|
42
|
+
|
|
43
|
+
let service: FileSearchServiceImpl;
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
service = testContainer.get(FileSearchServiceImpl);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should fuzzy search this spec file', async () => {
|
|
50
|
+
const rootUri = FileUri.create(path.resolve(__dirname, '..')).toString();
|
|
51
|
+
const matches = await service.find('spc', { rootUris: [rootUri] });
|
|
52
|
+
const expectedFile = FileUri.create(__filename).path.base;
|
|
53
|
+
const testFile = matches.find(e => e.endsWith(expectedFile));
|
|
54
|
+
expect(testFile).to.not.be.undefined;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it.skip('should respect nested .gitignore', async () => {
|
|
58
|
+
const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources')).toString();
|
|
59
|
+
const matches = await service.find('foo', { rootUris: [rootUri], fuzzyMatch: false });
|
|
60
|
+
|
|
61
|
+
expect(matches.find(match => match.endsWith('subdir1/sub-bar/foo.txt'))).to.be.undefined;
|
|
62
|
+
expect(matches.find(match => match.endsWith('subdir1/sub2/foo.txt'))).to.not.be.undefined;
|
|
63
|
+
expect(matches.find(match => match.endsWith('subdir1/foo.txt'))).to.not.be.undefined;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should cancel searches', async () => {
|
|
67
|
+
const rootUri = FileUri.create(path.resolve(__dirname, '../../../../..')).toString();
|
|
68
|
+
const cancelTokenSource = new CancellationTokenSource();
|
|
69
|
+
cancelTokenSource.cancel();
|
|
70
|
+
const matches = await service.find('foo', { rootUris: [rootUri], fuzzyMatch: false }, cancelTokenSource.token);
|
|
71
|
+
|
|
72
|
+
expect(matches).to.be.empty;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should perform file search across all folders in the workspace', async () => {
|
|
76
|
+
const dirA = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub-bar')).toString();
|
|
77
|
+
const dirB = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString();
|
|
78
|
+
|
|
79
|
+
const matches = await service.find('foo', { rootUris: [dirA, dirB] });
|
|
80
|
+
expect(matches).to.not.be.undefined;
|
|
81
|
+
expect(matches.length).to.eq(2);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('search with glob', () => {
|
|
85
|
+
it('should support file searches with globs', async () => {
|
|
86
|
+
const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString();
|
|
87
|
+
|
|
88
|
+
const matches = await service.find('', { rootUris: [rootUri], includePatterns: ['**/*oo.*'] });
|
|
89
|
+
expect(matches).to.not.be.undefined;
|
|
90
|
+
expect(matches.length).to.eq(1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should NOT support file searches with globs without the prefixed or trailing star (*)', async () => {
|
|
94
|
+
const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString();
|
|
95
|
+
|
|
96
|
+
const trailingMatches = await service.find('', { rootUris: [rootUri], includePatterns: ['*oo'] });
|
|
97
|
+
expect(trailingMatches).to.not.be.undefined;
|
|
98
|
+
expect(trailingMatches.length).to.eq(0);
|
|
99
|
+
|
|
100
|
+
const prefixedMatches = await service.find('', { rootUris: [rootUri], includePatterns: ['oo*'] });
|
|
101
|
+
expect(prefixedMatches).to.not.be.undefined;
|
|
102
|
+
expect(prefixedMatches.length).to.eq(0);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('search with ignored patterns', () => {
|
|
107
|
+
it('should NOT ignore strings passed through the search options', async () => {
|
|
108
|
+
const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString();
|
|
109
|
+
|
|
110
|
+
const matches = await service.find('', { rootUris: [rootUri], includePatterns: ['**/*oo.*'], excludePatterns: ['foo'] });
|
|
111
|
+
expect(matches).to.not.be.undefined;
|
|
112
|
+
expect(matches.length).to.eq(1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const ignoreGlobsUri = FileUri.create(path.resolve(__dirname, '../../test-resources/subdir1/sub2')).toString();
|
|
116
|
+
it('should ignore globs passed through the search options #1', () => assertIgnoreGlobs({
|
|
117
|
+
rootUris: [ignoreGlobsUri],
|
|
118
|
+
includePatterns: ['**/*oo.*'],
|
|
119
|
+
excludePatterns: ['*fo*']
|
|
120
|
+
}));
|
|
121
|
+
it('should ignore globs passed through the search options #2', () => assertIgnoreGlobs({
|
|
122
|
+
rootOptions: {
|
|
123
|
+
[ignoreGlobsUri]: {
|
|
124
|
+
includePatterns: ['**/*oo.*'],
|
|
125
|
+
excludePatterns: ['*fo*']
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}));
|
|
129
|
+
it('should ignore globs passed through the search options #3', () => assertIgnoreGlobs({
|
|
130
|
+
rootOptions: {
|
|
131
|
+
[ignoreGlobsUri]: {
|
|
132
|
+
includePatterns: ['**/*oo.*']
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
excludePatterns: ['*fo*']
|
|
136
|
+
}));
|
|
137
|
+
it('should ignore globs passed through the search options #4', () => assertIgnoreGlobs({
|
|
138
|
+
rootOptions: {
|
|
139
|
+
[ignoreGlobsUri]: {
|
|
140
|
+
excludePatterns: ['*fo*']
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
includePatterns: ['**/*oo.*']
|
|
144
|
+
}));
|
|
145
|
+
it('should ignore globs passed through the search options #5', () => assertIgnoreGlobs({
|
|
146
|
+
rootOptions: {
|
|
147
|
+
[ignoreGlobsUri]: {}
|
|
148
|
+
},
|
|
149
|
+
excludePatterns: ['*fo*'],
|
|
150
|
+
includePatterns: ['**/*oo.*']
|
|
151
|
+
}));
|
|
152
|
+
|
|
153
|
+
async function assertIgnoreGlobs(options: FileSearchService.Options): Promise<void> {
|
|
154
|
+
const matches = await service.find('', options);
|
|
155
|
+
expect(matches).to.not.be.undefined;
|
|
156
|
+
expect(matches.length).to.eq(0);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('irrelevant absolute results', () => {
|
|
161
|
+
const rootUri = FileUri.create(path.resolve(__dirname, '../../../..'));
|
|
162
|
+
|
|
163
|
+
it('not fuzzy', async () => {
|
|
164
|
+
const searchPattern = 'package'; // package.json should produce a result.
|
|
165
|
+
const matches = await service.find(searchPattern, { rootUris: [rootUri.toString()], fuzzyMatch: false, useGitIgnore: true, limit: 200 });
|
|
166
|
+
expect(matches).not.empty;
|
|
167
|
+
for (const match of matches) {
|
|
168
|
+
const relativeUri = rootUri.relative(new URI(match));
|
|
169
|
+
assert.notStrictEqual(relativeUri, undefined);
|
|
170
|
+
const relativeMatch = relativeUri!.toString();
|
|
171
|
+
assert.notStrictEqual(relativeMatch.indexOf(searchPattern), -1, relativeMatch);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('fuzzy', async () => {
|
|
176
|
+
const matches = await service.find('shell', { rootUris: [rootUri.toString()], fuzzyMatch: true, useGitIgnore: true, limit: 200 });
|
|
177
|
+
expect(matches).not.empty;
|
|
178
|
+
for (const match of matches) {
|
|
179
|
+
const relativeUri = rootUri.relative(new URI(match));
|
|
180
|
+
assert.notStrictEqual(relativeUri, undefined);
|
|
181
|
+
const relativeMatch = relativeUri!.toString();
|
|
182
|
+
let position = 0;
|
|
183
|
+
for (const ch of 'shell') {
|
|
184
|
+
position = relativeMatch.toLowerCase().indexOf(ch, position);
|
|
185
|
+
assert.notStrictEqual(position, -1, `character "${ch}" not found in "${relativeMatch}"`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should not look into .git', async () => {
|
|
191
|
+
const matches = await service.find('master', { rootUris: [rootUri.toString()], fuzzyMatch: false, useGitIgnore: true, limit: 200 });
|
|
192
|
+
// `**/.git/refs/remotes/*/master` files should not be picked up
|
|
193
|
+
assert.deepStrictEqual([], matches);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('search with whitespaces', () => {
|
|
198
|
+
const rootUri = FileUri.create(path.resolve(__dirname, '../../test-resources')).toString();
|
|
199
|
+
|
|
200
|
+
it('should support file searches with whitespaces', async () => {
|
|
201
|
+
const matches = await service.find('foo sub', { rootUris: [rootUri], fuzzyMatch: true, useGitIgnore: true, limit: 200 });
|
|
202
|
+
|
|
203
|
+
expect(matches).to.be.length(2);
|
|
204
|
+
expect(matches[0].endsWith('subdir1/sub-bar/foo.txt'));
|
|
205
|
+
expect(matches[1].endsWith('subdir1/sub2/foo.txt'));
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should support fuzzy file searches with whitespaces', async () => {
|
|
209
|
+
const matchesExact = await service.find('foo sbd2', { rootUris: [rootUri], fuzzyMatch: false, useGitIgnore: true, limit: 200 });
|
|
210
|
+
const matchesFuzzy = await service.find('foo sbd2', { rootUris: [rootUri], fuzzyMatch: true, useGitIgnore: true, limit: 200 });
|
|
211
|
+
|
|
212
|
+
expect(matchesExact).to.be.length(0);
|
|
213
|
+
expect(matchesFuzzy).to.be.length(1);
|
|
214
|
+
expect(matchesFuzzy[0].endsWith('subdir1/sub2/foo.txt'));
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should support file searches with whitespaces regardless of order', async () => {
|
|
218
|
+
const matchesA = await service.find('foo sub', { rootUris: [rootUri], fuzzyMatch: true, useGitIgnore: true, limit: 200 });
|
|
219
|
+
const matchesB = await service.find('sub foo', { rootUris: [rootUri], fuzzyMatch: true, useGitIgnore: true, limit: 200 });
|
|
220
|
+
|
|
221
|
+
expect(matchesA).to.not.be.empty;
|
|
222
|
+
expect(matchesB).to.not.be.empty;
|
|
223
|
+
expect(matchesA.length).to.equal(matchesB.length);
|
|
224
|
+
|
|
225
|
+
// Due to ripgrep parallelism we cannot deepEqual the matches since order is not guaranteed.
|
|
226
|
+
expect(matchesA).to.have.members(matchesB);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
});
|