// ***************************************************************************** // Copyright (C) 2017 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { injectable, inject, postConstruct } from '@theia/core/shared/inversify'; import { Event, Emitter } from '@theia/core/lib/common'; import URI from '@theia/core/lib/common/uri'; import { Marker } from '../common/marker'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { FileChangesEvent, FileChangeType } from '@theia/filesystem/lib/common/files'; /* * argument to the `findMarkers` method. */ export interface SearchFilter { uri?: URI, owner?: string, dataFilter?: (data: D) => boolean } export class MarkerCollection { protected readonly owner2Markers = new Map>[]>(); constructor( public readonly uri: URI, public readonly kind: string ) { } get empty(): boolean { return !this.owner2Markers.size; } getOwners(): string[] { return Array.from(this.owner2Markers.keys()); } getMarkers(owner: string): Readonly>[] { return this.owner2Markers.get(owner) || []; } setMarkers(owner: string, markerData: T[]): Marker[] { const before = this.owner2Markers.get(owner); if (markerData.length > 0) { this.owner2Markers.set(owner, markerData.map(data => this.createMarker(owner, data))); } else { this.owner2Markers.delete(owner); } return before || []; } protected createMarker(owner: string, data: T): Readonly> { return Object.freeze({ uri: this.uri.toString(), kind: this.kind, owner: owner, data }); } findMarkers(filter: SearchFilter): Marker[] { if (filter.owner) { if (this.owner2Markers.has(filter.owner)) { return this.filterMarkers(filter, this.owner2Markers.get(filter.owner)); } return []; } else { const result: Marker[] = []; for (const markers of this.owner2Markers.values()) { result.push(...this.filterMarkers(filter, markers)); } return result; } } protected filterMarkers(filter: SearchFilter, toFilter?: Marker[]): Marker[] { if (!toFilter) { return []; } if (filter.dataFilter) { return toFilter.filter(d => filter.dataFilter!(d.data)); } else { return toFilter; } } } export interface Uri2MarkerEntry { uri: string markers: Owner2MarkerEntry[] } export interface Owner2MarkerEntry { owner: string markerData: object[]; } @injectable() export abstract class MarkerManager { public abstract getKind(): string; protected readonly uri2MarkerCollection = new Map>(); protected readonly onDidChangeMarkersEmitter = new Emitter(); @inject(FileService) protected readonly fileService: FileService; @postConstruct() protected init(): void { this.fileService.onDidFilesChange(event => { if (event.gotDeleted()) { this.cleanMarkers(event); } }); } protected cleanMarkers(event: FileChangesEvent): void { for (const uriString of this.uri2MarkerCollection.keys()) { const uri = new URI(uriString); if (event.contains(uri, FileChangeType.DELETED)) { this.cleanAllMarkers(uri); } } } get onDidChangeMarkers(): Event { return this.onDidChangeMarkersEmitter.event; } protected fireOnDidChangeMarkers(uri: URI): void { this.onDidChangeMarkersEmitter.fire(uri); } /* * replaces the current markers for the given uri and owner with the given data. */ setMarkers(uri: URI, owner: string, data: D[]): Marker[] { const uriString = uri.toString(); const collection = this.uri2MarkerCollection.get(uriString) || new MarkerCollection(uri, this.getKind()); const oldMarkers = collection.setMarkers(owner, data); if (collection.empty) { this.uri2MarkerCollection.delete(uri.toString()); } else { this.uri2MarkerCollection.set(uriString, collection); } this.fireOnDidChangeMarkers(uri); return oldMarkers; } /* * returns all markers that satisfy the given filter. */ findMarkers(filter: SearchFilter = {}): Marker[] { if (filter.uri) { const collection = this.uri2MarkerCollection.get(filter.uri.toString()); return collection ? collection.findMarkers(filter) : []; } const result: Marker[] = []; for (const uri of this.getUris()) { result.push(...this.uri2MarkerCollection.get(uri)!.findMarkers(filter)); } return result; } getMarkersByUri(): IterableIterator<[string, MarkerCollection]> { return this.uri2MarkerCollection.entries(); } getUris(): IterableIterator { return this.uri2MarkerCollection.keys(); } cleanAllMarkers(uri?: URI): void { if (uri) { this.doCleanAllMarkers(uri); } else { for (const uriString of this.getUris()) { this.doCleanAllMarkers(new URI(uriString)); } } } protected doCleanAllMarkers(uri: URI): void { const uriString = uri.toString(); const collection = this.uri2MarkerCollection.get(uriString); if (collection !== undefined) { this.uri2MarkerCollection.delete(uriString); this.fireOnDidChangeMarkers(uri); } } }