UNPKG

monaco-editor

Version:
331 lines (328 loc) • 11.6 kB
import { isFalsyOrEmpty, isNonEmptyArray } from '../../../base/common/arrays.js'; import { DebounceEmitter } from '../../../base/common/event.js'; import { Iterable } from '../../../base/common/iterator.js'; import { ResourceMap, ResourceSet } from '../../../base/common/map.js'; import { Schemas } from '../../../base/common/network.js'; import { URI } from '../../../base/common/uri.js'; import { localize } from '../../../nls.js'; import { MarkerSeverity } from './markers.js'; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ const unsupportedSchemas = new Set([ Schemas.inMemory, Schemas.vscodeSourceControl, Schemas.walkThrough, Schemas.walkThroughSnippet, Schemas.vscodeChatCodeBlock, Schemas.vscodeTerminal ]); class DoubleResourceMap { constructor() { this._byResource = new ResourceMap(); this._byOwner = new Map(); } set(resource, owner, value) { let ownerMap = this._byResource.get(resource); if (!ownerMap) { ownerMap = new Map(); this._byResource.set(resource, ownerMap); } ownerMap.set(owner, value); let resourceMap = this._byOwner.get(owner); if (!resourceMap) { resourceMap = new ResourceMap(); this._byOwner.set(owner, resourceMap); } resourceMap.set(resource, value); } get(resource, owner) { const ownerMap = this._byResource.get(resource); return ownerMap?.get(owner); } delete(resource, owner) { let removedA = false; let removedB = false; const ownerMap = this._byResource.get(resource); if (ownerMap) { removedA = ownerMap.delete(owner); } const resourceMap = this._byOwner.get(owner); if (resourceMap) { removedB = resourceMap.delete(resource); } if (removedA !== removedB) { throw new Error('illegal state'); } return removedA && removedB; } values(key) { if (typeof key === 'string') { return this._byOwner.get(key)?.values() ?? Iterable.empty(); } if (URI.isUri(key)) { return this._byResource.get(key)?.values() ?? Iterable.empty(); } return Iterable.map(Iterable.concat(...this._byOwner.values()), map => map[1]); } } class MarkerStats { constructor(service) { this.errors = 0; this.infos = 0; this.warnings = 0; this.unknowns = 0; this._data = new ResourceMap(); this._service = service; this._subscription = service.onMarkerChanged(this._update, this); } dispose() { this._subscription.dispose(); } _update(resources) { for (const resource of resources) { const oldStats = this._data.get(resource); if (oldStats) { this._substract(oldStats); } const newStats = this._resourceStats(resource); this._add(newStats); this._data.set(resource, newStats); } } _resourceStats(resource) { const result = { errors: 0, warnings: 0, infos: 0, unknowns: 0 }; // TODO this is a hack if (unsupportedSchemas.has(resource.scheme)) { return result; } for (const { severity } of this._service.read({ resource })) { if (severity === MarkerSeverity.Error) { result.errors += 1; } else if (severity === MarkerSeverity.Warning) { result.warnings += 1; } else if (severity === MarkerSeverity.Info) { result.infos += 1; } else { result.unknowns += 1; } } return result; } _substract(op) { this.errors -= op.errors; this.warnings -= op.warnings; this.infos -= op.infos; this.unknowns -= op.unknowns; } _add(op) { this.errors += op.errors; this.warnings += op.warnings; this.infos += op.infos; this.unknowns += op.unknowns; } } class MarkerService { constructor() { this._onMarkerChanged = new DebounceEmitter({ delay: 0, merge: MarkerService._merge }); this.onMarkerChanged = this._onMarkerChanged.event; this._data = new DoubleResourceMap(); this._stats = new MarkerStats(this); this._filteredResources = new ResourceMap(); } dispose() { this._stats.dispose(); this._onMarkerChanged.dispose(); } remove(owner, resources) { for (const resource of resources || []) { this.changeOne(owner, resource, []); } } changeOne(owner, resource, markerData) { if (isFalsyOrEmpty(markerData)) { // remove marker for this (owner,resource)-tuple const removed = this._data.delete(resource, owner); if (removed) { this._onMarkerChanged.fire([resource]); } } else { // insert marker for this (owner,resource)-tuple const markers = []; for (const data of markerData) { const marker = MarkerService._toMarker(owner, resource, data); if (marker) { markers.push(marker); } } this._data.set(resource, owner, markers); this._onMarkerChanged.fire([resource]); } } static _toMarker(owner, resource, data) { let { code, severity, message, source, startLineNumber, startColumn, endLineNumber, endColumn, relatedInformation, tags, origin } = data; if (!message) { return undefined; } // santize data startLineNumber = startLineNumber > 0 ? startLineNumber : 1; startColumn = startColumn > 0 ? startColumn : 1; endLineNumber = endLineNumber >= startLineNumber ? endLineNumber : startLineNumber; endColumn = endColumn > 0 ? endColumn : startColumn; return { resource, owner, code, severity, message, source, startLineNumber, startColumn, endLineNumber, endColumn, relatedInformation, tags, origin }; } changeAll(owner, data) { const changes = []; // remove old marker const existing = this._data.values(owner); if (existing) { for (const data of existing) { const first = Iterable.first(data); if (first) { changes.push(first.resource); this._data.delete(first.resource, owner); } } } // add new markers if (isNonEmptyArray(data)) { // group by resource const groups = new ResourceMap(); for (const { resource, marker: markerData } of data) { const marker = MarkerService._toMarker(owner, resource, markerData); if (!marker) { // filter bad markers continue; } const array = groups.get(resource); if (!array) { groups.set(resource, [marker]); changes.push(resource); } else { array.push(marker); } } // insert all for (const [resource, value] of groups) { this._data.set(resource, owner, value); } } if (changes.length > 0) { this._onMarkerChanged.fire(changes); } } /** * Creates an information marker for filtered resources */ _createFilteredMarker(resource, reasons) { const message = reasons.length === 1 ? localize(1738, "Problems are paused because: \"{0}\"", reasons[0]) : localize(1739, "Problems are paused because: \"{0}\" and {1} more", reasons[0], reasons.length - 1); return { owner: 'markersFilter', resource, severity: MarkerSeverity.Info, message, startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1, }; } read(filter = Object.create(null)) { let { owner, resource, severities, take } = filter; if (!take || take < 0) { take = -1; } if (owner && resource) { // exactly one owner AND resource const reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(resource) : undefined; if (reasons?.length) { const infoMarker = this._createFilteredMarker(resource, reasons); return [infoMarker]; } const data = this._data.get(resource, owner); if (!data) { return []; } const result = []; for (const marker of data) { if (take > 0 && result.length === take) { break; } const reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(resource) : undefined; if (reasons?.length) { result.push(this._createFilteredMarker(resource, reasons)); } else if (MarkerService._accept(marker, severities)) { result.push(marker); } } return result; } else { // of one resource OR owner const iterable = !owner && !resource ? this._data.values() : this._data.values(resource ?? owner); const result = []; const filtered = new ResourceSet(); for (const markers of iterable) { for (const data of markers) { if (filtered.has(data.resource)) { continue; } if (take > 0 && result.length === take) { break; } const reasons = !filter.ignoreResourceFilters ? this._filteredResources.get(data.resource) : undefined; if (reasons?.length) { result.push(this._createFilteredMarker(data.resource, reasons)); filtered.add(data.resource); } else if (MarkerService._accept(data, severities)) { result.push(data); } } } return result; } } static _accept(marker, severities) { return severities === undefined || (severities & marker.severity) === marker.severity; } // --- event debounce logic static _merge(all) { const set = new ResourceMap(); for (const array of all) { for (const item of array) { set.set(item, true); } } return Array.from(set.keys()); } } export { MarkerService, unsupportedSchemas };