Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/markers/common/markerService.ts
5243 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 { isFalsyOrEmpty, isNonEmptyArray } from '../../../base/common/arrays.js';
7
import { MicrotaskEmitter } from '../../../base/common/event.js';
8
import { Iterable } from '../../../base/common/iterator.js';
9
import { IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
10
import { ResourceMap, ResourceSet } from '../../../base/common/map.js';
11
import { Schemas } from '../../../base/common/network.js';
12
import { URI } from '../../../base/common/uri.js';
13
import { localize } from '../../../nls.js';
14
import { IMarker, IMarkerData, IMarkerReadOptions, IMarkerService, IResourceMarker, MarkerSeverity, MarkerStatistics } from './markers.js';
15
16
export const unsupportedSchemas = new Set([
17
Schemas.inMemory,
18
Schemas.vscodeSourceControl,
19
Schemas.walkThrough,
20
Schemas.walkThroughSnippet,
21
Schemas.vscodeChatCodeBlock,
22
Schemas.vscodeTerminal
23
]);
24
25
class DoubleResourceMap<V> {
26
27
private _byResource = new ResourceMap<Map<string, V>>();
28
private _byOwner = new Map<string, ResourceMap<V>>();
29
30
set(resource: URI, owner: string, value: V) {
31
let ownerMap = this._byResource.get(resource);
32
if (!ownerMap) {
33
ownerMap = new Map();
34
this._byResource.set(resource, ownerMap);
35
}
36
ownerMap.set(owner, value);
37
38
let resourceMap = this._byOwner.get(owner);
39
if (!resourceMap) {
40
resourceMap = new ResourceMap();
41
this._byOwner.set(owner, resourceMap);
42
}
43
resourceMap.set(resource, value);
44
}
45
46
get(resource: URI, owner: string): V | undefined {
47
const ownerMap = this._byResource.get(resource);
48
return ownerMap?.get(owner);
49
}
50
51
delete(resource: URI, owner: string): boolean {
52
let removedA = false;
53
let removedB = false;
54
const ownerMap = this._byResource.get(resource);
55
if (ownerMap) {
56
removedA = ownerMap.delete(owner);
57
}
58
const resourceMap = this._byOwner.get(owner);
59
if (resourceMap) {
60
removedB = resourceMap.delete(resource);
61
}
62
if (removedA !== removedB) {
63
throw new Error('illegal state');
64
}
65
return removedA && removedB;
66
}
67
68
values(key?: URI | string): Iterable<V> {
69
if (typeof key === 'string') {
70
return this._byOwner.get(key)?.values() ?? Iterable.empty();
71
}
72
if (URI.isUri(key)) {
73
return this._byResource.get(key)?.values() ?? Iterable.empty();
74
}
75
76
return Iterable.map(Iterable.concat(...this._byOwner.values()), map => map[1]);
77
}
78
}
79
80
class MarkerStats implements MarkerStatistics {
81
82
errors: number = 0;
83
infos: number = 0;
84
warnings: number = 0;
85
unknowns: number = 0;
86
87
private readonly _data = new ResourceMap<MarkerStatistics>();
88
private readonly _service: IMarkerService;
89
private readonly _subscription: IDisposable;
90
91
constructor(service: IMarkerService) {
92
this._service = service;
93
this._subscription = service.onMarkerChanged(this._update, this);
94
}
95
96
dispose(): void {
97
this._subscription.dispose();
98
}
99
100
private _update(resources: readonly URI[]): void {
101
for (const resource of resources) {
102
const oldStats = this._data.get(resource);
103
if (oldStats) {
104
this._substract(oldStats);
105
}
106
const newStats = this._resourceStats(resource);
107
this._add(newStats);
108
this._data.set(resource, newStats);
109
}
110
}
111
112
private _resourceStats(resource: URI): MarkerStatistics {
113
const result: MarkerStatistics = { errors: 0, warnings: 0, infos: 0, unknowns: 0 };
114
115
// TODO this is a hack
116
if (unsupportedSchemas.has(resource.scheme)) {
117
return result;
118
}
119
120
for (const { severity } of this._service.read({ resource })) {
121
if (severity === MarkerSeverity.Error) {
122
result.errors += 1;
123
} else if (severity === MarkerSeverity.Warning) {
124
result.warnings += 1;
125
} else if (severity === MarkerSeverity.Info) {
126
result.infos += 1;
127
} else {
128
result.unknowns += 1;
129
}
130
}
131
132
return result;
133
}
134
135
private _substract(op: MarkerStatistics) {
136
this.errors -= op.errors;
137
this.warnings -= op.warnings;
138
this.infos -= op.infos;
139
this.unknowns -= op.unknowns;
140
}
141
142
private _add(op: MarkerStatistics) {
143
this.errors += op.errors;
144
this.warnings += op.warnings;
145
this.infos += op.infos;
146
this.unknowns += op.unknowns;
147
}
148
}
149
150
export class MarkerService implements IMarkerService {
151
152
declare readonly _serviceBrand: undefined;
153
154
private readonly _onMarkerChanged = new MicrotaskEmitter<readonly URI[]>({
155
merge: MarkerService._merge
156
});
157
158
readonly onMarkerChanged = this._onMarkerChanged.event;
159
160
private readonly _data = new DoubleResourceMap<IMarker[]>();
161
private readonly _stats = new MarkerStats(this);
162
private readonly _filteredResources = new ResourceMap<string[]>();
163
164
dispose(): void {
165
this._stats.dispose();
166
this._onMarkerChanged.dispose();
167
}
168
169
getStatistics(): MarkerStatistics {
170
return this._stats;
171
}
172
173
remove(owner: string, resources: URI[]): void {
174
for (const resource of resources || []) {
175
this.changeOne(owner, resource, []);
176
}
177
}
178
179
changeOne(owner: string, resource: URI, markerData: IMarkerData[]): void {
180
181
if (isFalsyOrEmpty(markerData)) {
182
// remove marker for this (owner,resource)-tuple
183
const removed = this._data.delete(resource, owner);
184
if (removed) {
185
this._onMarkerChanged.fire([resource]);
186
}
187
188
} else {
189
// insert marker for this (owner,resource)-tuple
190
const markers: IMarker[] = [];
191
for (const data of markerData) {
192
const marker = MarkerService._toMarker(owner, resource, data);
193
if (marker) {
194
markers.push(marker);
195
}
196
}
197
this._data.set(resource, owner, markers);
198
this._onMarkerChanged.fire([resource]);
199
}
200
}
201
202
installResourceFilter(resource: URI, reason: string): IDisposable {
203
let reasons = this._filteredResources.get(resource);
204
205
if (!reasons) {
206
reasons = [];
207
this._filteredResources.set(resource, reasons);
208
}
209
reasons.push(reason);
210
this._onMarkerChanged.fire([resource]);
211
212
return toDisposable(() => {
213
const reasons = this._filteredResources.get(resource);
214
if (!reasons) {
215
return;
216
}
217
const reasonIndex = reasons.indexOf(reason);
218
if (reasonIndex !== -1) {
219
reasons.splice(reasonIndex, 1);
220
if (reasons.length === 0) {
221
this._filteredResources.delete(resource);
222
}
223
this._onMarkerChanged.fire([resource]);
224
}
225
});
226
}
227
228
private static _toMarker(owner: string, resource: URI, data: IMarkerData): IMarker | undefined {
229
let {
230
code, severity,
231
message, source,
232
startLineNumber, startColumn, endLineNumber, endColumn,
233
relatedInformation,
234
modelVersionId,
235
tags, origin
236
} = data;
237
238
if (!message) {
239
return undefined;
240
}
241
242
// santize data
243
startLineNumber = startLineNumber > 0 ? startLineNumber : 1;
244
startColumn = startColumn > 0 ? startColumn : 1;
245
endLineNumber = endLineNumber >= startLineNumber ? endLineNumber : startLineNumber;
246
endColumn = endColumn > 0 ? endColumn : startColumn;
247
248
return {
249
resource,
250
owner,
251
code,
252
severity,
253
message,
254
source,
255
startLineNumber,
256
startColumn,
257
endLineNumber,
258
endColumn,
259
relatedInformation,
260
modelVersionId,
261
tags,
262
origin
263
};
264
}
265
266
changeAll(owner: string, data: IResourceMarker[]): void {
267
const changes: URI[] = [];
268
269
// remove old marker
270
const existing = this._data.values(owner);
271
if (existing) {
272
for (const data of existing) {
273
const first = Iterable.first(data);
274
if (first) {
275
changes.push(first.resource);
276
this._data.delete(first.resource, owner);
277
}
278
}
279
}
280
281
// add new markers
282
if (isNonEmptyArray(data)) {
283
284
// group by resource
285
const groups = new ResourceMap<IMarker[]>();
286
for (const { resource, marker: markerData } of data) {
287
const marker = MarkerService._toMarker(owner, resource, markerData);
288
if (!marker) {
289
// filter bad markers
290
continue;
291
}
292
const array = groups.get(resource);
293
if (!array) {
294
groups.set(resource, [marker]);
295
changes.push(resource);
296
} else {
297
array.push(marker);
298
}
299
}
300
301
// insert all
302
for (const [resource, value] of groups) {
303
this._data.set(resource, owner, value);
304
}
305
}
306
307
if (changes.length > 0) {
308
this._onMarkerChanged.fire(changes);
309
}
310
}
311
312
/**
313
* Creates an information marker for filtered resources
314
*/
315
private _createFilteredMarker(resource: URI, reasons: string[]): IMarker {
316
const message = reasons.length === 1
317
? localize('filtered', "Problems are paused because: \"{0}\"", reasons[0])
318
: localize('filtered.network', "Problems are paused because: \"{0}\" and {1} more", reasons[0], reasons.length - 1);
319
320
return {
321
owner: 'markersFilter',
322
resource,
323
severity: MarkerSeverity.Info,
324
message,
325
startLineNumber: 1,
326
startColumn: 1,
327
endLineNumber: 1,
328
endColumn: 1,
329
};
330
}
331
332
read(filter: IMarkerReadOptions = Object.create(null)): IMarker[] {
333
334
let { owner, resource, severities, take } = filter;
335
336
if (!take || take < 0) {
337
take = -1;
338
}
339
340
if (owner && resource) {
341
// exactly one owner AND resource
342
const reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(resource) : undefined;
343
if (reasons?.length) {
344
const infoMarker = this._createFilteredMarker(resource, reasons);
345
return [infoMarker];
346
}
347
348
const data = this._data.get(resource, owner);
349
if (!data) {
350
return [];
351
}
352
353
const result: IMarker[] = [];
354
for (const marker of data) {
355
if (take > 0 && result.length === take) {
356
break;
357
}
358
const reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(resource) : undefined;
359
if (reasons?.length) {
360
result.push(this._createFilteredMarker(resource, reasons));
361
362
} else if (MarkerService._accept(marker, severities)) {
363
result.push(marker);
364
}
365
}
366
return result;
367
368
} else {
369
// of one resource OR owner
370
const iterable = !owner && !resource
371
? this._data.values()
372
: this._data.values(resource ?? owner!);
373
374
const result: IMarker[] = [];
375
const filtered = new ResourceSet();
376
377
for (const markers of iterable) {
378
for (const data of markers) {
379
if (filtered.has(data.resource)) {
380
continue;
381
}
382
if (take > 0 && result.length === take) {
383
break;
384
}
385
const reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(data.resource) : undefined;
386
if (reasons?.length) {
387
result.push(this._createFilteredMarker(data.resource, reasons));
388
filtered.add(data.resource);
389
390
} else if (MarkerService._accept(data, severities)) {
391
result.push(data);
392
}
393
}
394
}
395
return result;
396
}
397
}
398
399
private static _accept(marker: IMarker, severities?: number): boolean {
400
return severities === undefined || (severities & marker.severity) === marker.severity;
401
}
402
403
// --- event debounce logic
404
405
private static _merge(all: (readonly URI[])[]): URI[] {
406
const set = new ResourceMap<boolean>();
407
for (const array of all) {
408
for (const item of array) {
409
set.set(item, true);
410
}
411
}
412
return Array.from(set.keys());
413
}
414
}
415
416