Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/common/resources.ts
3291 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { URI } from '../../base/common/uri.js';
7
import { equals } from '../../base/common/objects.js';
8
import { isAbsolute } from '../../base/common/path.js';
9
import { Emitter } from '../../base/common/event.js';
10
import { relativePath } from '../../base/common/resources.js';
11
import { Disposable } from '../../base/common/lifecycle.js';
12
import { ParsedExpression, IExpression, parse } from '../../base/common/glob.js';
13
import { IWorkspaceContextService } from '../../platform/workspace/common/workspace.js';
14
import { IConfigurationService, IConfigurationChangeEvent } from '../../platform/configuration/common/configuration.js';
15
import { Schemas } from '../../base/common/network.js';
16
import { ResourceSet } from '../../base/common/map.js';
17
import { getDriveLetter } from '../../base/common/extpath.js';
18
19
interface IConfiguredExpression {
20
readonly expression: IExpression;
21
readonly hasAbsolutePath: boolean;
22
}
23
24
export class ResourceGlobMatcher extends Disposable {
25
26
private static readonly NO_FOLDER = null;
27
28
private readonly _onExpressionChange = this._register(new Emitter<void>());
29
readonly onExpressionChange = this._onExpressionChange.event;
30
31
private readonly mapFolderToParsedExpression = new Map<string | null, ParsedExpression>();
32
private readonly mapFolderToConfiguredExpression = new Map<string | null, IConfiguredExpression>();
33
34
constructor(
35
private getExpression: (folder?: URI) => IExpression | undefined,
36
private shouldUpdate: (event: IConfigurationChangeEvent) => boolean,
37
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
38
@IConfigurationService private readonly configurationService: IConfigurationService
39
) {
40
super();
41
42
this.updateExpressions(false);
43
44
this.registerListeners();
45
}
46
47
private registerListeners(): void {
48
this._register(this.configurationService.onDidChangeConfiguration(e => {
49
if (this.shouldUpdate(e)) {
50
this.updateExpressions(true);
51
}
52
}));
53
54
this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateExpressions(true)));
55
}
56
57
private updateExpressions(fromEvent: boolean): void {
58
let changed = false;
59
60
// Add expressions per workspaces that got added
61
for (const folder of this.contextService.getWorkspace().folders) {
62
const folderUriStr = folder.uri.toString();
63
64
const newExpression = this.doGetExpression(folder.uri);
65
const currentExpression = this.mapFolderToConfiguredExpression.get(folderUriStr);
66
67
if (newExpression) {
68
if (!currentExpression || !equals(currentExpression.expression, newExpression.expression)) {
69
changed = true;
70
71
this.mapFolderToParsedExpression.set(folderUriStr, parse(newExpression.expression));
72
this.mapFolderToConfiguredExpression.set(folderUriStr, newExpression);
73
}
74
} else {
75
if (currentExpression) {
76
changed = true;
77
78
this.mapFolderToParsedExpression.delete(folderUriStr);
79
this.mapFolderToConfiguredExpression.delete(folderUriStr);
80
}
81
}
82
}
83
84
// Remove expressions per workspace no longer present
85
const foldersMap = new ResourceSet(this.contextService.getWorkspace().folders.map(folder => folder.uri));
86
for (const [folder] of this.mapFolderToConfiguredExpression) {
87
if (folder === ResourceGlobMatcher.NO_FOLDER) {
88
continue; // always keep this one
89
}
90
91
if (!foldersMap.has(URI.parse(folder))) {
92
this.mapFolderToParsedExpression.delete(folder);
93
this.mapFolderToConfiguredExpression.delete(folder);
94
95
changed = true;
96
}
97
}
98
99
// Always set for resources outside workspace as well
100
const globalNewExpression = this.doGetExpression(undefined);
101
const globalCurrentExpression = this.mapFolderToConfiguredExpression.get(ResourceGlobMatcher.NO_FOLDER);
102
if (globalNewExpression) {
103
if (!globalCurrentExpression || !equals(globalCurrentExpression.expression, globalNewExpression.expression)) {
104
changed = true;
105
106
this.mapFolderToParsedExpression.set(ResourceGlobMatcher.NO_FOLDER, parse(globalNewExpression.expression));
107
this.mapFolderToConfiguredExpression.set(ResourceGlobMatcher.NO_FOLDER, globalNewExpression);
108
}
109
} else {
110
if (globalCurrentExpression) {
111
changed = true;
112
113
this.mapFolderToParsedExpression.delete(ResourceGlobMatcher.NO_FOLDER);
114
this.mapFolderToConfiguredExpression.delete(ResourceGlobMatcher.NO_FOLDER);
115
}
116
}
117
118
if (fromEvent && changed) {
119
this._onExpressionChange.fire();
120
}
121
}
122
123
private doGetExpression(resource: URI | undefined): IConfiguredExpression | undefined {
124
const expression = this.getExpression(resource);
125
if (!expression) {
126
return undefined;
127
}
128
129
const keys = Object.keys(expression);
130
if (keys.length === 0) {
131
return undefined;
132
}
133
134
let hasAbsolutePath = false;
135
136
// Check the expression for absolute paths/globs
137
// and specifically for Windows, make sure the
138
// drive letter is lowercased, because we later
139
// check with `URI.fsPath` which is always putting
140
// the drive letter lowercased.
141
142
const massagedExpression: IExpression = Object.create(null);
143
for (const key of keys) {
144
if (!hasAbsolutePath) {
145
hasAbsolutePath = isAbsolute(key);
146
}
147
148
let massagedKey = key;
149
150
const driveLetter = getDriveLetter(massagedKey, true /* probe for windows */);
151
if (driveLetter) {
152
const driveLetterLower = driveLetter.toLowerCase();
153
if (driveLetter !== driveLetter.toLowerCase()) {
154
massagedKey = `${driveLetterLower}${massagedKey.substring(1)}`;
155
}
156
}
157
158
massagedExpression[massagedKey] = expression[key];
159
}
160
161
return {
162
expression: massagedExpression,
163
hasAbsolutePath
164
};
165
}
166
167
matches(
168
resource: URI,
169
hasSibling?: (name: string) => boolean
170
): boolean {
171
if (this.mapFolderToParsedExpression.size === 0) {
172
return false; // return early: no expression for this matcher
173
}
174
175
const folder = this.contextService.getWorkspaceFolder(resource);
176
let expressionForFolder: ParsedExpression | undefined;
177
let expressionConfigForFolder: IConfiguredExpression | undefined;
178
if (folder && this.mapFolderToParsedExpression.has(folder.uri.toString())) {
179
expressionForFolder = this.mapFolderToParsedExpression.get(folder.uri.toString());
180
expressionConfigForFolder = this.mapFolderToConfiguredExpression.get(folder.uri.toString());
181
} else {
182
expressionForFolder = this.mapFolderToParsedExpression.get(ResourceGlobMatcher.NO_FOLDER);
183
expressionConfigForFolder = this.mapFolderToConfiguredExpression.get(ResourceGlobMatcher.NO_FOLDER);
184
}
185
186
if (!expressionForFolder) {
187
return false; // return early: no expression for this resource
188
}
189
190
// If the resource if from a workspace, convert its absolute path to a relative
191
// path so that glob patterns have a higher probability to match. For example
192
// a glob pattern of "src/**" will not match on an absolute path "/folder/src/file.txt"
193
// but can match on "src/file.txt"
194
195
let resourcePathToMatch: string | undefined;
196
if (folder) {
197
resourcePathToMatch = relativePath(folder.uri, resource);
198
} else {
199
resourcePathToMatch = this.uriToPath(resource);
200
}
201
202
if (typeof resourcePathToMatch === 'string' && !!expressionForFolder(resourcePathToMatch, undefined, hasSibling)) {
203
return true;
204
}
205
206
// If the configured expression has an absolute path, we also check for absolute paths
207
// to match, otherwise we potentially miss out on matches. We only do that if we previously
208
// matched on the relative path.
209
210
if (resourcePathToMatch !== this.uriToPath(resource) && expressionConfigForFolder?.hasAbsolutePath) {
211
return !!expressionForFolder(this.uriToPath(resource), undefined, hasSibling);
212
}
213
214
return false;
215
}
216
217
private uriToPath(uri: URI): string {
218
if (uri.scheme === Schemas.file) {
219
return uri.fsPath;
220
}
221
222
return uri.path;
223
}
224
}
225
226