提交 19829be6 编写于 作者: J Johannes Rieken

use two indecies for markers

上级 9f3efd44
...@@ -4,312 +4,251 @@ ...@@ -4,312 +4,251 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import arrays = require('vs/base/common/arrays'); import * as arrays from 'vs/base/common/arrays';
import network = require('vs/base/common/network'); import * as network from 'vs/base/common/network';
import collections = require('vs/base/common/collections'); // import * as collections from 'vs/base/common/collections';
import {isEmptyObject} from 'vs/base/common/types';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import Event, {Emitter, debounceEvent} from 'vs/base/common/event'; import Event, {Emitter, debounceEvent} from 'vs/base/common/event';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import {IMarkerService, IMarkerData, IResourceMarker, IMarker, MarkerStatistics} from './markers'; import {IMarkerService, IMarkerData, IResourceMarker, IMarker, MarkerStatistics} from './markers';
interface Key { interface MapMap<V> {
owner: string; [key: string]: { [key: string]: V };
resource: URI;
} }
namespace Key { namespace MapMap {
export function fromValue(value: string): Key { export function get<V>(map: MapMap<V>, key1: string, key2: string): V {
const idx = value.indexOf(''); if (map[key1]) {
const owner = value.substring(0, idx); return map[key1][key2];
const resource = URI.parse(value.substring(idx + 1)); }
return { owner, resource };
} }
export function selector(owner?: string, resource?: URI): (input: string)=> boolean { export function set<V>(map: MapMap<V>, key1: string, key2: string, value: V): void {
if (!map[key1]) {
if (!owner && !resource) { map[key1] = Object.create(null);
// anything
return input => true;
} else if (!owner) {
// ends with
const suffix = '' + resource.toString();
return input => input.lastIndexOf(suffix) === input.length - suffix.length;
} else if (!resource) {
// starts with
const prefix = owner + `→`;
return input => input.indexOf(prefix) === 0;
} else {
// exact match
const match = owner + '' + resource.toString();
return input => input === match;
} }
map[key1][key2] = value;
} }
export function raw(owner: string, resource: URI): string { export function remove(map: MapMap<any>, key1: string, key2: string): boolean {
return owner + '' + resource; if (map[key1]) {
const result = delete map[key1][key2];
if (isEmptyObject(map[key1])) {
delete map[key1];
}
return result;
}
} }
} }
export interface MarkerData {
[k: string]: IMarkerData[];
}
export class MarkerService implements IMarkerService { export class MarkerService implements IMarkerService {
public _serviceBrand: any; _serviceBrand: any;
private _data: { [k: string]: IMarkerData[] };
private _stats: MarkerStatistics;
private _onMarkerChanged = new Emitter<URI[]>(); private _onMarkerChanged = new Emitter<URI[]>();
private _onMarkerChangedEvent: Event<URI[]> = debounceEvent(this._onMarkerChanged.event, MarkerService._debouncer, 0); private _onMarkerChangedEvent: Event<URI[]> = debounceEvent(this._onMarkerChanged.event, MarkerService._debouncer, 0);
private _byResource: MapMap<IMarker[]> = Object.create(null);
private _byOwner: MapMap<IMarker[]> = Object.create(null);
private _stats: MarkerStatistics;
constructor() { constructor() {
this._data = Object.create(null); this._onMarkerChangedEvent(() => this._stats = undefined);
this._stats = this._emptyStats();
} }
public getStatistics(): MarkerStatistics { get onMarkerChanged(): Event<URI[]> {
return this._stats;
}
// ---- IMarkerService ------------------------------------------
public get onMarkerChanged(): Event<URI[]> {
return this._onMarkerChangedEvent; return this._onMarkerChangedEvent;
} }
public changeOne(owner: string, resource: URI, markers: IMarkerData[]): void { getStatistics(): MarkerStatistics {
if (this._doChangeOne(owner, resource, markers)) { if (!this._stats) {
this._onMarkerChanged.fire([resource]); this._stats = { errors: 0, infos: 0, warnings: 0, unknowns: 0 };
} for (const {severity, resource} of this.read()) {
} // TODO this is a hack
if (resource.scheme === network.Schemas.inMemory) {
public remove(owner: string, resources: URI[]): void { continue;
if (arrays.isFalsyOrEmpty(resources)) { }
return; if (severity === Severity.Error) {
} this._stats.errors += 1;
let changedResources: URI[]; } else if (severity === Severity.Warning) {
for (let resource of resources) { this._stats.warnings += 1;
if (this._doChangeOne(owner, resource, undefined)) { } else if (severity === Severity.Info) {
if (!changedResources) { this._stats.infos += 1;
changedResources = []; } else {
this._stats.unknowns += 1;
} }
changedResources.push(resource);
} }
} }
if (changedResources) { return this._stats;
this._onMarkerChanged.fire(changedResources);
}
} }
private _doChangeOne(owner: string, resource: URI, markers: IMarkerData[]): boolean { remove(owner: string, resources: URI[]): void {
if (!arrays.isFalsyOrEmpty(resources)) {
let key = Key.raw(owner, resource), for (const resource of resources) {
oldMarkers = this._data[key], this.changeOne(owner, resource, undefined);
hasOldMarkers = !arrays.isFalsyOrEmpty(oldMarkers), }
getsNewMarkers = !arrays.isFalsyOrEmpty(markers),
oldStats = this._computeStats(oldMarkers),
newStats = this._computeStats(markers);
if (!hasOldMarkers && !getsNewMarkers) {
return;
}
if (getsNewMarkers) {
this._data[key] = markers;
} else if (hasOldMarkers) {
delete this._data[key];
}
if (this._isStatRelevant(resource)) {
this._updateStatsMinus(oldStats);
this._updateStatsPlus(newStats);
} }
return true;
} }
public changeAll(owner: string, data: IResourceMarker[]): void { changeOne(owner: string, resource: URI, markerData: IMarkerData[]): void {
let changedResources: { [n: string]: URI } = Object.create(null);
// remove and record old markers if (arrays.isFalsyOrEmpty(markerData)) {
let oldStats = this._emptyStats(); // remove marker for this (owner,resource)-tuple
this._forEach(owner, undefined, -1, (e, r) => { const a = MapMap.remove(this._byResource, resource.toString(), owner);
let resource = Key.fromValue(e.key).resource; const b = MapMap.remove(this._byOwner, owner, resource.toString());
if (this._isStatRelevant(resource)) { if (a && b) {
this._updateStatsPlus(oldStats, this._computeStats(e.value)); this._onMarkerChanged.fire([resource]);
} }
changedResources[resource.toString()] = resource;
r();
});
this._updateStatsMinus(oldStats);
// add and record new markers } else {
if (!arrays.isFalsyOrEmpty(data)) { // insert marker for this (owner,resource)-tuple
let newStats = this._emptyStats(); const markers: IMarker[] = [];
data.forEach(d => { for (const data of markerData) {
changedResources[d.resource.toString()] = d.resource; const marker = MarkerService._toMarker(owner, resource, data);
collections.lookupOrInsert(this._data, Key.raw(owner, d.resource), []).push(d.marker); if (marker) {
if (this._isStatRelevant(d.resource)) { markers.push(marker);
this._updateStatsMarker(newStats, d.marker);
} }
}); }
this._updateStatsPlus(newStats); MapMap.set(this._byResource, resource.toString(), owner, markers);
MapMap.set(this._byOwner, owner, resource.toString(), markers);
this._onMarkerChanged.fire([resource]);
} }
this._onMarkerChanged.fire(collections.values(changedResources));
} }
public read(filter: { owner?: string; resource?: URI; take?: number; } = Object.create(null)): IMarker[] { private static _toMarker(owner: string, resource: URI, data: IMarkerData): IMarker {
let ret: IMarker[] = []; let {code, severity, message, source, startLineNumber, startColumn, endLineNumber, endColumn} = data;
this._forEach(filter.owner, filter.resource, filter.take, entry => this._fromEntry(entry, ret));
return ret; if (!message) {
} return;
}
// santize data
code = code || null;
startLineNumber = startLineNumber > 0 ? startLineNumber : 1;
startColumn = startColumn > 0 ? startColumn : 1;
endLineNumber = endLineNumber >= startLineNumber ? endLineNumber : startLineNumber;
endColumn = endColumn > 0 ? endColumn : startColumn;
private _isStatRelevant(resource: URI): boolean { return {
//TODO@Dirk this is a hack resource,
return resource.scheme !== network.Schemas.inMemory; owner,
code,
severity,
message,
source,
startLineNumber,
startColumn,
endLineNumber,
endColumn
};
} }
private _forEach(owner: string, resource: URI, take: number, callback: (entry: { key: string; value: IMarkerData[]; }, remove: Function) => any): void { changeAll(owner: string, data: IResourceMarker[]): void {
//TODO@Joh: be smart and use an index const changes: URI[] = [];
const selector = Key.selector(owner, resource); const map = this._byOwner[owner];
// remove old marker
if (map) {
delete this._byOwner[owner];
for (const resource in map) {
// remeber what we remove
const [first] = this._byResource[resource][owner];
if (first) {
changes.push(first.resource);
}
// actual remove
delete this._byResource[resource];
}
}
// add new markers
if (!arrays.isFalsyOrEmpty(data)) {
let took = 0; // group by resource
for (let key in this._data) { const groups: { [resource: string]: IMarker[] } = Object.create(null);
if (selector(key)) { for (const {resource, marker: markerData} of data) {
callback({ key, value: this._data[key] }, () => delete this._data[key]); const marker = MarkerService._toMarker(owner, resource, markerData);
if (take > 0 && took++ >= take) { if (!marker) {
break; // filter bad markers
continue;
} }
const array = groups[resource.toString()];
if (!array) {
groups[resource.toString()] = [marker];
changes.push(resource);
} else {
array.push(marker);
}
}
// insert all
for (const resource in groups) {
MapMap.set(this._byResource, resource, owner, groups[resource]);
MapMap.set(this._byOwner, owner, resource, groups[resource]);
} }
} }
if (changes.length > 0) {
this._onMarkerChanged.fire(changes);
}
} }
private _fromEntry(entry: { key: string; value: IMarkerData[]; }, bucket: IMarker[]): void { read(filter: { owner?: string; resource?: URI; take?: number; } = Object.create(null)): IMarker[] {
let key = Key.fromValue(entry.key); let {owner, resource, take} = filter;
entry.value.forEach(data => { if (!take || take < 0) {
take = -1;
}
// before reading, we sanitize the data if (owner && resource) {
// skip entry if not sanitizable // exactly one owner AND resource
const ok = MarkerService._sanitize(data); const result = MapMap.get(this._byResource, resource.toString(), owner);
if (!ok) { if (!result) {
return; return [];
} else {
return result.slice(0, take > 0 ? take : undefined);
} }
bucket.push({ } else if (!owner && !resource) {
owner: key.owner, // all
resource: key.resource, const result: IMarker[] = [];
code: data.code, for (const key1 in this._byResource) {
message: data.message, for (const key2 in this._byResource[key1]) {
source: data.source, for (const data of this._byResource[key1][key2]) {
severity: data.severity, const newLen = result.push(data);
startLineNumber: data.startLineNumber,
startColumn: data.startColumn, if (take > 0 && newLen === take) {
endLineNumber: data.endLineNumber, return result;
endColumn: data.endColumn }
});
});
}
private _computeStats(markers: IMarkerData[]): MarkerStatistics {
let errors = 0, warnings = 0, infos = 0, unknwons = 0;
if (markers) {
for (let i = 0; i < markers.length; i++) {
let marker = markers[i];
if (marker.severity) {
switch (marker.severity) {
case Severity.Error:
errors++;
break;
case Severity.Warning:
warnings++;
break;
case Severity.Info:
infos++;
break;
default:
unknwons++;
break;
} }
} else {
unknwons++;
} }
} }
} return result;
return {
errors: errors,
warnings: warnings,
infos: infos,
unknowns: unknwons
};
}
private _emptyStats(): MarkerStatistics {
return { errors: 0, warnings: 0, infos: 0, unknowns: 0 };
}
private _updateStatsPlus(toAdd: MarkerStatistics): void; } else {
private _updateStatsPlus(toUpdate: MarkerStatistics, toAdd: MarkerStatistics): void; // of one resource OR owner
private _updateStatsPlus(toUpdate: MarkerStatistics, toAdd?: MarkerStatistics): void { const map: { [key: string]: IMarker[] } = owner
if (!toAdd) { ? this._byOwner[owner]
toAdd = toUpdate; : this._byResource[resource.toString()];
toUpdate = this._stats;
}
toUpdate.errors += toAdd.errors;
toUpdate.warnings += toAdd.warnings;
toUpdate.infos += toAdd.infos;
toUpdate.unknowns += toAdd.unknowns;
}
private _updateStatsMinus(toSubtract: MarkerStatistics): void; if (!map) {
private _updateStatsMinus(toUpdate: MarkerStatistics, toSubtract: MarkerStatistics): void; return [];
private _updateStatsMinus(toUpdate: MarkerStatistics, toSubtract?: MarkerStatistics): void { }
if (!toSubtract) {
toSubtract = toUpdate;
toUpdate = this._stats;
}
toUpdate.errors -= toSubtract.errors;
toUpdate.warnings -= toSubtract.warnings;
toUpdate.infos -= toSubtract.infos;
toUpdate.unknowns -= toSubtract.unknowns;
}
private _updateStatsMarker(toUpdate: MarkerStatistics, marker: IMarkerData): void { const result: IMarker[] = [];
switch (marker.severity) { for (const key in map) {
case Severity.Error: for (const data of map[key]) {
toUpdate.errors++; const newLen = result.push(data);
break;
case Severity.Warning:
toUpdate.warnings++;
break;
case Severity.Info:
toUpdate.infos++;
break;
default:
toUpdate.unknowns++;
break;
}
}
private static _sanitize(data: IMarkerData): boolean { if (take > 0 && newLen === take) {
if (!data.message) { return result;
return false; }
}
}
return result;
} }
data.code = data.code || null;
data.startLineNumber = data.startLineNumber > 0 ? data.startLineNumber : 1;
data.startColumn = data.startColumn > 0 ? data.startColumn : 1;
data.endLineNumber = data.endLineNumber >= data.startLineNumber ? data.endLineNumber : data.startLineNumber;
data.endColumn = data.endColumn > 0 ? data.endColumn : data.startColumn;
return true;
} }
// --- event debounce logic // --- event debounce logic
......
...@@ -22,7 +22,7 @@ export interface IMarkerService { ...@@ -22,7 +22,7 @@ export interface IMarkerService {
remove(owner: string, resources: URI[]): void; remove(owner: string, resources: URI[]): void;
read(filter?: { owner?: string; resource?: URI; selector?: RegExp, take?: number; }): IMarker[]; read(filter?: { owner?: string; resource?: URI; take?: number; }): IMarker[];
onMarkerChanged: Event<URI[]>; onMarkerChanged: Event<URI[]>;
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册