monaco-editor
Version:
A browser based code editor
298 lines (297 loc) • 10.6 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isFalsyOrEmpty, isNonEmptyArray } from '../../../base/common/arrays.js';
import { DebounceEmitter } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { ResourceMap } from '../../../base/common/map.js';
import { Schemas } from '../../../base/common/network.js';
import { URI } from '../../../base/common/uri.js';
import { MarkerSeverity } from './markers.js';
export const unsupportedSchemas = new Set([Schemas.inMemory, Schemas.vscodeSourceControl, Schemas.walkThrough, Schemas.walkThroughSnippet]);
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 === null || ownerMap === void 0 ? void 0 : 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) {
var _a, _b, _c, _d;
if (typeof key === 'string') {
return (_b = (_a = this._byOwner.get(key)) === null || _a === void 0 ? void 0 : _a.values()) !== null && _b !== void 0 ? _b : Iterable.empty();
}
if (URI.isUri(key)) {
return (_d = (_c = this._byResource.get(key)) === null || _c === void 0 ? void 0 : _c.values()) !== null && _d !== void 0 ? _d : 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;
}
}
export 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);
}
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, } = 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,
};
}
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);
}
}
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 data = this._data.get(resource, owner);
if (!data) {
return [];
}
else {
const result = [];
for (const marker of data) {
if (MarkerService._accept(marker, severities)) {
const newLen = result.push(marker);
if (take > 0 && newLen === take) {
break;
}
}
}
return result;
}
}
else if (!owner && !resource) {
// all
const result = [];
for (const markers of this._data.values()) {
for (const data of markers) {
if (MarkerService._accept(data, severities)) {
const newLen = result.push(data);
if (take > 0 && newLen === take) {
return result;
}
}
}
}
return result;
}
else {
// of one resource OR owner
const iterable = this._data.values(resource !== null && resource !== void 0 ? resource : owner);
const result = [];
for (const markers of iterable) {
for (const data of markers) {
if (MarkerService._accept(data, severities)) {
const newLen = result.push(data);
if (take > 0 && newLen === take) {
return result;
}
}
}
}
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());
}
}