From 573574c31eba5962fdcd5f5870ce1a0631784883 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 4 Oct 2018 16:08:44 +0100 Subject: [PATCH 01/35] markers: move fixes gathering outside the model --- .../markers/electron-browser/markersModel.ts | 52 ++----------------- .../electron-browser/markersPanelActions.ts | 47 +++++++++++++++-- 2 files changed, 47 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts index 0ff5bbf375997..7647cdf2608dc 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts @@ -6,7 +6,7 @@ import * as paths from 'vs/base/common/paths'; import { URI } from 'vs/base/common/uri'; import { Range, IRange } from 'vs/editor/common/core/range'; -import { IMarker, MarkerSeverity, IRelatedInformation, IMarkerData } from 'vs/platform/markers/common/markers'; +import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers'; import { IFilter, IMatch, or, matchesContiguousSubString, matchesPrefix, matchesFuzzy } from 'vs/base/common/filters'; import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; import { Schemas } from 'vs/base/common/network'; @@ -14,10 +14,6 @@ import { groupBy, isFalsyOrEmpty, flatten } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/map'; import * as glob from 'vs/base/common/glob'; import * as strings from 'vs/base/common/strings'; -import { CodeAction } from 'vs/editor/common/modes'; -import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; -import { IModelService } from 'vs/editor/common/services/modelService'; function compareUris(a: URI, b: URI) { if (a.toString() < b.toString()) { @@ -37,7 +33,6 @@ export class ResourceMarkers extends NodeWithId { private _name: string = null; private _path: string = null; - private _allFixesPromise: Promise; markers: Marker[] = []; isExcluded: boolean = false; @@ -45,10 +40,7 @@ export class ResourceMarkers extends NodeWithId { filteredCount: number; uriMatches: IMatch[] = []; - constructor( - readonly uri: URI, - private modelService: IModelService - ) { + constructor(readonly uri: URI) { super(uri.toString()); } @@ -66,39 +58,6 @@ export class ResourceMarkers extends NodeWithId { return this._name; } - public getFixes(marker: Marker): Promise { - return this._getFixes(new Range(marker.range.startLineNumber, marker.range.startColumn, marker.range.endLineNumber, marker.range.endColumn)); - } - - public async hasFixes(marker: Marker): Promise { - if (!this.modelService.getModel(this.uri)) { - // Return early, If the model is not yet created - return false; - } - if (!this._allFixesPromise) { - this._allFixesPromise = this._getFixes(); - } - const allFixes = await this._allFixesPromise; - if (allFixes.length) { - const markerKey = IMarkerData.makeKey(marker.raw); - for (const fix of allFixes) { - if (fix.diagnostics && fix.diagnostics.some(d => IMarkerData.makeKey(d) === markerKey)) { - return true; - } - } - } - return false; - } - - private async _getFixes(range?: Range): Promise { - const model = this.modelService.getModel(this.uri); - if (model) { - const codeActions = await getCodeActions(model, range ? range : model.getFullModelRange(), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }); - return codeActions; - } - return []; - } - static compare(a: ResourceMarkers, b: ResourceMarkers): number { let [firstMarkerOfA] = a.markers; let [firstMarkerOfB] = b.markers; @@ -225,10 +184,7 @@ export class MarkersModel { private _markersByResource: Map; private _filterOptions: FilterOptions; - constructor( - markers: IMarker[] = [], - @IModelService private modelService: IModelService - ) { + constructor(markers: IMarker[] = []) { this._markersByResource = new Map(); this._filterOptions = new FilterOptions(); @@ -313,7 +269,7 @@ export class MarkersModel { private createResource(uri: URI, rawMarkers: IMarker[]): ResourceMarkers { const markers: Marker[] = []; - const resource = new ResourceMarkers(uri, this.modelService); + const resource = new ResourceMarkers(uri); this.updateResource(resource); rawMarkers.forEach((rawMarker, index) => { diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts index fdfccb5eed827..0b33828f95d2e 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts @@ -37,6 +37,12 @@ import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/commo import { IModelService } from 'vs/editor/common/services/modelService'; import { isEqual } from 'vs/base/common/resources'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { CodeAction } from 'vs/editor/common/modes'; +import { Range } from 'vs/editor/common/core/range'; +import { IMarkerData } from 'vs/platform/markers/common/markers'; +import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; +import { URI } from 'vs/base/common/uri'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; export class ToggleMarkersPanelAction extends TogglePanelAction { @@ -301,6 +307,7 @@ export class QuickFixAction extends Action { public static readonly ID: string = 'workbench.actions.problems.quickfix'; private updated: boolean = false; + private _allFixesPromise: Promise; private disposables: IDisposable[] = []; constructor( @@ -308,7 +315,7 @@ export class QuickFixAction extends Action { @IBulkEditService private bulkEditService: IBulkEditService, @ICommandService private commandService: ICommandService, @IEditorService private editorService: IEditorService, - @IModelService modelService: IModelService + @IModelService private modelService: IModelService ) { super(QuickFixAction.ID, Messages.MARKERS_PANEL_ACTION_TOOLTIP_QUICKFIX, 'markers-panel-action-quickfix', false); if (modelService.getModel(this.marker.resourceMarkers.uri)) { @@ -320,18 +327,17 @@ export class QuickFixAction extends Action { } }, this, this.disposables); } - } private update(): void { if (!this.updated) { - this.marker.resourceMarkers.hasFixes(this.marker).then(hasFixes => this.enabled = hasFixes); + this.hasFixes(this.marker).then(hasFixes => this.enabled = hasFixes); this.updated = true; } } async getQuickFixActions(): Promise { - const codeActions = await this.marker.resourceMarkers.getFixes(this.marker); + const codeActions = await this.getFixes(this.marker); return codeActions.map(codeAction => new Action( codeAction.command ? codeAction.command.id : codeAction.title, codeAction.title, @@ -356,6 +362,39 @@ export class QuickFixAction extends Action { }, ACTIVE_GROUP).then(() => null); } + private getFixes(marker: Marker): Promise { + return this._getFixes(marker.resource, new Range(marker.range.startLineNumber, marker.range.startColumn, marker.range.endLineNumber, marker.range.endColumn)); + } + + private async hasFixes(marker: Marker): Promise { + if (!this.modelService.getModel(marker.resource)) { + // Return early, If the model is not yet created + return false; + } + if (!this._allFixesPromise) { + this._allFixesPromise = this._getFixes(marker.resource); + } + const allFixes = await this._allFixesPromise; + if (allFixes.length) { + const markerKey = IMarkerData.makeKey(marker.raw); + for (const fix of allFixes) { + if (fix.diagnostics && fix.diagnostics.some(d => IMarkerData.makeKey(d) === markerKey)) { + return true; + } + } + } + return false; + } + + private async _getFixes(uri: URI, range?: Range): Promise { + const model = this.modelService.getModel(uri); + if (model) { + const codeActions = await getCodeActions(model, range ? range : model.getFullModelRange(), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }); + return codeActions; + } + return []; + } + dispose(): void { dispose(this.disposables); super.dispose(); From 18c2ed882408f3fa4ca8a3b70d412478204d23ef Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 4 Oct 2018 16:30:01 +0100 Subject: [PATCH 02/35] markers: wip remove filtering from model --- .../parts/markers/electron-browser/markers.ts | 15 +- .../markers/electron-browser/markersModel.ts | 300 ++++++------------ .../markers/electron-browser/markersPanel.ts | 171 +++++----- .../electron-browser/markersPanelActions.ts | 7 +- .../electron-browser/markersTreeViewer.ts | 36 +-- .../electron-browser/markersModel.test.ts | 5 +- 6 files changed, 203 insertions(+), 331 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markers.ts b/src/vs/workbench/parts/markers/electron-browser/markers.ts index 12ea873cd7ca4..aa934a8f2d9b9 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markers.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markers.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersModel, FilterOptions } from './markersModel'; +import { MarkersModel } from './markersModel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IMarkerService, MarkerSeverity, IMarker } from 'vs/platform/markers/common/markers'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; @@ -55,11 +55,12 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb super(); this.markersModel = this._register(instantiationService.createInstance(MarkersModel, this.readMarkers())); this._register(markerService.onMarkerChanged(resources => this.onMarkerChanged(resources))); - this._register(configurationService.onDidChangeConfiguration(e => { - if (this.useFilesExclude && e.affectsConfiguration('files.exclude')) { - this.doFilter(this.markersModel.filterOptions.filter, this.getExcludeExpression()); - } - })); + // TODO@joao + // this._register(configurationService.onDidChangeConfiguration(e => { + // if (this.useFilesExclude && e.affectsConfiguration('files.exclude')) { + // this.doFilter(this.markersModel.filterOptions.filter, this.getExcludeExpression()); + // } + // })); } filter(filter: IFilter): void { @@ -98,7 +99,7 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb } private doFilter(filterText: string, filesExclude: IExpression): void { - this.markersModel.updateFilterOptions(new FilterOptions(filterText, filesExclude)); + console.warn('marker filter not implemented'); this.refreshBadge(); this._onDidChange.fire([]); } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts index 7647cdf2608dc..77d515a998d34 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts @@ -7,13 +7,8 @@ import * as paths from 'vs/base/common/paths'; import { URI } from 'vs/base/common/uri'; import { Range, IRange } from 'vs/editor/common/core/range'; import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers'; -import { IFilter, IMatch, or, matchesContiguousSubString, matchesPrefix, matchesFuzzy } from 'vs/base/common/filters'; -import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; -import { Schemas } from 'vs/base/common/network'; import { groupBy, isFalsyOrEmpty, flatten } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/map'; -import * as glob from 'vs/base/common/glob'; -import * as strings from 'vs/base/common/strings'; function compareUris(a: URI, b: URI) { if (a.toString() < b.toString()) { @@ -35,23 +30,19 @@ export class ResourceMarkers extends NodeWithId { private _path: string = null; markers: Marker[] = []; - isExcluded: boolean = false; - isIncluded: boolean = false; - filteredCount: number; - uriMatches: IMatch[] = []; constructor(readonly uri: URI) { super(uri.toString()); } - public get path(): string { + get path(): string { if (this._path === null) { this._path = this.uri.fsPath; } return this._path; } - public get name(): string { + get name(): string { if (this._name === null) { this._name = paths.basename(this.uri.fsPath); } @@ -74,10 +65,6 @@ export class ResourceMarkers extends NodeWithId { export class Marker extends NodeWithId { - isSelected: boolean = false; - messageMatches: IMatch[] = []; - sourceMatches: IMatch[] = []; - codeMatches: IMatch[] = []; resourceRelatedInformation: RelatedInformation[] = []; constructor( @@ -88,15 +75,15 @@ export class Marker extends NodeWithId { super(id); } - public get resource(): URI { + get resource(): URI { return this.raw.resource; } - public get range(): IRange { + get range(): IRange { return this.raw; } - public toString(): string { + toString(): string { return JSON.stringify({ ...this.raw, resource: this.raw.resource.path, @@ -112,81 +99,77 @@ export class Marker extends NodeWithId { export class RelatedInformation extends NodeWithId { - messageMatches: IMatch[]; - uriMatches: IMatch[]; - constructor(id: string, readonly raw: IRelatedInformation) { super(id); } } -export class FilterOptions { - - static readonly _filter: IFilter = or(matchesPrefix, matchesContiguousSubString); - static readonly _fuzzyFilter: IFilter = or(matchesPrefix, matchesContiguousSubString, matchesFuzzy); - - readonly filterErrors: boolean = false; - readonly filterWarnings: boolean = false; - readonly filterInfos: boolean = false; - readonly excludePattern: glob.ParsedExpression = null; - readonly includePattern: glob.ParsedExpression = null; - readonly textFilter: string = ''; - - constructor(readonly filter: string = '', excludePatterns: glob.IExpression = {}) { - filter = filter.trim(); - for (const key of Object.keys(excludePatterns)) { - if (excludePatterns[key]) { - this.setPattern(excludePatterns, key); - } - delete excludePatterns[key]; - } - const includePatterns: glob.IExpression = glob.getEmptyExpression(); - if (filter) { - const filters = glob.splitGlobAware(filter, ',').map(s => s.trim()).filter(s => !!s.length); - for (const f of filters) { - this.filterErrors = this.filterErrors || this.matches(f, Messages.MARKERS_PANEL_FILTER_ERRORS); - this.filterWarnings = this.filterWarnings || this.matches(f, Messages.MARKERS_PANEL_FILTER_WARNINGS); - this.filterInfos = this.filterInfos || this.matches(f, Messages.MARKERS_PANEL_FILTER_INFOS); - if (strings.startsWith(f, '!')) { - this.setPattern(excludePatterns, strings.ltrim(f, '!')); - } else { - this.setPattern(includePatterns, f); - this.textFilter += ` ${f}`; - } - } - } - if (Object.keys(excludePatterns).length) { - this.excludePattern = glob.parse(excludePatterns); - } - if (Object.keys(includePatterns).length) { - this.includePattern = glob.parse(includePatterns); - } - this.textFilter = this.textFilter.trim(); - } - - private setPattern(expression: glob.IExpression, pattern: string) { - if (pattern[0] === '.') { - pattern = '*' + pattern; // convert ".js" to "*.js" - } - expression[`**/${pattern}/**`] = true; - expression[`**/${pattern}`] = true; - } - - private matches(prefix: string, word: string): boolean { - let result = matchesPrefix(prefix, word); - return result && result.length > 0; - } -} +// TODO@joao +// export class FilterOptions { + +// static readonly _filter: IFilter = or(matchesPrefix, matchesContiguousSubString); +// static readonly _fuzzyFilter: IFilter = or(matchesPrefix, matchesContiguousSubString, matchesFuzzy); + +// readonly filterErrors: boolean = false; +// readonly filterWarnings: boolean = false; +// readonly filterInfos: boolean = false; +// readonly excludePattern: glob.ParsedExpression = null; +// readonly includePattern: glob.ParsedExpression = null; +// readonly textFilter: string = ''; + +// constructor(readonly filter: string = '', excludePatterns: glob.IExpression = {}) { +// filter = filter.trim(); +// for (const key of Object.keys(excludePatterns)) { +// if (excludePatterns[key]) { +// this.setPattern(excludePatterns, key); +// } +// delete excludePatterns[key]; +// } +// const includePatterns: glob.IExpression = glob.getEmptyExpression(); +// if (filter) { +// const filters = glob.splitGlobAware(filter, ',').map(s => s.trim()).filter(s => !!s.length); +// for (const f of filters) { +// this.filterErrors = this.filterErrors || this.matches(f, Messages.MARKERS_PANEL_FILTER_ERRORS); +// this.filterWarnings = this.filterWarnings || this.matches(f, Messages.MARKERS_PANEL_FILTER_WARNINGS); +// this.filterInfos = this.filterInfos || this.matches(f, Messages.MARKERS_PANEL_FILTER_INFOS); +// if (strings.startsWith(f, '!')) { +// this.setPattern(excludePatterns, strings.ltrim(f, '!')); +// } else { +// this.setPattern(includePatterns, f); +// this.textFilter += ` ${f}`; +// } +// } +// } +// if (Object.keys(excludePatterns).length) { +// this.excludePattern = glob.parse(excludePatterns); +// } +// if (Object.keys(includePatterns).length) { +// this.includePattern = glob.parse(includePatterns); +// } +// this.textFilter = this.textFilter.trim(); +// } + +// private setPattern(expression: glob.IExpression, pattern: string) { +// if (pattern[0] === '.') { +// pattern = '*' + pattern; // convert ".js" to "*.js" +// } +// expression[`**/${pattern}/**`] = true; +// expression[`**/${pattern}`] = true; +// } + +// private matches(prefix: string, word: string): boolean { +// let result = matchesPrefix(prefix, word); +// return result && result.length > 0; +// } +// } export class MarkersModel { private _cachedSortedResources: ResourceMarkers[]; private _markersByResource: Map; - private _filterOptions: FilterOptions; constructor(markers: IMarker[] = []) { this._markersByResource = new Map(); - this._filterOptions = new FilterOptions(); for (const group of groupBy(markers, MarkersModel._compareMarkersByUri)) { const resource = this.createResource(group[0].resource, group); @@ -198,53 +181,51 @@ export class MarkersModel { return compareUris(a.resource, b.resource); } - public get filterOptions(): FilterOptions { - return this._filterOptions; - } - - public get resources(): ResourceMarkers[] { + get resources(): ResourceMarkers[] { if (!this._cachedSortedResources) { this._cachedSortedResources = values(this._markersByResource).sort(ResourceMarkers.compare); } return this._cachedSortedResources; } - public forEachFilteredResource(callback: (resource: ResourceMarkers) => any) { - this._markersByResource.forEach(resource => { - if (resource.filteredCount > 0) { - callback(resource); - } - }); - } - - public hasFilteredResources(): boolean { - let res = false; - this._markersByResource.forEach(resource => { - res = res || resource.filteredCount > 0; - }); - return res; - } - - public hasResources(): boolean { + // TODO@joao + // forEachFilteredResource(callback: (resource: ResourceMarkers) => any) { + // this._markersByResource.forEach(resource => { + // if (resource.filteredCount > 0) { + // callback(resource); + // } + // }); + // } + + // TODO@joao + // hasFilteredResources(): boolean { + // let res = false; + // this._markersByResource.forEach(resource => { + // res = res || resource.filteredCount > 0; + // }); + // return res; + // } + + hasResources(): boolean { return this._markersByResource.size > 0; } - public hasResource(resource: URI): boolean { + hasResource(resource: URI): boolean { return this._markersByResource.has(resource.toString()); } - public stats(): { total: number, filtered: number } { + stats(): { total: number, filtered: number } { let total = 0; - let filtered = 0; + // let filtered = 0; this._markersByResource.forEach(resource => { total += resource.markers.length; - filtered += resource.filteredCount; + // filtered += resource.filteredCount; // TODO@joao }); - return { total, filtered }; - + console.warn('stats not implemented'); // TODO@joao + return { total, filtered: total }; } - public updateMarkers(callback: (updater: (resource: URI, markers: IMarker[]) => any) => void): void { + updateMarkers(callback: (updater: (resource: URI, markers: IMarker[]) => any) => void): void { callback((resource, markers) => { if (isFalsyOrEmpty(markers)) { this._markersByResource.delete(resource.toString()); @@ -255,22 +236,9 @@ export class MarkersModel { this._cachedSortedResources = undefined; } - public updateFilterOptions(filterOptions: FilterOptions): void { - this._filterOptions = filterOptions; - this._markersByResource.forEach(resource => { - this.updateResource(resource); - for (const marker of resource.markers) { - this.updateMarker(marker, resource); - } - this.updateFilteredCount(resource); - }); - } - private createResource(uri: URI, rawMarkers: IMarker[]): ResourceMarkers { - const markers: Marker[] = []; const resource = new ResourceMarkers(uri); - this.updateResource(resource); rawMarkers.forEach((rawMarker, index) => { const marker = new Marker(uri.toString() + index, rawMarker, resource); @@ -279,100 +247,18 @@ export class MarkersModel { groupedByResource.sort((a, b) => compareUris(a[0].resource, b[0].resource)); marker.resourceRelatedInformation = flatten(groupedByResource).map((r, index) => new RelatedInformation(marker.id + index, r)); } - this.updateMarker(marker, resource); markers.push(marker); }); resource.markers = markers.sort(Marker.compare); - this.updateFilteredCount(resource); - return resource; } - private updateResource(resource: ResourceMarkers): void { - resource.isExcluded = this.isResourceExcluded(resource); - resource.isIncluded = this.isResourceIncluded(resource); - resource.uriMatches = this._filterOptions.textFilter ? FilterOptions._filter(this._filterOptions.textFilter, paths.basename(resource.uri.fsPath)) : []; - } - - private updateFilteredCount(resource: ResourceMarkers): void { - if (resource.isExcluded) { - resource.filteredCount = 0; - } else if (resource.isIncluded) { - resource.filteredCount = resource.markers.length; - } else { - resource.filteredCount = resource.markers.filter(m => m.isSelected).length; - } - } - - private updateMarker(marker: Marker, resource: ResourceMarkers): void { - marker.messageMatches = !resource.isExcluded && this._filterOptions.textFilter ? FilterOptions._fuzzyFilter(this._filterOptions.textFilter, marker.raw.message) : []; - marker.sourceMatches = !resource.isExcluded && marker.raw.source && this._filterOptions.textFilter ? FilterOptions._filter(this._filterOptions.textFilter, marker.raw.source) : []; - marker.codeMatches = !resource.isExcluded && marker.raw.code && this._filterOptions.textFilter ? FilterOptions._filter(this._filterOptions.textFilter, marker.raw.code) : []; - marker.resourceRelatedInformation.forEach(r => { - r.uriMatches = !resource.isExcluded && this._filterOptions.textFilter ? FilterOptions._filter(this._filterOptions.textFilter, paths.basename(r.raw.resource.fsPath)) : []; - r.messageMatches = !resource.isExcluded && this._filterOptions.textFilter ? FilterOptions._fuzzyFilter(this._filterOptions.textFilter, r.raw.message) : []; - }); - marker.isSelected = this.isMarkerSelected(marker.raw, resource); - } - - private isResourceExcluded(resource: ResourceMarkers): boolean { - if (resource.uri.scheme === Schemas.walkThrough || resource.uri.scheme === Schemas.walkThroughSnippet) { - return true; - } - if (this.filterOptions.excludePattern && !!this.filterOptions.excludePattern(resource.uri.fsPath)) { - return true; - } - return false; - } - - private isResourceIncluded(resource: ResourceMarkers): boolean { - if (this.filterOptions.includePattern && this.filterOptions.includePattern(resource.uri.fsPath)) { - return true; - } - if (this._filterOptions.textFilter && !!FilterOptions._filter(this._filterOptions.textFilter, paths.basename(resource.uri.fsPath))) { - return true; - } - return false; - } - - private isMarkerSelected(marker: IMarker, resource: ResourceMarkers): boolean { - if (resource.isExcluded) { - return false; - } - if (resource.isIncluded) { - return true; - } - if (this._filterOptions.filterErrors && MarkerSeverity.Error === marker.severity) { - return true; - } - if (this._filterOptions.filterWarnings && MarkerSeverity.Warning === marker.severity) { - return true; - } - if (this._filterOptions.filterInfos && MarkerSeverity.Info === marker.severity) { - return true; - } - if (!this._filterOptions.textFilter) { - return true; - } - if (!!FilterOptions._fuzzyFilter(this._filterOptions.textFilter, marker.message)) { - return true; - } - if (!!marker.source && !!FilterOptions._filter(this._filterOptions.textFilter, marker.source)) { - return true; - } - if (!!marker.code && !!FilterOptions._filter(this._filterOptions.textFilter, marker.code)) { - return true; - } - if (!!marker.relatedInformation && marker.relatedInformation.some(r => - !!FilterOptions._filter(this._filterOptions.textFilter, paths.basename(r.resource.fsPath)) || ! - !FilterOptions._filter(this._filterOptions.textFilter, r.message))) { - return true; - } - return false; + getMarkers(resource: URI): ResourceMarkers | null { + return this._markersByResource.get(resource.toString()) || null; } - public dispose(): void { + dispose(): void { this._markersByResource.clear(); } } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 0206943dcab8d..644f9c8813c57 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -31,8 +31,6 @@ import { SimpleFileResourceDragAndDrop } from 'vs/workbench/browser/dnd'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { Scope } from 'vs/workbench/common/memento'; import { localize } from 'vs/nls'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class MarkersPanel extends Panel { @@ -115,16 +113,19 @@ export class MarkersPanel extends Panel { return; } - if (this.markersWorkbenchService.markersModel.hasFilteredResources()) { - this.tree.domFocus(); - if (this.tree.getSelection().length === 0) { - this.tree.focusFirst(); - } - this.highlightCurrentSelectedMarkerRange(); - this.autoReveal(true); - } else { - this.messageBoxContainer.focus(); - } + this.tree.domFocus(); + + // TODO@joao + // if (this.markersWorkbenchService.markersModel.hasFilteredResources()) { + // this.tree.domFocus(); + // if (this.tree.getSelection().length === 0) { + // this.tree.focusFirst(); + // } + // this.highlightCurrentSelectedMarkerRange(); + // this.autoReveal(true); + // } else { + // this.messageBoxContainer.focus(); + // } } public focusFilter(): void { @@ -180,14 +181,15 @@ export class MarkersPanel extends Panel { return false; } + // TODO@joao private refreshPanel(): TPromise { if (this.isVisible()) { - this.collapseAllAction.enabled = this.markersWorkbenchService.markersModel.hasFilteredResources(); - dom.toggleClass(this.treeContainer, 'hidden', !this.markersWorkbenchService.markersModel.hasFilteredResources()); + this.collapseAllAction.enabled = true /* this.markersWorkbenchService.markersModel.hasFilteredResources() */; + dom.toggleClass(this.treeContainer, 'hidden', false/* !this.markersWorkbenchService.markersModel.hasFilteredResources() */); this.renderMessage(); - if (this.markersWorkbenchService.markersModel.hasFilteredResources()) { - return this.tree.refresh(); - } + // if (this.markersWorkbenchService.markersModel.hasFilteredResources()) { + return this.tree.refresh(); + // } } return TPromise.as(null); } @@ -214,7 +216,6 @@ export class MarkersPanel extends Panel { const controller = this.instantiationService.createInstance(Controller, () => this.focusFilter()); this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { dataSource: new Viewer.DataSource(), - filter: new Viewer.DataFilter(), renderer, controller, accessibilityProvider: this.instantiationService.createInstance(Viewer.MarkersTreeAccessibilityProvider), @@ -300,76 +301,78 @@ export class MarkersPanel extends Panel { } } + // TODO@joao private async render(): Promise { - dom.toggleClass(this.treeContainer, 'hidden', !this.markersWorkbenchService.markersModel.hasFilteredResources()); + dom.toggleClass(this.treeContainer, 'hidden', false/* !this.markersWorkbenchService.markersModel.hasFilteredResources() */); await this.tree.setInput(this.markersWorkbenchService.markersModel); this.renderMessage(); } + // TODO@joao private renderMessage(): void { dom.clearNode(this.messageBoxContainer); const markersModel = this.markersWorkbenchService.markersModel; - if (markersModel.hasFilteredResources()) { - this.messageBoxContainer.style.display = 'none'; - const { total, filtered } = markersModel.stats(); - if (filtered === total) { - this.ariaLabelElement.setAttribute('aria-label', localize('No problems filtered', "Showing {0} problems", total)); - } else { - this.ariaLabelElement.setAttribute('aria-label', localize('problems filtered', "Showing {0} of {1} problems", filtered, total)); - } - this.messageBoxContainer.removeAttribute('tabIndex'); + // if (markersModel.hasFilteredResources()) { + this.messageBoxContainer.style.display = 'none'; + const { total, filtered } = markersModel.stats(); + if (filtered === total) { + this.ariaLabelElement.setAttribute('aria-label', localize('No problems filtered', "Showing {0} problems", total)); } else { - this.messageBoxContainer.style.display = 'block'; - this.messageBoxContainer.setAttribute('tabIndex', '0'); - if (markersModel.hasResources()) { - if (markersModel.filterOptions.filter) { - this.renderFilteredByFilterMessage(this.messageBoxContainer); - } else { - this.renderFilteredByFilesExcludeMessage(this.messageBoxContainer); - } - } else { - this.renderNoProblemsMessage(this.messageBoxContainer); - } + this.ariaLabelElement.setAttribute('aria-label', localize('problems filtered', "Showing {0} of {1} problems", filtered, total)); } - } - - private renderFilteredByFilesExcludeMessage(container: HTMLElement) { - const span1 = dom.append(container, dom.$('span')); - span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER; - const link = dom.append(container, dom.$('a.messageAction')); - link.textContent = localize('disableFilesExclude', "Disable Files Exclude Filter."); - link.setAttribute('tabIndex', '0'); - dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.useFilesExclude = false); - dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { - if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { - this.filterAction.useFilesExclude = false; - e.stopPropagation(); - } - }); - this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER); - } - - private renderFilteredByFilterMessage(container: HTMLElement) { - const span1 = dom.append(container, dom.$('span')); - span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS; - const link = dom.append(container, dom.$('a.messageAction')); - link.textContent = localize('clearFilter', "Clear Filter."); - link.setAttribute('tabIndex', '0'); - dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.filterText = ''); - dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { - if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { - this.filterAction.filterText = ''; - e.stopPropagation(); - } - }); - this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); - } - - private renderNoProblemsMessage(container: HTMLElement) { - const span = dom.append(container, dom.$('span')); - span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT; - this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT); - } + this.messageBoxContainer.removeAttribute('tabIndex'); + // } else { + // this.messageBoxContainer.style.display = 'block'; + // this.messageBoxContainer.setAttribute('tabIndex', '0'); + // if (markersModel.hasResources()) { + // if (markersModel.filterOptions.filter) { + // this.renderFilteredByFilterMessage(this.messageBoxContainer); + // } else { + // this.renderFilteredByFilesExcludeMessage(this.messageBoxContainer); + // } + // } else { + // this.renderNoProblemsMessage(this.messageBoxContainer); + // } + // } + } + + // private renderFilteredByFilesExcludeMessage(container: HTMLElement) { + // const span1 = dom.append(container, dom.$('span')); + // span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER; + // const link = dom.append(container, dom.$('a.messageAction')); + // link.textContent = localize('disableFilesExclude', "Disable Files Exclude Filter."); + // link.setAttribute('tabIndex', '0'); + // dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.useFilesExclude = false); + // dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + // if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { + // this.filterAction.useFilesExclude = false; + // e.stopPropagation(); + // } + // }); + // this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER); + // } + + // private renderFilteredByFilterMessage(container: HTMLElement) { + // const span1 = dom.append(container, dom.$('span')); + // span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS; + // const link = dom.append(container, dom.$('a.messageAction')); + // link.textContent = localize('clearFilter', "Clear Filter."); + // link.setAttribute('tabIndex', '0'); + // dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.filterText = ''); + // dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + // if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { + // this.filterAction.filterText = ''; + // e.stopPropagation(); + // } + // }); + // this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); + // } + + // private renderNoProblemsMessage(container: HTMLElement) { + // const span = dom.append(container, dom.$('span')); + // span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT; + // this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT); + // } private autoReveal(focus: boolean = false): void { let autoReveal = this.configurationService.getValue('problems.autoReveal'); @@ -402,16 +405,8 @@ export class MarkersPanel extends Panel { } } - private getResourceForCurrentActiveResource(): ResourceMarkers { - let res: ResourceMarkers = null; - if (this.currentActiveResource) { - this.markersWorkbenchService.markersModel.forEachFilteredResource(resource => { - if (!res && resource.uri.toString() === this.currentActiveResource.toString()) { - res = resource; - } - }); - } - return res; + private getResourceForCurrentActiveResource(): ResourceMarkers | null { + return this.currentActiveResource ? this.markersWorkbenchService.markersModel.getMarkers(this.currentActiveResource) : null; } private hasSelectedMarkerFor(resource: ResourceMarkers): boolean { diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts index 0b33828f95d2e..e702b8ba17a41 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts @@ -288,9 +288,10 @@ export class MarkersFilterActionItem extends BaseActionItem { private reportFilteringUsed(): void { let data = {}; - data['errors'] = this.markersWorkbenchService.markersModel.filterOptions.filterErrors; - data['warnings'] = this.markersWorkbenchService.markersModel.filterOptions.filterWarnings; - data['infos'] = this.markersWorkbenchService.markersModel.filterOptions.filterInfos; + console.warn('reportFilteringUsed not implemented'); // TODO@joao + // data['errors'] = this.markersWorkbenchService.markersModel.filterOptions.filterErrors; + // data['warnings'] = this.markersWorkbenchService.markersModel.filterOptions.filterWarnings; + // data['infos'] = this.markersWorkbenchService.markersModel.filterOptions.filterInfos; /* __GDPR__ "problems.filter" : { "errors" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index c652c0e9864fc..22e53b339495d 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -7,7 +7,7 @@ import { TPromise, Promise } from 'vs/base/common/winjs.base'; import * as dom from 'vs/base/browser/dom'; import * as network from 'vs/base/common/network'; import * as paths from 'vs/base/common/paths'; -import { IDataSource, ITree, IRenderer, IAccessibilityProvider, IFilter } from 'vs/base/parts/tree/browser/tree'; +import { IDataSource, ITree, IRenderer, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { FileLabel, ResourceLabel } from 'vs/workbench/browser/labels'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; @@ -93,18 +93,6 @@ export class DataSource implements IDataSource { } } -export class DataFilter implements IFilter { - public isVisible(tree: ITree, element: any): boolean { - if (element instanceof ResourceMarkers) { - return element.filteredCount > 0; - } - if (element instanceof Marker) { - return element.isSelected; - } - return true; - } -} - export class Renderer implements IRenderer { private static readonly RESOURCE_MARKERS_TEMPLATE_ID = 'resource-markers-template'; @@ -222,42 +210,45 @@ export class Renderer implements IRenderer { } } + // TODO@joao private renderResourceMarkersElement(tree: ITree, element: ResourceMarkers, templateData: IResourceMarkersTemplateData) { if (templateData.resourceLabel instanceof FileLabel) { - templateData.resourceLabel.setFile(element.uri, { matches: element.uriMatches }); + templateData.resourceLabel.setFile(element.uri/* , { matches: element.uriMatches } */); } else { - templateData.resourceLabel.setLabel({ name: element.name, description: this.labelService.getUriLabel(dirname(element.uri), { relative: true }), resource: element.uri }, { matches: element.uriMatches }); + templateData.resourceLabel.setLabel({ name: element.name, description: this.labelService.getUriLabel(dirname(element.uri), { relative: true }), resource: element.uri }/* , { matches: element.uriMatches } */); } - (templateData).count.setCount(element.filteredCount); + (templateData).count.setCount(element.markers.length/* filteredCount */); } + // TODO@joao private renderMarkerElement(tree: ITree, element: Marker, templateData: IMarkerTemplateData) { let marker = element.raw; templateData.icon.className = 'icon ' + Renderer.iconClassNameFor(marker); - templateData.source.set(marker.source, element.sourceMatches); + templateData.source.set(marker.source/* , element.sourceMatches */); dom.toggleClass(templateData.source.element, 'marker-source', !!marker.source); templateData.actionBar.clear(); const quickFixAction = this.instantiationService.createInstance(QuickFixAction, element); templateData.actionBar.push([quickFixAction], { icon: true, label: false }); - templateData.description.set(marker.message, element.messageMatches); + templateData.description.set(marker.message/* , element.messageMatches */); templateData.description.element.title = marker.message; dom.toggleClass(templateData.code.element, 'marker-code', !!marker.code); - templateData.code.set(marker.code || '', element.codeMatches); + templateData.code.set(marker.code || ''/* , element.codeMatches */); templateData.lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(marker.startLineNumber, marker.startColumn); } + // TODO@joao private renderRelatedInfoElement(tree: ITree, element: RelatedInformation, templateData: IRelatedInformationTemplateData) { - templateData.resourceLabel.set(paths.basename(element.raw.resource.fsPath), element.uriMatches); + templateData.resourceLabel.set(paths.basename(element.raw.resource.fsPath)/* , element.uriMatches */); templateData.resourceLabel.element.title = this.labelService.getUriLabel(element.raw.resource, { relative: true }); templateData.lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(element.raw.startLineNumber, element.raw.startColumn); - templateData.description.set(element.raw.message, element.messageMatches); + templateData.description.set(element.raw.message/* , element.messageMatches */); templateData.description.element.title = element.raw.message; } @@ -297,10 +288,11 @@ export class MarkersTreeAccessibilityProvider implements IAccessibilityProvider ) { } + // TODO@joao public getAriaLabel(tree: ITree, element: any): string { if (element instanceof ResourceMarkers) { const path = this.labelServie.getUriLabel(element.uri, { relative: true }) || element.uri.fsPath; - return Messages.MARKERS_TREE_ARIA_LABEL_RESOURCE(element.filteredCount, element.name, paths.dirname(path)); + return Messages.MARKERS_TREE_ARIA_LABEL_RESOURCE(element.markers.length/* element.filteredCount */, element.name, paths.dirname(path)); } if (element instanceof Marker) { return Messages.MARKERS_TREE_ARIA_LABEL_MARKER(element); diff --git a/src/vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts b/src/vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts index dbe592b436867..5d5f1f5cccdee 100644 --- a/src/vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts +++ b/src/vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts @@ -13,11 +13,8 @@ import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestSe class TestMarkersModel extends MarkersModel { get filteredResources(): ResourceMarkers[] { - const res: ResourceMarkers[] = []; - this.forEachFilteredResource(resource => res.push(resource)); - return res; + return this.resources; } - } suite('MarkersModel Test', () => { From 70ee13e7fa906068715ded5e25b5d22add78f059 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 4 Oct 2018 17:26:46 +0100 Subject: [PATCH 03/35] markers: wip reduce marker model --- .../electron-browser/markers.contribution.ts | 2 +- .../parts/markers/electron-browser/markers.ts | 17 +- .../markers/electron-browser/markersModel.ts | 215 +++++++----------- .../markers/electron-browser/markersPanel.ts | 6 +- .../electron-browser/markersPanelActions.ts | 4 +- .../electron-browser/markersTreeViewer.ts | 28 +-- .../markers/electron-browser/messages.ts | 20 +- .../electron-browser/markersModel.test.ts | 58 ++--- 8 files changed, 159 insertions(+), 191 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts b/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts index fef8aa5b91137..c5df5330e8d8f 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts @@ -163,7 +163,7 @@ function copyMessage(panelService: IPanelService) { if (activePanel instanceof MarkersPanel) { const element = (activePanel).getFocusElement(); if (element instanceof Marker) { - clipboard.writeText(element.raw.message); + clipboard.writeText(element.marker.message); } } } diff --git a/src/vs/workbench/parts/markers/electron-browser/markers.ts b/src/vs/workbench/parts/markers/electron-browser/markers.ts index aa934a8f2d9b9..ed1a267278977 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markers.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markers.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersModel } from './markersModel'; +import { MarkersModel, compareMarkersByUri } from './markersModel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IMarkerService, MarkerSeverity, IMarker } from 'vs/platform/markers/common/markers'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; @@ -18,6 +18,7 @@ import { deepClone, mixin } from 'vs/base/common/objects'; import { IExpression, getEmptyExpression } from 'vs/base/common/glob'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { join, isAbsolute } from 'vs/base/common/paths'; +import { groupBy } from 'vs/base/common/arrays'; export const IMarkersWorkbenchService = createDecorator('markersWorkbenchService'); @@ -54,6 +55,11 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb ) { super(); this.markersModel = this._register(instantiationService.createInstance(MarkersModel, this.readMarkers())); + + for (const group of groupBy(this.readMarkers(), compareMarkersByUri)) { + this.markersModel.setResourceMarkers(group[0].resource, group); + } + this._register(markerService.onMarkerChanged(resources => this.onMarkerChanged(resources))); // TODO@joao // this._register(configurationService.onDidChangeConfiguration(e => { @@ -69,11 +75,10 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb } private onMarkerChanged(resources: URI[]): void { - this.markersModel.updateMarkers(updater => { - for (const resource of resources) { - updater(resource, this.readMarkers(resource)); - } - }); + for (const resource of resources) { + this.markersModel.setResourceMarkers(resource, this.readMarkers(resource)); + } + this.refreshBadge(); this._onDidChange.fire(resources); } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts index 77d515a998d34..660d509122399 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts @@ -7,101 +7,71 @@ import * as paths from 'vs/base/common/paths'; import { URI } from 'vs/base/common/uri'; import { Range, IRange } from 'vs/editor/common/core/range'; import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers'; -import { groupBy, isFalsyOrEmpty, flatten } from 'vs/base/common/arrays'; +import { groupBy, flatten, isFalsyOrEmpty } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/map'; +import { memoize } from 'vs/base/common/decorators'; function compareUris(a: URI, b: URI) { - if (a.toString() < b.toString()) { - return -1; - } else if (a.toString() > b.toString()) { - return 1; - } else { - return 0; - } + const astr = a.toString(); + const bstr = b.toString(); + return astr === bstr ? 0 : (astr < bstr ? -1 : 1); } -export abstract class NodeWithId { - constructor(readonly id: string) { } +export function compareMarkersByUri(a: IMarker, b: IMarker) { + return compareUris(a.resource, b.resource); } -export class ResourceMarkers extends NodeWithId { +function compareResourceMarkers(a: ResourceMarkers, b: ResourceMarkers): number { + let [firstMarkerOfA] = a.markers; + let [firstMarkerOfB] = b.markers; + let res = 0; + if (firstMarkerOfA && firstMarkerOfB) { + res = MarkerSeverity.compare(firstMarkerOfA.marker.severity, firstMarkerOfB.marker.severity); + } + if (res === 0) { + res = a.path.localeCompare(b.path) || a.name.localeCompare(b.name); + } + return res; +} - private _name: string = null; - private _path: string = null; +function compareMarkers(a: Marker, b: Marker): number { + return MarkerSeverity.compare(a.marker.severity, b.marker.severity) + || Range.compareRangesUsingStarts(a.marker, b.marker); +} - markers: Marker[] = []; +export class ResourceMarkers { - constructor(readonly uri: URI) { - super(uri.toString()); - } + @memoize + get path(): string { return this.resource.fsPath; } - get path(): string { - if (this._path === null) { - this._path = this.uri.fsPath; - } - return this._path; - } - - get name(): string { - if (this._name === null) { - this._name = paths.basename(this.uri.fsPath); - } - return this._name; - } + @memoize + get name(): string { return paths.basename(this.resource.fsPath); } - static compare(a: ResourceMarkers, b: ResourceMarkers): number { - let [firstMarkerOfA] = a.markers; - let [firstMarkerOfB] = b.markers; - let res = 0; - if (firstMarkerOfA && firstMarkerOfB) { - res = MarkerSeverity.compare(firstMarkerOfA.raw.severity, firstMarkerOfB.raw.severity); - } - if (res === 0) { - res = a.path.localeCompare(b.path) || a.name.localeCompare(b.name); - } - return res; - } + constructor(readonly resource: URI, readonly markers: Marker[]) { } } -export class Marker extends NodeWithId { +export class Marker { - resourceRelatedInformation: RelatedInformation[] = []; + get resource(): URI { return this.marker.resource; } + get range(): IRange { return this.marker; } constructor( - id: string, - readonly raw: IMarker, - readonly resourceMarkers: ResourceMarkers - ) { - super(id); - } - - get resource(): URI { - return this.raw.resource; - } - - get range(): IRange { - return this.raw; - } + readonly marker: IMarker, + readonly relatedInformation: RelatedInformation[] = [] + ) { } toString(): string { return JSON.stringify({ - ...this.raw, - resource: this.raw.resource.path, - relatedInformation: this.resourceRelatedInformation.length ? this.resourceRelatedInformation.map(r => ({ ...r.raw, resource: r.raw.resource.path })) : void 0 + ...this.marker, + resource: this.marker.resource.path, + relatedInformation: this.relatedInformation.length ? this.relatedInformation.map(r => ({ ...r.raw, resource: r.raw.resource.path })) : void 0 }, null, '\t'); } - - static compare(a: Marker, b: Marker): number { - return MarkerSeverity.compare(a.raw.severity, b.raw.severity) - || Range.compareRangesUsingStarts(a.raw, b.raw); - } } -export class RelatedInformation extends NodeWithId { +export class RelatedInformation { - constructor(id: string, readonly raw: IRelatedInformation) { - super(id); - } + constructor(readonly raw: IRelatedInformation) { } } // TODO@joao @@ -165,27 +135,59 @@ export class RelatedInformation extends NodeWithId { export class MarkersModel { - private _cachedSortedResources: ResourceMarkers[]; - private _markersByResource: Map; + private cachedSortedResources: ResourceMarkers[] | undefined = undefined; - constructor(markers: IMarker[] = []) { - this._markersByResource = new Map(); - - for (const group of groupBy(markers, MarkersModel._compareMarkersByUri)) { - const resource = this.createResource(group[0].resource, group); - this._markersByResource.set(resource.uri.toString(), resource); + get resourceMarkers(): ResourceMarkers[] { + if (!this.cachedSortedResources) { + this.cachedSortedResources = values(this.resourcesByUri).sort(compareResourceMarkers); } + + return this.cachedSortedResources; } - private static _compareMarkersByUri(a: IMarker, b: IMarker) { - return compareUris(a.resource, b.resource); + private resourcesByUri: Map; + + constructor() { + this.resourcesByUri = new Map(); } - get resources(): ResourceMarkers[] { - if (!this._cachedSortedResources) { - this._cachedSortedResources = values(this._markersByResource).sort(ResourceMarkers.compare); + // updateMarkers(callback: (updater: (resource: URI, markers: IMarker[]) => any) => void): void { + // callback((resource, markers) => { + // if (isFalsyOrEmpty(markers)) { + // this.resourcesByUri.delete(resource.toString()); + // } else { + // this.resourcesByUri.set(resource.toString(), this.createResource(resource, markers)); + // } + // }); + // this.cachedSortedResources = undefined; + // } + + getResourceMarkers(resource: URI): ResourceMarkers | null { + return this.resourcesByUri.get(resource.toString()) || null; + } + + setResourceMarkers(resource: URI, rawMarkers: IMarker[]): void { + if (isFalsyOrEmpty(rawMarkers)) { + this.resourcesByUri.delete(resource.toString()); + } else { + const markers = rawMarkers.map(rawMarker => { + let relatedInformation: RelatedInformation[] | undefined = undefined; + + if (rawMarker.relatedInformation) { + const groupedByResource = groupBy(rawMarker.relatedInformation, compareMarkersByUri); + groupedByResource.sort((a, b) => compareUris(a[0].resource, b[0].resource)); + relatedInformation = flatten(groupedByResource).map((r, index) => new RelatedInformation(r)); + } + + return new Marker(rawMarker, relatedInformation); + }); + + markers.sort(compareMarkers); + + this.resourcesByUri.set(resource.toString(), new ResourceMarkers(resource, markers)); } - return this._cachedSortedResources; + + this.cachedSortedResources = undefined; } // TODO@joao @@ -206,18 +208,10 @@ export class MarkersModel { // return res; // } - hasResources(): boolean { - return this._markersByResource.size > 0; - } - - hasResource(resource: URI): boolean { - return this._markersByResource.has(resource.toString()); - } - stats(): { total: number, filtered: number } { let total = 0; // let filtered = 0; - this._markersByResource.forEach(resource => { + this.resourcesByUri.forEach(resource => { total += resource.markers.length; // filtered += resource.filteredCount; // TODO@joao }); @@ -225,40 +219,7 @@ export class MarkersModel { return { total, filtered: total }; } - updateMarkers(callback: (updater: (resource: URI, markers: IMarker[]) => any) => void): void { - callback((resource, markers) => { - if (isFalsyOrEmpty(markers)) { - this._markersByResource.delete(resource.toString()); - } else { - this._markersByResource.set(resource.toString(), this.createResource(resource, markers)); - } - }); - this._cachedSortedResources = undefined; - } - - private createResource(uri: URI, rawMarkers: IMarker[]): ResourceMarkers { - const markers: Marker[] = []; - const resource = new ResourceMarkers(uri); - - rawMarkers.forEach((rawMarker, index) => { - const marker = new Marker(uri.toString() + index, rawMarker, resource); - if (rawMarker.relatedInformation) { - const groupedByResource = groupBy(rawMarker.relatedInformation, MarkersModel._compareMarkersByUri); - groupedByResource.sort((a, b) => compareUris(a[0].resource, b[0].resource)); - marker.resourceRelatedInformation = flatten(groupedByResource).map((r, index) => new RelatedInformation(marker.id + index, r)); - } - markers.push(marker); - }); - resource.markers = markers.sort(Marker.compare); - - return resource; - } - - getMarkers(resource: URI): ResourceMarkers | null { - return this._markersByResource.get(resource.toString()) || null; - } - dispose(): void { - this._markersByResource.clear(); + this.resourcesByUri.clear(); } } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 644f9c8813c57..7aefc5555f30c 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -212,7 +212,7 @@ export class MarkersPanel extends Panel { private createTree(parent: HTMLElement): void { this.treeContainer = dom.append(parent, dom.$('.tree-container.show-file-icons')); const renderer = this.instantiationService.createInstance(Viewer.Renderer, (action) => this.getActionItem(action)); - const dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, obj => obj instanceof ResourceMarkers ? obj.uri : void 0); + const dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, obj => obj instanceof ResourceMarkers ? obj.resource : void 0); const controller = this.instantiationService.createInstance(Controller, () => this.focusFilter()); this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { dataSource: new Viewer.DataSource(), @@ -406,14 +406,14 @@ export class MarkersPanel extends Panel { } private getResourceForCurrentActiveResource(): ResourceMarkers | null { - return this.currentActiveResource ? this.markersWorkbenchService.markersModel.getMarkers(this.currentActiveResource) : null; + return this.currentActiveResource ? this.markersWorkbenchService.markersModel.getResourceMarkers(this.currentActiveResource) : null; } private hasSelectedMarkerFor(resource: ResourceMarkers): boolean { let selectedElement = this.tree.getSelection(); if (selectedElement && selectedElement.length > 0) { if (selectedElement[0] instanceof Marker) { - if (resource.uri.toString() === (selectedElement[0]).raw.resource.toString()) { + if (resource.resource.toString() === (selectedElement[0]).marker.resource.toString()) { return true; } } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts index e702b8ba17a41..6245d0fd15c72 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts @@ -319,7 +319,7 @@ export class QuickFixAction extends Action { @IModelService private modelService: IModelService ) { super(QuickFixAction.ID, Messages.MARKERS_PANEL_ACTION_TOOLTIP_QUICKFIX, 'markers-panel-action-quickfix', false); - if (modelService.getModel(this.marker.resourceMarkers.uri)) { + if (modelService.getModel(this.marker.resource)) { this.update(); } else { modelService.onModelAdded(model => { @@ -377,7 +377,7 @@ export class QuickFixAction extends Action { } const allFixes = await this._allFixesPromise; if (allFixes.length) { - const markerKey = IMarkerData.makeKey(marker.raw); + const markerKey = IMarkerData.makeKey(marker.marker); for (const fix of allFixes) { if (fix.diagnostics && fix.diagnostics.some(d => IMarkerData.makeKey(d) === markerKey)) { return true; diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 22e53b339495d..a139ae134516e 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -12,7 +12,7 @@ import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { FileLabel, ResourceLabel } from 'vs/workbench/browser/labels'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IMarker, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { MarkersModel, ResourceMarkers, Marker, RelatedInformation, NodeWithId } from 'vs/workbench/parts/markers/electron-browser/markersModel'; +import { MarkersModel, ResourceMarkers, Marker, RelatedInformation } from 'vs/workbench/parts/markers/electron-browser/markersModel'; import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; @@ -49,25 +49,25 @@ export class DataSource implements IDataSource { if (element instanceof MarkersModel) { return 'root'; } - if (element instanceof NodeWithId) { - return element.id; - } + // if (element instanceof NodeWithId) { + // return element.id; + // } return ''; } public hasChildren(tree: ITree, element: any): boolean { - return element instanceof MarkersModel || element instanceof ResourceMarkers || (element instanceof Marker && element.resourceRelatedInformation.length > 0); + return element instanceof MarkersModel || element instanceof ResourceMarkers || (element instanceof Marker && element.relatedInformation.length > 0); } public getChildren(tree: ITree, element: any): Promise { if (element instanceof MarkersModel) { - return Promise.as(element.resources); + return Promise.as(element.resourceMarkers); } if (element instanceof ResourceMarkers) { return Promise.as(element.markers); } - if (element instanceof Marker && element.resourceRelatedInformation.length > 0) { - return Promise.as(element.resourceRelatedInformation); + if (element instanceof Marker && element.relatedInformation.length > 0) { + return Promise.as(element.relatedInformation); } return null; } @@ -85,7 +85,7 @@ export class DataSource implements IDataSource { return true; } - if (element instanceof Marker && element.resourceRelatedInformation.length > 0) { + if (element instanceof Marker && element.relatedInformation.length > 0) { return true; } @@ -114,7 +114,7 @@ export class Renderer implements IRenderer { public getTemplateId(tree: ITree, element: any): string { if (element instanceof ResourceMarkers) { - if ((element).uri.scheme === network.Schemas.file || (element).uri.scheme === network.Schemas.untitled) { + if ((element).resource.scheme === network.Schemas.file || (element).resource.scheme === network.Schemas.untitled) { return Renderer.FILE_RESOURCE_MARKERS_TEMPLATE_ID; } else { return Renderer.RESOURCE_MARKERS_TEMPLATE_ID; @@ -213,16 +213,16 @@ export class Renderer implements IRenderer { // TODO@joao private renderResourceMarkersElement(tree: ITree, element: ResourceMarkers, templateData: IResourceMarkersTemplateData) { if (templateData.resourceLabel instanceof FileLabel) { - templateData.resourceLabel.setFile(element.uri/* , { matches: element.uriMatches } */); + templateData.resourceLabel.setFile(element.resource/* , { matches: element.uriMatches } */); } else { - templateData.resourceLabel.setLabel({ name: element.name, description: this.labelService.getUriLabel(dirname(element.uri), { relative: true }), resource: element.uri }/* , { matches: element.uriMatches } */); + templateData.resourceLabel.setLabel({ name: element.name, description: this.labelService.getUriLabel(dirname(element.resource), { relative: true }), resource: element.resource }/* , { matches: element.uriMatches } */); } (templateData).count.setCount(element.markers.length/* filteredCount */); } // TODO@joao private renderMarkerElement(tree: ITree, element: Marker, templateData: IMarkerTemplateData) { - let marker = element.raw; + let marker = element.marker; templateData.icon.className = 'icon ' + Renderer.iconClassNameFor(marker); @@ -291,7 +291,7 @@ export class MarkersTreeAccessibilityProvider implements IAccessibilityProvider // TODO@joao public getAriaLabel(tree: ITree, element: any): string { if (element instanceof ResourceMarkers) { - const path = this.labelServie.getUriLabel(element.uri, { relative: true }) || element.uri.fsPath; + const path = this.labelServie.getUriLabel(element.resource, { relative: true }) || element.resource.fsPath; return Messages.MARKERS_TREE_ARIA_LABEL_RESOURCE(element.markers.length/* element.filteredCount */, element.name, paths.dirname(path)); } if (element instanceof Marker) { diff --git a/src/vs/workbench/parts/markers/electron-browser/messages.ts b/src/vs/workbench/parts/markers/electron-browser/messages.ts index 3cced7be7691e..50e2086b5868b 100644 --- a/src/vs/workbench/parts/markers/electron-browser/messages.ts +++ b/src/vs/workbench/parts/markers/electron-browser/messages.ts @@ -47,21 +47,21 @@ export default class Messages { public static readonly MARKERS_TREE_ARIA_LABEL_RESOURCE = (noOfProblems: number, fileName: string, folder: string): string => { return nls.localize('problems.tree.aria.label.resource', "{0} problems in file {1} of folder {2}", noOfProblems, fileName, folder); }; public static readonly MARKERS_TREE_ARIA_LABEL_MARKER = (marker: Marker): string => { - const relatedInformationMessage = marker.resourceRelatedInformation.length ? nls.localize('problems.tree.aria.label.marker.relatedInformation', " This problem has references to {0} locations.", marker.resourceRelatedInformation.length) : ''; - switch (marker.raw.severity) { + const relatedInformationMessage = marker.relatedInformation.length ? nls.localize('problems.tree.aria.label.marker.relatedInformation', " This problem has references to {0} locations.", marker.relatedInformation.length) : ''; + switch (marker.marker.severity) { case MarkerSeverity.Error: - return marker.raw.source ? nls.localize('problems.tree.aria.label.error.marker', "Error generated by {0}: {1} at line {2} and character {3}.{4}", marker.raw.source, marker.raw.message, marker.raw.startLineNumber, marker.raw.startColumn, relatedInformationMessage) - : nls.localize('problems.tree.aria.label.error.marker.nosource', "Error: {0} at line {1} and character {2}.{3}", marker.raw.message, marker.raw.startLineNumber, marker.raw.startColumn, relatedInformationMessage); + return marker.marker.source ? nls.localize('problems.tree.aria.label.error.marker', "Error generated by {0}: {1} at line {2} and character {3}.{4}", marker.marker.source, marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage) + : nls.localize('problems.tree.aria.label.error.marker.nosource', "Error: {0} at line {1} and character {2}.{3}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage); case MarkerSeverity.Warning: - return marker.raw.source ? nls.localize('problems.tree.aria.label.warning.marker', "Warning generated by {0}: {1} at line {2} and character {3}.{4}", marker.raw.source, marker.raw.message, marker.raw.startLineNumber, marker.raw.startColumn, relatedInformationMessage) - : nls.localize('problems.tree.aria.label.warning.marker.nosource', "Warning: {0} at line {1} and character {2}.{3}", marker.raw.message, marker.raw.startLineNumber, marker.raw.startColumn, relatedInformationMessage, relatedInformationMessage); + return marker.marker.source ? nls.localize('problems.tree.aria.label.warning.marker', "Warning generated by {0}: {1} at line {2} and character {3}.{4}", marker.marker.source, marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage) + : nls.localize('problems.tree.aria.label.warning.marker.nosource', "Warning: {0} at line {1} and character {2}.{3}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage, relatedInformationMessage); case MarkerSeverity.Info: - return marker.raw.source ? nls.localize('problems.tree.aria.label.info.marker', "Info generated by {0}: {1} at line {2} and character {3}.{4}", marker.raw.source, marker.raw.message, marker.raw.startLineNumber, marker.raw.startColumn, relatedInformationMessage) - : nls.localize('problems.tree.aria.label.info.marker.nosource', "Info: {0} at line {1} and character {2}.{3}", marker.raw.message, marker.raw.startLineNumber, marker.raw.startColumn, relatedInformationMessage); + return marker.marker.source ? nls.localize('problems.tree.aria.label.info.marker', "Info generated by {0}: {1} at line {2} and character {3}.{4}", marker.marker.source, marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage) + : nls.localize('problems.tree.aria.label.info.marker.nosource', "Info: {0} at line {1} and character {2}.{3}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage); default: - return marker.raw.source ? nls.localize('problems.tree.aria.label.marker', "Problem generated by {0}: {1} at line {2} and character {3}.{4}", marker.raw.source, marker.raw.message, marker.raw.startLineNumber, marker.raw.startColumn, relatedInformationMessage) - : nls.localize('problems.tree.aria.label.marker.nosource', "Problem: {0} at line {1} and character {2}.{3}", marker.raw.message, marker.raw.startLineNumber, marker.raw.startColumn, relatedInformationMessage); + return marker.marker.source ? nls.localize('problems.tree.aria.label.marker', "Problem generated by {0}: {1} at line {2} and character {3}.{4}", marker.marker.source, marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage) + : nls.localize('problems.tree.aria.label.marker.nosource', "Problem: {0} at line {1} and character {2}.{3}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage); } } public static readonly MARKERS_TREE_ARIA_LABEL_RELATED_INFORMATION = (relatedInformation: IRelatedInformation): string => nls.localize('problems.tree.aria.label.relatedinfo.message', "{0} at line {1} and character {2} in {3}", relatedInformation.message, relatedInformation.startLineNumber, relatedInformation.startColumn, paths.basename(relatedInformation.resource.fsPath)); diff --git a/src/vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts b/src/vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts index 5d5f1f5cccdee..195e29eacaf8f 100644 --- a/src/vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts +++ b/src/vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts @@ -13,7 +13,7 @@ import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestSe class TestMarkersModel extends MarkersModel { get filteredResources(): ResourceMarkers[] { - return this.resources; + return this.resourceMarkers; } } @@ -66,7 +66,7 @@ suite('MarkersModel Test', () => { const marker6 = aMarker('c/res2', MarkerSeverity.Info); const testObject = instantiationService.createInstance(TestMarkersModel, [marker1, marker2, marker3, marker4, marker5, marker6]); - const actuals = testObject.resources; + const actuals = testObject.resourceMarkers; assert.equal(5, actuals.length); assert.ok(compareResource(actuals[0], 'a/res2')); @@ -85,7 +85,7 @@ suite('MarkersModel Test', () => { const marker6 = aMarker('c/res2'); const testObject = instantiationService.createInstance(TestMarkersModel, [marker1, marker2, marker3, marker4, marker5, marker6]); - const actuals = testObject.resources; + const actuals = testObject.resourceMarkers; assert.equal(5, actuals.length); assert.ok(compareResource(actuals[0], 'a/res1')); @@ -113,53 +113,55 @@ suite('MarkersModel Test', () => { const marker15 = anErrorWithRange(8, 2, 8, 4); const testObject = instantiationService.createInstance(TestMarkersModel, [marker1, marker2, marker3, marker4, marker5, marker6, marker7, marker8, marker9, marker10, marker11, marker12, marker13, marker14, marker15]); - const actuals = testObject.resources[0].markers; - - assert.equal(actuals[0].raw, marker6); - assert.equal(actuals[1].raw, marker14); - assert.equal(actuals[2].raw, marker7); - assert.equal(actuals[3].raw, marker9); - assert.equal(actuals[4].raw, marker11); - assert.equal(actuals[5].raw, marker3); - assert.equal(actuals[6].raw, marker15); - assert.equal(actuals[7].raw, marker10); - assert.equal(actuals[8].raw, marker2); - assert.equal(actuals[9].raw, marker13); - assert.equal(actuals[10].raw, marker1); - assert.equal(actuals[11].raw, marker8); - assert.equal(actuals[12].raw, marker5); - assert.equal(actuals[13].raw, marker12); - assert.equal(actuals[14].raw, marker4); + const actuals = testObject.resourceMarkers[0].markers; + + assert.equal(actuals[0].marker, marker6); + assert.equal(actuals[1].marker, marker14); + assert.equal(actuals[2].marker, marker7); + assert.equal(actuals[3].marker, marker9); + assert.equal(actuals[4].marker, marker11); + assert.equal(actuals[5].marker, marker3); + assert.equal(actuals[6].marker, marker15); + assert.equal(actuals[7].marker, marker10); + assert.equal(actuals[8].marker, marker2); + assert.equal(actuals[9].marker, marker13); + assert.equal(actuals[10].marker, marker1); + assert.equal(actuals[11].marker, marker8); + assert.equal(actuals[12].marker, marker5); + assert.equal(actuals[13].marker, marker12); + assert.equal(actuals[14].marker, marker4); }); test('toString()', () => { let marker = aMarker('a/res1'); marker.code = '1234'; - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), instantiationService.createInstance(Marker, '', marker, null).toString()); + assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), instantiationService.createInstance(Marker, marker, null).toString()); marker = aMarker('a/res2', MarkerSeverity.Warning); - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), instantiationService.createInstance(Marker, '', marker, null).toString()); + assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), instantiationService.createInstance(Marker, marker, null).toString()); marker = aMarker('a/res2', MarkerSeverity.Info, 1, 2, 1, 8, 'Info', ''); - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), instantiationService.createInstance(Marker, '', marker, null).toString()); + assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), instantiationService.createInstance(Marker, marker, null).toString()); marker = aMarker('a/res2', MarkerSeverity.Hint, 1, 2, 1, 8, 'Ignore message', 'Ignore'); - assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), instantiationService.createInstance(Marker, '', marker, null).toString()); + assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path }, null, '\t'), instantiationService.createInstance(Marker, marker, null).toString()); marker = aMarker('a/res2', MarkerSeverity.Warning, 1, 2, 1, 8, 'Warning message', '', [{ startLineNumber: 2, startColumn: 5, endLineNumber: 2, endColumn: 10, message: 'some info', resource: URI.file('a/res3') }]); - const testObject = instantiationService.createInstance(Marker, '', marker, null); - testObject.resourceRelatedInformation = marker.relatedInformation.map(r => new RelatedInformation('', r)); + const testObject = instantiationService.createInstance(Marker, marker, null); + + // hack + (testObject as any).relatedInformation = marker.relatedInformation.map(r => new RelatedInformation(r)); assert.equal(JSON.stringify({ ...marker, resource: marker.resource.path, relatedInformation: marker.relatedInformation.map(r => ({ ...r, resource: r.resource.path })) }, null, '\t'), testObject.toString()); }); function hasMarker(markers: Marker[], marker: IMarker): boolean { return markers.filter((m): boolean => { - return m.raw === marker; + return m.marker === marker; }).length === 1; } function compareResource(a: ResourceMarkers, b: string): boolean { - return a.uri.toString() === URI.file(b).toString(); + return a.resource.toString() === URI.file(b).toString(); } function anErrorWithRange(startLineNumber: number = 10, From 7843e40d0680ff51d633104c5d964827de0a65aa Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 8 Oct 2018 08:31:16 +0100 Subject: [PATCH 04/35] :lipstick: --- src/vs/workbench/parts/markers/electron-browser/markersModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts index 660d509122399..a4956c6e463dc 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts @@ -176,7 +176,7 @@ export class MarkersModel { if (rawMarker.relatedInformation) { const groupedByResource = groupBy(rawMarker.relatedInformation, compareMarkersByUri); groupedByResource.sort((a, b) => compareUris(a[0].resource, b[0].resource)); - relatedInformation = flatten(groupedByResource).map((r, index) => new RelatedInformation(r)); + relatedInformation = flatten(groupedByResource).map(r => new RelatedInformation(r)); } return new Marker(rawMarker, relatedInformation); From 2570a185b2d3636174061c2a6ce11f5d18c9c079 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 8 Oct 2018 09:59:46 +0100 Subject: [PATCH 05/35] wip: problems tree --- src/vs/base/browser/ui/tree/abstractTree.ts | 104 +++++++- src/vs/platform/list/browser/listService.ts | 2 +- .../browser/parts/editor/editorCommands.ts | 2 +- src/vs/workbench/electron-browser/commands.ts | 2 +- src/vs/workbench/parts/files/browser/files.ts | 4 +- .../markers/electron-browser/markersPanel.ts | 174 +++++++----- .../electron-browser/markersPanelActions.ts | 9 - .../electron-browser/markersTreeViewer.ts | 247 +++++++++--------- 8 files changed, 345 insertions(+), 199 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index a4f8a0988276d..a953f25ff6cc9 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -8,12 +8,13 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IListOptions, List, IIdentityProvider, IMultipleSelectionController } from 'vs/base/browser/ui/list/listWidget'; import { IVirtualDelegate, IRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list'; import { append, $ } from 'vs/base/browser/dom'; -import { Event, Relay, chain } from 'vs/base/common/event'; +import { Event, Relay, chain, mapEvent } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ITreeModel, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel'; +import { memoize } from 'vs/base/common/decorators'; export function createComposedTreeListOptions(options?: IListOptions): IListOptions { if (!options) { @@ -174,6 +175,14 @@ export abstract class AbstractTree implements IDisposable readonly onDidChangeCollapseState: Event>; + @memoize get onDidChangeFocus(): Event { + return mapEvent(this.view.onFocusChange, e => e.elements.map(e => e.element)); + } + + @memoize get onDidChangeSelection(): Event { + return mapEvent(this.view.onSelectionChange, e => e.elements.map(e => e.element)); + } + constructor( container: HTMLElement, delegate: IVirtualDelegate, @@ -202,10 +211,13 @@ export abstract class AbstractTree implements IDisposable onKeyDown.filter(e => e.keyCode === KeyCode.Space).on(this.onSpace, this, this.disposables); } - protected abstract createModel(view: ISpliceable>, options: ITreeOptions): ITreeModel; + // TODO@joao rename to `get domElement` + getHTMLElement(): HTMLElement { + return this.view.getHTMLElement(); + } - refilter(): void { - this.model.refilter(); + layout(height?: number): void { + this.view.layout(height); } collapse(location: TRef): boolean { @@ -216,6 +228,88 @@ export abstract class AbstractTree implements IDisposable return this.model.setCollapsed(location, false); } + toggleCollapsed(ref: TRef): void { + this.model.toggleCollapsed(ref); + } + + isCollapsed(ref: TRef): boolean { + return this.model.isCollapsed(ref); + } + + isExpanded(ref: TRef): boolean { + return !this.isCollapsed(ref); + } + + refilter(): void { + this.model.refilter(); + } + + setSelection(elements: TRef[]): void { + const indexes = elements.map(e => this.model.getListIndex(e)); + this.view.setSelection(indexes); + } + + selectNext(n = 1, loop = false): void { + this.view.selectNext(n, loop); + } + + selectPrevious(n = 1, loop = false): void { + this.view.selectPrevious(n, loop); + } + + getSelection(): T[] { + const nodes = this.view.getSelectedElements(); + return nodes.map(n => n.element); + } + + setFocus(elements: TRef[]): void { + const indexes = elements.map(e => this.model.getListIndex(e)); + this.view.setFocus(indexes); + } + + focusNext(n = 1, loop = false): void { + this.view.focusNext(); + } + + focusPrevious(n = 1, loop = false): void { + this.view.focusPrevious(); + } + + focusNextPage(): void { + this.view.focusNextPage(); + } + + focusPreviousPage(): void { + this.view.focusPreviousPage(); + } + + focusLast(): void { + this.view.focusLast(); + } + + focusFirst(): void { + this.view.focusFirst(); + } + + getFocus(): T[] { + const nodes = this.view.getFocusedElements(); + return nodes.map(n => n.element); + } + + reveal(location: TRef, relativeTop?: number): void { + const index = this.model.getListIndex(location); + this.view.reveal(index, relativeTop); + } + + /** + * Returns the relative position of an element rendered in the list. + * Returns `null` if the element isn't *entirely* in the visible viewport. + */ + getRelativeTop(location: TRef): number | null { + const index = this.model.getListIndex(location); + return this.view.getRelativeTop(index); + } + private onMouseClick(e: IListMouseEvent>): void { const node = e.element; const location = this.model.getNodeLocation(node); @@ -293,6 +387,8 @@ export abstract class AbstractTree implements IDisposable this.model.toggleCollapsed(location); } + protected abstract createModel(view: ISpliceable>, options: ITreeOptions): ITreeModel; + dispose(): void { this.disposables = dispose(this.disposables); this.view.dispose(); diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 56fffaac42458..13880be7cf186 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -75,7 +75,7 @@ export class ListService implements IListService { this.lists.push(registeredList); // Check for currently being focused - if (widget.isDOMFocused()) { + if (widget.getHTMLElement() === document.activeElement) { this._lastFocusedWidget = widget; } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index c94806acc4aae..d01baf277a88d 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -687,7 +687,7 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon // First check for a focused list to return the selected items from const list = listService.lastFocusedList; - if (list instanceof List && list.isDOMFocused()) { + if (list instanceof List && list.getHTMLElement() === document.activeElement) { const elementToContext = (element: IEditorIdentifier | IEditorGroup) => { if (isEditorGroup(element)) { return { groupId: element.id, editorIndex: void 0 }; diff --git a/src/vs/workbench/electron-browser/commands.ts b/src/vs/workbench/electron-browser/commands.ts index 0db3f756eb11c..45fe4a8eed78e 100644 --- a/src/vs/workbench/electron-browser/commands.ts +++ b/src/vs/workbench/electron-browser/commands.ts @@ -30,7 +30,7 @@ function ensureDOMFocus(widget: ListWidget): void { // DOM focus is within another focusable control within the // list/tree item. therefor we should ensure that the // list/tree has DOM focus again after the command ran. - if (widget && !widget.isDOMFocused()) { + if (widget && widget.getHTMLElement() !== document.activeElement) { widget.domFocus(); } } diff --git a/src/vs/workbench/parts/files/browser/files.ts b/src/vs/workbench/parts/files/browser/files.ts index 9d1c2f6887dac..00f7804d9df7e 100644 --- a/src/vs/workbench/parts/files/browser/files.ts +++ b/src/vs/workbench/parts/files/browser/files.ts @@ -19,7 +19,7 @@ export function getResourceForCommand(resource: URI | object, listService: IList } let list = listService.lastFocusedList; - if (list && list.isDOMFocused()) { + if (list && list.getHTMLElement() === document.activeElement) { let focus: any; if (list instanceof List) { const focused = list.getFocusedElements(); @@ -42,7 +42,7 @@ export function getResourceForCommand(resource: URI | object, listService: IList export function getMultiSelectedResources(resource: URI | object, listService: IListService, editorService: IEditorService): URI[] { const list = listService.lastFocusedList; - if (list && list.isDOMFocused()) { + if (list && list.getHTMLElement() === document.activeElement) { // Explorer if (list instanceof Tree) { const selection = list.getSelection().map((fs: ExplorerItem) => fs.resource); diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 7aefc5555f30c..47b470aeaa730 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -9,29 +9,47 @@ import { URI } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { Delayer } from 'vs/base/common/async'; import * as dom from 'vs/base/browser/dom'; -import { IAction, IActionItem } from 'vs/base/common/actions'; +import { IAction, IActionItem, Action } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Panel } from 'vs/workbench/browser/panel'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; -import { Marker, ResourceMarkers, RelatedInformation } from 'vs/workbench/parts/markers/electron-browser/markersModel'; -import { Controller } from 'vs/workbench/parts/markers/electron-browser/markersTreeController'; +import { Marker, ResourceMarkers, RelatedInformation, MarkersModel } from 'vs/workbench/parts/markers/electron-browser/markersModel'; import * as Viewer from 'vs/workbench/parts/markers/electron-browser/markersTreeViewer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CollapseAllAction, MarkersFilterActionItem, MarkersFilterAction, QuickFixAction, QuickFixActionItem, IMarkersFilterActionChangeEvent } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; +import { MarkersFilterActionItem, MarkersFilterAction, QuickFixAction, QuickFixActionItem, IMarkersFilterActionChangeEvent } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { debounceEvent } from 'vs/base/common/event'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService'; import { IMarkersWorkbenchService } from 'vs/workbench/parts/markers/electron-browser/markers'; -import { SimpleFileResourceDragAndDrop } from 'vs/workbench/browser/dnd'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { Scope } from 'vs/workbench/common/memento'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; +import { Iterator } from 'vs/base/common/iterator'; +import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; + +type TreeElement = ResourceMarkers | Marker | RelatedInformation; + +function createModelIterator(model: MarkersModel): Iterator> { + const resourcesIt = Iterator.fromArray(model.resourceMarkers); + + return Iterator.map(resourcesIt, m => { + const markersIt = Iterator.fromArray(m.markers); + + const children = Iterator.map(markersIt, m => { + const relatedInformationIt = Iterator.from(m.relatedInformation); + const children = Iterator.map(relatedInformationIt, r => ({ element: r })); + + return { element: m, children }; + }); + + return { element: m, children }; + }); +} export class MarkersPanel extends Panel { @@ -40,7 +58,7 @@ export class MarkersPanel extends Panel { private lastSelectedRelativeTop: number = 0; private currentActiveResource: URI = null; - private tree: WorkbenchTree; + private tree: ObjectTree; private rangeHighlightDecorations: RangeHighlightDecorations; private actions: IAction[]; @@ -93,7 +111,9 @@ export class MarkersPanel extends Panel { this.onDidFocus(() => this.panelFoucusContextKey.set(true)); this.onDidBlur(() => this.panelFoucusContextKey.set(false)); - return this.render(); + this.render(); + + return Promise.resolve(null); } public getTitle(): string { @@ -102,18 +122,18 @@ export class MarkersPanel extends Panel { public layout(dimension: dom.Dimension): void { this.treeContainer.style.height = `${dimension.height}px`; - this.tree.layout(dimension.height, dimension.width); + this.tree.layout(dimension.height); if (this.filterInputActionItem) { this.filterInputActionItem.toggleLayout(dimension.width < 1200); } } public focus(): void { - if (this.tree.isDOMFocused()) { + if (this.tree.getHTMLElement() === document.activeElement) { return; } - this.tree.domFocus(); + this.tree.getHTMLElement().focus(); // TODO@joao // if (this.markersWorkbenchService.markersModel.hasFilteredResources()) { @@ -188,7 +208,7 @@ export class MarkersPanel extends Panel { dom.toggleClass(this.treeContainer, 'hidden', false/* !this.markersWorkbenchService.markersModel.hasFilteredResources() */); this.renderMessage(); // if (this.markersWorkbenchService.markersModel.hasFilteredResources()) { - return this.tree.refresh(); + // return this.tree.refresh(); // } } return TPromise.as(null); @@ -209,42 +229,65 @@ export class MarkersPanel extends Panel { this.ariaLabelElement.setAttribute('aria-live', 'polite'); } + // TODO@joao private createTree(parent: HTMLElement): void { this.treeContainer = dom.append(parent, dom.$('.tree-container.show-file-icons')); - const renderer = this.instantiationService.createInstance(Viewer.Renderer, (action) => this.getActionItem(action)); - const dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, obj => obj instanceof ResourceMarkers ? obj.resource : void 0); - const controller = this.instantiationService.createInstance(Controller, () => this.focusFilter()); - this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { - dataSource: new Viewer.DataSource(), - renderer, - controller, - accessibilityProvider: this.instantiationService.createInstance(Viewer.MarkersTreeAccessibilityProvider), - dnd - }, { - twistiePixels: 20, - ariaLabel: Messages.MARKERS_PANEL_ARIA_LABEL_PROBLEMS_TREE - }); - - const markerFocusContextKey = Constants.MarkerFocusContextKey.bindTo(this.tree.contextKeyService); - const relatedInformationFocusContextKey = Constants.RelatedInformationFocusContextKey.bindTo(this.tree.contextKeyService); - this._register(this.tree.onDidChangeFocus((e: { focus: any }) => { - markerFocusContextKey.set(e.focus instanceof Marker); - relatedInformationFocusContextKey.set(e.focus instanceof RelatedInformation); - })); - const focusTracker = this._register(dom.trackFocus(this.tree.getHTMLElement())); - this._register(focusTracker.onDidBlur(() => { - markerFocusContextKey.set(false); - relatedInformationFocusContextKey.set(false); - })); - - const markersNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); - this._register(debounceEvent(markersNavigator.openResource, (last, event) => event, 75, true)(options => { - this.openFileAtElement(options.element, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned); - })); + // const dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, obj => obj instanceof ResourceMarkers ? obj.resource : void 0); + // const controller = this.instantiationService.createInstance(Controller, () => this.focusFilter()); + + const virtualDelegate = new Viewer.VirtualDelegate(); + const renderers = [ + this.instantiationService.createInstance(Viewer.FileResourceMarkersRenderer), + this.instantiationService.createInstance(Viewer.ResourceMarkersRenderer), + this.instantiationService.createInstance(Viewer.MarkerRenderer, a => this.getActionItem(a)), + this.instantiationService.createInstance(Viewer.RelatedInformationRenderer) + ]; + + this.tree = new ObjectTree( + this.treeContainer, + virtualDelegate, + renderers + ); + + // this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { + // dataSource: new Viewer.DataSource(), + // renderer, + // controller, + // accessibilityProvider: this.instantiationService.createInstance(Viewer.MarkersTreeAccessibilityProvider), + // dnd + // }, { + // twistiePixels: 20, + // ariaLabel: Messages.MARKERS_PANEL_ARIA_LABEL_PROBLEMS_TREE + // }); + + // const markerFocusContextKey = Constants.MarkerFocusContextKey.bindTo(this.tree.contextKeyService); + // const relatedInformationFocusContextKey = Constants.RelatedInformationFocusContextKey.bindTo(this.tree.contextKeyService); + // this._register(this.tree.onDidChangeFocus(elements => { + // markerFocusContextKey.set(elements.some(e => e instanceof Marker)); + // relatedInformationFocusContextKey.set(elements.some(e => e instanceof RelatedInformation)); + // })); + // const focusTracker = this._register(dom.trackFocus(this.tree.getHTMLElement())); + // this._register(focusTracker.onDidBlur(() => { + // markerFocusContextKey.set(false); + // relatedInformationFocusContextKey.set(false); + // })); + + // const markersNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); + // this._register(debounceEvent(markersNavigator.openResource, (last, event) => event, 75, true)(options => { + // this.openFileAtElement(options.element, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned); + // })); } + // TODO@joao private createActions(): void { - this.collapseAllAction = this.instantiationService.createInstance(CollapseAllAction, this.tree, true); + this.collapseAllAction = new Action('vs.tree.collapse', localize('collapse', "Collapse"), 'monaco-tree-action collapse-all', true, async () => { + // this.tree.collapseAll(); + this.tree.setSelection([]); + this.tree.setFocus([]); + this.tree.getHTMLElement().focus(); + this.tree.focusFirst(); + }); + this.filterAction = this.instantiationService.createInstance(MarkersFilterAction, { filterText: this.panelSettings['filter'] || '', filterHistory: this.panelSettings['filterHistory'] || [], useFilesExclude: !!this.panelSettings['useFilesExclude'] }); this.actions = [this.filterAction, this.collapseAllAction]; } @@ -302,9 +345,9 @@ export class MarkersPanel extends Panel { } // TODO@joao - private async render(): Promise { + private render(): void { dom.toggleClass(this.treeContainer, 'hidden', false/* !this.markersWorkbenchService.markersModel.hasFilteredResources() */); - await this.tree.setInput(this.markersWorkbenchService.markersModel); + this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); this.renderMessage(); } @@ -387,17 +430,16 @@ export class MarkersPanel extends Panel { if (this.tree.isExpanded(currentActiveResource) && this.hasSelectedMarkerFor(currentActiveResource)) { this.tree.reveal(this.tree.getSelection()[0], this.lastSelectedRelativeTop); if (focus) { - this.tree.setFocus(this.tree.getSelection()[0]); + this.tree.setFocus(this.tree.getSelection()); } } else { - this.tree.expand(currentActiveResource) - .then(() => this.tree.reveal(currentActiveResource, 0)) - .then(() => { - if (focus) { - this.tree.setFocus(currentActiveResource); - this.tree.setSelection([currentActiveResource]); - } - }); + this.tree.expand(currentActiveResource); + this.tree.reveal(currentActiveResource, 0); + + if (focus) { + this.tree.setFocus([currentActiveResource]); + this.tree.setSelection([currentActiveResource]); + } } } else if (focus) { this.tree.setSelection([]); @@ -423,21 +465,29 @@ export class MarkersPanel extends Panel { private updateRangeHighlights() { this.rangeHighlightDecorations.removeHighlightRange(); - if (this.tree.isDOMFocused()) { + if (this.tree.getHTMLElement() === document.activeElement) { this.highlightCurrentSelectedMarkerRange(); } } private highlightCurrentSelectedMarkerRange() { - let selections = this.tree.getSelection(); - if (selections && selections.length === 1 && selections[0] instanceof Marker) { - const marker: Marker = selections[0]; - this.rangeHighlightDecorations.highlightRange(marker); + const selections = this.tree.getSelection(); + + if (selections.length !== 1) { + return; } + + const selection = selections[0]; + + if (!(selection instanceof Marker)) { + return; + } + + this.rangeHighlightDecorations.highlightRange(selection); } - public getFocusElement(): ResourceMarkers | Marker { - return this.tree.getFocus(); + public getFocusElement(): ResourceMarkers | Marker | RelatedInformation { + return this.tree.getFocus()[0]; } public getActionItem(action: IAction): IActionItem { diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts index 6245d0fd15c72..43aa396694c93 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts @@ -17,8 +17,6 @@ import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { CollapseAllAction as TreeCollapseAction } from 'vs/base/parts/tree/browser/treeDefaults'; -import * as Tree from 'vs/base/parts/tree/browser/tree'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachStylerCallback, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; import { IMarkersWorkbenchService } from 'vs/workbench/parts/markers/electron-browser/markers'; @@ -74,13 +72,6 @@ export class ShowProblemsPanelAction extends Action { } } -export class CollapseAllAction extends TreeCollapseAction { - - constructor(viewer: Tree.ITree, enabled: boolean) { - super(viewer, enabled); - } -} - export interface IMarkersFilterActionChangeEvent extends IActionChangeEvent { filterText?: boolean; useFilesExclude?: boolean; diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index a139ae134516e..6ac37d3586c78 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -7,7 +7,7 @@ import { TPromise, Promise } from 'vs/base/common/winjs.base'; import * as dom from 'vs/base/browser/dom'; import * as network from 'vs/base/common/network'; import * as paths from 'vs/base/common/paths'; -import { IDataSource, ITree, IRenderer, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree'; +import { IDataSource, ITree, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { FileLabel, ResourceLabel } from 'vs/workbench/browser/labels'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; @@ -22,6 +22,8 @@ import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/act import { QuickFixAction } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname } from 'vs/base/common/resources'; +import { IVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ITreeRenderer } from 'vs/base/browser/ui/tree/abstractTree'; interface IResourceMarkersTemplateData { resourceLabel: ResourceLabel; @@ -93,61 +95,72 @@ export class DataSource implements IDataSource { } } -export class Renderer implements IRenderer { - - private static readonly RESOURCE_MARKERS_TEMPLATE_ID = 'resource-markers-template'; - private static readonly FILE_RESOURCE_MARKERS_TEMPLATE_ID = 'file-resource-markers-template'; - private static readonly MARKER_TEMPLATE_ID = 'marker-template'; - private static readonly RELATED_INFO_TEMPLATE_ID = 'related-info-template'; +export class MarkersTreeAccessibilityProvider implements IAccessibilityProvider { constructor( - private actionItemProvider: IActionItemProvider, - @IInstantiationService private instantiationService: IInstantiationService, - @IThemeService private themeService: IThemeService, - @ILabelService private labelService: ILabelService + @ILabelService private labelServie: ILabelService ) { } - public getHeight(tree: ITree, element: any): number { - return 22; - } - - public getTemplateId(tree: ITree, element: any): string { + // TODO@joao + public getAriaLabel(tree: ITree, element: any): string { if (element instanceof ResourceMarkers) { - if ((element).resource.scheme === network.Schemas.file || (element).resource.scheme === network.Schemas.untitled) { - return Renderer.FILE_RESOURCE_MARKERS_TEMPLATE_ID; - } else { - return Renderer.RESOURCE_MARKERS_TEMPLATE_ID; - } + const path = this.labelServie.getUriLabel(element.resource, { relative: true }) || element.resource.fsPath; + return Messages.MARKERS_TREE_ARIA_LABEL_RESOURCE(element.markers.length/* element.filteredCount */, element.name, paths.dirname(path)); } if (element instanceof Marker) { - return Renderer.MARKER_TEMPLATE_ID; + return Messages.MARKERS_TREE_ARIA_LABEL_MARKER(element); } if (element instanceof RelatedInformation) { - return Renderer.RELATED_INFO_TEMPLATE_ID; + return Messages.MARKERS_TREE_ARIA_LABEL_RELATED_INFORMATION(element.raw); } - return ''; + return null; } +} - public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any { - dom.addClass(container, 'markers-panel-tree-entry'); - switch (templateId) { - case Renderer.FILE_RESOURCE_MARKERS_TEMPLATE_ID: - return this.renderFileResourceMarkersTemplate(container); - case Renderer.RESOURCE_MARKERS_TEMPLATE_ID: - return this.renderResourceMarkersTemplate(container); - case Renderer.MARKER_TEMPLATE_ID: - return this.renderMarkerTemplate(container); - case Renderer.RELATED_INFO_TEMPLATE_ID: - return this.renderRelatedInfoTemplate(container); +const enum TemplateId { + FileResourceMarkers = 'frm', + ResourceMarkers = 'rm', + Marker = 'm', + RelatedInformation = 'ri' +} + +export class VirtualDelegate implements IVirtualDelegate { + + getHeight(): number { + return 22; + } + + getTemplateId(element: ResourceMarkers | Marker | RelatedInformation): string { + if (element instanceof ResourceMarkers) { + if ((element).resource.scheme === network.Schemas.file || (element).resource.scheme === network.Schemas.untitled) { + return TemplateId.FileResourceMarkers; + } else { + return TemplateId.ResourceMarkers; + } + } else if (element instanceof Marker) { + return TemplateId.Marker; + } else { + return TemplateId.RelatedInformation; } } +} + +export class ResourceMarkersRenderer implements ITreeRenderer { - private renderFileResourceMarkersTemplate(container: HTMLElement): IResourceMarkersTemplateData { + constructor( + @IInstantiationService protected instantiationService: IInstantiationService, + @IThemeService private themeService: IThemeService, + @ILabelService private labelService: ILabelService + ) { } + + templateId = TemplateId.ResourceMarkers; + + renderTemplate(container: HTMLElement): IResourceMarkersTemplateData { const data = Object.create(null); const resourceLabelContainer = dom.append(container, dom.$('.resource-label-container')); - data.resourceLabel = this.instantiationService.createInstance(FileLabel, resourceLabelContainer, { supportHighlights: true }); + data.resourceLabel = this.createResourceLabel(resourceLabelContainer); const badgeWrapper = dom.append(container, dom.$('.count-badge-wrapper')); data.count = new CountBadge(badgeWrapper); @@ -156,37 +169,48 @@ export class Renderer implements IRenderer { return data; } - private renderResourceMarkersTemplate(container: HTMLElement): IResourceMarkersTemplateData { - const data = Object.create(null); + // TODO@joao + renderElement(element: ResourceMarkers, _: number, templateData: IResourceMarkersTemplateData): void { + if (templateData.resourceLabel instanceof FileLabel) { + templateData.resourceLabel.setFile(element.resource/* , { matches: element.uriMatches } */); + } else { + templateData.resourceLabel.setLabel({ name: element.name, description: this.labelService.getUriLabel(dirname(element.resource), { relative: true }), resource: element.resource }/* , { matches: element.uriMatches } */); + } - const resourceLabelContainer = dom.append(container, dom.$('.resource-label-container')); - data.resourceLabel = this.instantiationService.createInstance(ResourceLabel, resourceLabelContainer, { supportHighlights: true }); + templateData.count.setCount(element.markers.length/* filteredCount */); + } - const badgeWrapper = dom.append(container, dom.$('.count-badge-wrapper')); - data.count = new CountBadge(badgeWrapper); - data.styler = attachBadgeStyler(data.count, this.themeService); + disposeElement(): void { + // noop + } - return data; + disposeTemplate(templateData: IResourceMarkersTemplateData): void { + templateData.resourceLabel.dispose(); + templateData.styler.dispose(); } - private renderRelatedInfoTemplate(container: HTMLElement): IRelatedInformationTemplateData { - const data: IRelatedInformationTemplateData = Object.create(null); + protected createResourceLabel(container: HTMLElement): ResourceLabel { + return this.instantiationService.createInstance(ResourceLabel, container, { supportHighlights: true }); + } +} - dom.append(container, dom.$('.actions')); - dom.append(container, dom.$('.icon')); +export class FileResourceMarkersRenderer extends ResourceMarkersRenderer { - data.resourceLabel = new HighlightedLabel(dom.append(container, dom.$('.related-info-resource'))); - data.lnCol = dom.append(container, dom.$('span.marker-line')); + protected createResourceLabel(container: HTMLElement): ResourceLabel { + return this.instantiationService.createInstance(FileLabel, container, { supportHighlights: true }); + } +} - const separator = dom.append(container, dom.$('span.related-info-resource-separator')); - separator.textContent = ':'; - separator.style.paddingRight = '4px'; +export class MarkerRenderer implements ITreeRenderer { - data.description = new HighlightedLabel(dom.append(container, dom.$('.marker-description'))); - return data; - } + constructor( + private actionItemProvider: IActionItemProvider, + @IInstantiationService protected instantiationService: IInstantiationService + ) { } - private renderMarkerTemplate(container: HTMLElement): IMarkerTemplateData { + templateId = TemplateId.Marker; + + renderTemplate(container: HTMLElement): IMarkerTemplateData { const data: IMarkerTemplateData = Object.create(null); const actionsContainer = dom.append(container, dom.$('.actions')); data.actionBar = new ActionBar(actionsContainer, { actionItemProvider: this.actionItemProvider }); @@ -198,33 +222,11 @@ export class Renderer implements IRenderer { return data; } - public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void { - switch (templateId) { - case Renderer.FILE_RESOURCE_MARKERS_TEMPLATE_ID: - case Renderer.RESOURCE_MARKERS_TEMPLATE_ID: - return this.renderResourceMarkersElement(tree, element, templateData); - case Renderer.MARKER_TEMPLATE_ID: - return this.renderMarkerElement(tree, (element), templateData); - case Renderer.RELATED_INFO_TEMPLATE_ID: - return this.renderRelatedInfoElement(tree, element, templateData); - } - } - // TODO@joao - private renderResourceMarkersElement(tree: ITree, element: ResourceMarkers, templateData: IResourceMarkersTemplateData) { - if (templateData.resourceLabel instanceof FileLabel) { - templateData.resourceLabel.setFile(element.resource/* , { matches: element.uriMatches } */); - } else { - templateData.resourceLabel.setLabel({ name: element.name, description: this.labelService.getUriLabel(dirname(element.resource), { relative: true }), resource: element.resource }/* , { matches: element.uriMatches } */); - } - (templateData).count.setCount(element.markers.length/* filteredCount */); - } - - // TODO@joao - private renderMarkerElement(tree: ITree, element: Marker, templateData: IMarkerTemplateData) { + renderElement(element: Marker, _: number, templateData: IMarkerTemplateData): void { let marker = element.marker; - templateData.icon.className = 'icon ' + Renderer.iconClassNameFor(marker); + templateData.icon.className = 'icon ' + MarkerRenderer.iconClassNameFor(marker); templateData.source.set(marker.source/* , element.sourceMatches */); dom.toggleClass(templateData.source.element, 'marker-source', !!marker.source); @@ -240,16 +242,16 @@ export class Renderer implements IRenderer { templateData.code.set(marker.code || ''/* , element.codeMatches */); templateData.lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(marker.startLineNumber, marker.startColumn); + } + disposeElement(): void { + // noop } - // TODO@joao - private renderRelatedInfoElement(tree: ITree, element: RelatedInformation, templateData: IRelatedInformationTemplateData) { - templateData.resourceLabel.set(paths.basename(element.raw.resource.fsPath)/* , element.uriMatches */); - templateData.resourceLabel.element.title = this.labelService.getUriLabel(element.raw.resource, { relative: true }); - templateData.lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(element.raw.startLineNumber, element.raw.startColumn); - templateData.description.set(element.raw.message/* , element.messageMatches */); - templateData.description.element.title = element.raw.message; + disposeTemplate(templateData: IMarkerTemplateData): void { + templateData.description.dispose(); + templateData.source.dispose(); + templateData.actionBar.dispose(); } private static iconClassNameFor(element: IMarker): string { @@ -265,41 +267,48 @@ export class Renderer implements IRenderer { } return ''; } - - public disposeTemplate(tree: ITree, templateId: string, templateData: any): void { - if (templateId === Renderer.RESOURCE_MARKERS_TEMPLATE_ID || templateId === Renderer.FILE_RESOURCE_MARKERS_TEMPLATE_ID) { - (templateData).resourceLabel.dispose(); - (templateData).styler.dispose(); - } else if (templateId === Renderer.MARKER_TEMPLATE_ID) { - (templateData).description.dispose(); - (templateData).source.dispose(); - (templateData).actionBar.dispose(); - } else if (templateId === Renderer.RELATED_INFO_TEMPLATE_ID) { - (templateData).description.dispose(); - (templateData).resourceLabel.dispose(); - } - } } -export class MarkersTreeAccessibilityProvider implements IAccessibilityProvider { +export class RelatedInformationRenderer implements ITreeRenderer { constructor( - @ILabelService private labelServie: ILabelService - ) { + @ILabelService private labelService: ILabelService + ) { } + + templateId = TemplateId.Marker; + + renderTemplate(container: HTMLElement): IRelatedInformationTemplateData { + const data: IRelatedInformationTemplateData = Object.create(null); + + dom.append(container, dom.$('.actions')); + dom.append(container, dom.$('.icon')); + + data.resourceLabel = new HighlightedLabel(dom.append(container, dom.$('.related-info-resource'))); + data.lnCol = dom.append(container, dom.$('span.marker-line')); + + const separator = dom.append(container, dom.$('span.related-info-resource-separator')); + separator.textContent = ':'; + separator.style.paddingRight = '4px'; + + data.description = new HighlightedLabel(dom.append(container, dom.$('.marker-description'))); + return data; } // TODO@joao - public getAriaLabel(tree: ITree, element: any): string { - if (element instanceof ResourceMarkers) { - const path = this.labelServie.getUriLabel(element.resource, { relative: true }) || element.resource.fsPath; - return Messages.MARKERS_TREE_ARIA_LABEL_RESOURCE(element.markers.length/* element.filteredCount */, element.name, paths.dirname(path)); - } - if (element instanceof Marker) { - return Messages.MARKERS_TREE_ARIA_LABEL_MARKER(element); - } - if (element instanceof RelatedInformation) { - return Messages.MARKERS_TREE_ARIA_LABEL_RELATED_INFORMATION(element.raw); - } - return null; + renderElement(element: RelatedInformation, _: number, templateData: IRelatedInformationTemplateData): void { + templateData.resourceLabel.set(paths.basename(element.raw.resource.fsPath)/* , element.uriMatches */); + templateData.resourceLabel.element.title = this.labelService.getUriLabel(element.raw.resource, { relative: true }); + templateData.lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(element.raw.startLineNumber, element.raw.startColumn); + templateData.description.set(element.raw.message/* , element.messageMatches */); + templateData.description.element.title = element.raw.message; } -} + + disposeElement(): void { + // noop + } + + disposeTemplate(templateData: IRelatedInformationTemplateData): void { + templateData.description.dispose(); + templateData.resourceLabel.dispose(); + } +} \ No newline at end of file From fd4091b8033a82d5832a71ed5b76f443f2887d62 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 8 Oct 2018 10:12:27 +0100 Subject: [PATCH 06/35] problems: first render success! --- .../parts/markers/electron-browser/markers.ts | 109 ++++++++---------- .../markers/electron-browser/markersModel.ts | 6 + .../markers/electron-browser/markersPanel.ts | 49 ++++---- .../electron-browser/markersPanelActions.ts | 2 +- .../electron-browser/markersTreeViewer.ts | 4 +- 5 files changed, 81 insertions(+), 89 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markers.ts b/src/vs/workbench/parts/markers/electron-browser/markers.ts index ed1a267278977..b851088dd6809 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markers.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markers.ts @@ -11,13 +11,7 @@ import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/co import { localize } from 'vs/nls'; import Constants from './constants'; import { URI } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { deepClone, mixin } from 'vs/base/common/objects'; -import { IExpression, getEmptyExpression } from 'vs/base/common/glob'; -import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { join, isAbsolute } from 'vs/base/common/paths'; import { groupBy } from 'vs/base/common/arrays'; export const IMarkersWorkbenchService = createDecorator('markersWorkbenchService'); @@ -30,10 +24,9 @@ export interface IFilter { export interface IMarkersWorkbenchService { _serviceBrand: any; - readonly onDidChange: Event; readonly markersModel: MarkersModel; - filter(filter: IFilter): void; + // filter(filter: IFilter): void; } export class MarkersWorkbenchService extends Disposable implements IMarkersWorkbenchService { @@ -41,15 +34,12 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb readonly markersModel: MarkersModel; - private readonly _onDidChange: Emitter = this._register(new Emitter()); - readonly onDidChange: Event = this._onDidChange.event; - - private useFilesExclude: boolean = false; + // private useFilesExclude: boolean = false; constructor( @IMarkerService private markerService: IMarkerService, - @IConfigurationService private configurationService: IConfigurationService, - @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + // @IConfigurationService private configurationService: IConfigurationService, + // @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, @IActivityService private activityService: IActivityService, @IInstantiationService instantiationService: IInstantiationService ) { @@ -69,10 +59,10 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb // })); } - filter(filter: IFilter): void { - this.useFilesExclude = filter.useFilesExclude; - this.doFilter(filter.filterText, this.getExcludeExpression()); - } + // filter(filter: IFilter): void { + // this.useFilesExclude = filter.useFilesExclude; + // this.doFilter(filter.filterText, this.getExcludeExpression()); + // } private onMarkerChanged(resources: URI[]): void { for (const resource of resources) { @@ -80,34 +70,33 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb } this.refreshBadge(); - this._onDidChange.fire(resources); } private readMarkers(resource?: URI): IMarker[] { return this.markerService.read({ resource, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }); } - private getExcludeExpression(): IExpression { - if (this.useFilesExclude) { - const workspaceFolders = this.workspaceContextService.getWorkspace().folders; - if (workspaceFolders.length) { - const result = getEmptyExpression(); - for (const workspaceFolder of workspaceFolders) { - mixin(result, this.getExcludesForFolder(workspaceFolder)); - } - return result; - } else { - return this.getFilesExclude(); - } - } - return {}; - } - - private doFilter(filterText: string, filesExclude: IExpression): void { - console.warn('marker filter not implemented'); - this.refreshBadge(); - this._onDidChange.fire([]); - } + // private getExcludeExpression(): IExpression { + // if (this.useFilesExclude) { + // const workspaceFolders = this.workspaceContextService.getWorkspace().folders; + // if (workspaceFolders.length) { + // const result = getEmptyExpression(); + // for (const workspaceFolder of workspaceFolders) { + // mixin(result, this.getExcludesForFolder(workspaceFolder)); + // } + // return result; + // } else { + // return this.getFilesExclude(); + // } + // } + // return {}; + // } + + // private doFilter(filterText: string, filesExclude: IExpression): void { + // console.warn('marker filter not implemented'); + // this.refreshBadge(); + // this._onDidChange.fire([]); + // } private refreshBadge(): void { const { total } = this.markersModel.stats(); @@ -115,26 +104,26 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb this.activityService.showActivity(Constants.MARKERS_PANEL_ID, new NumberBadge(total, () => message)); } - private getExcludesForFolder(workspaceFolder: IWorkspaceFolder): IExpression { - const expression = this.getFilesExclude(workspaceFolder.uri); - return this.getAbsoluteExpression(expression, workspaceFolder.uri.fsPath); - } - - private getFilesExclude(resource?: URI): IExpression { - return deepClone(this.configurationService.getValue('files.exclude', { resource })) || {}; - } - - private getAbsoluteExpression(expr: IExpression, root: string): IExpression { - return Object.keys(expr) - .reduce((absExpr: IExpression, key: string) => { - if (expr[key] && !isAbsolute(key)) { - const absPattern = join(root, key); - absExpr[absPattern] = expr[key]; - } - - return absExpr; - }, Object.create(null)); - } + // private getExcludesForFolder(workspaceFolder: IWorkspaceFolder): IExpression { + // const expression = this.getFilesExclude(workspaceFolder.uri); + // return this.getAbsoluteExpression(expression, workspaceFolder.uri.fsPath); + // } + + // private getFilesExclude(resource?: URI): IExpression { + // return deepClone(this.configurationService.getValue('files.exclude', { resource })) || {}; + // } + + // private getAbsoluteExpression(expr: IExpression, root: string): IExpression { + // return Object.keys(expr) + // .reduce((absExpr: IExpression, key: string) => { + // if (expr[key] && !isAbsolute(key)) { + // const absPattern = join(root, key); + // absExpr[absPattern] = expr[key]; + // } + + // return absExpr; + // }, Object.create(null)); + // } } registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService); \ No newline at end of file diff --git a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts index a4956c6e463dc..8dfc797ced8f6 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts @@ -10,6 +10,7 @@ import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/marker import { groupBy, flatten, isFalsyOrEmpty } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/map'; import { memoize } from 'vs/base/common/decorators'; +import { Emitter, Event } from 'vs/base/common/event'; function compareUris(a: URI, b: URI) { const astr = a.toString(); @@ -137,6 +138,9 @@ export class MarkersModel { private cachedSortedResources: ResourceMarkers[] | undefined = undefined; + private readonly _onDidChange: Emitter = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + get resourceMarkers(): ResourceMarkers[] { if (!this.cachedSortedResources) { this.cachedSortedResources = values(this.resourcesByUri).sort(compareResourceMarkers); @@ -188,6 +192,7 @@ export class MarkersModel { } this.cachedSortedResources = undefined; + this._onDidChange.fire(resource); } // TODO@joao @@ -220,6 +225,7 @@ export class MarkersModel { } dispose(): void { + this._onDidChange.dispose(); this.resourcesByUri.clear(); } } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 47b470aeaa730..4b92a012e96a4 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -7,7 +7,6 @@ import 'vs/css!./media/markers'; import { URI } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { Delayer } from 'vs/base/common/async'; import * as dom from 'vs/base/browser/dom'; import { IAction, IActionItem, Action } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -17,7 +16,7 @@ import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; import { Marker, ResourceMarkers, RelatedInformation, MarkersModel } from 'vs/workbench/parts/markers/electron-browser/markersModel'; import * as Viewer from 'vs/workbench/parts/markers/electron-browser/markersTreeViewer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersFilterActionItem, MarkersFilterAction, QuickFixAction, QuickFixActionItem, IMarkersFilterActionChangeEvent } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; +import { MarkersFilterActionItem, MarkersFilterAction, QuickFixAction, QuickFixActionItem } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; @@ -31,6 +30,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; +import { debounceEvent } from 'vs/base/common/event'; type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -53,8 +53,6 @@ function createModelIterator(model: MarkersModel): Iterator; - private lastSelectedRelativeTop: number = 0; private currentActiveResource: URI = null; @@ -86,7 +84,6 @@ export class MarkersPanel extends Panel { ) { super(Constants.MARKERS_PANEL_ID, telemetryService, themeService); this.panelFoucusContextKey = Constants.MarkerPanelFocusContextKey.bindTo(contextKeyService); - this.delayedRefresh = new Delayer(500); this.panelSettings = this.getMemento(storageService, Scope.WORKSPACE); this.setCurrentActiveEditor(); } @@ -106,7 +103,7 @@ export class MarkersPanel extends Panel { this.createActions(); this.createListeners(); - this.updateFilter(); + // this.updateFilter(); this.onDidFocus(() => this.panelFoucusContextKey.set(true)); this.onDidBlur(() => this.panelFoucusContextKey.set(false)); @@ -208,15 +205,15 @@ export class MarkersPanel extends Panel { dom.toggleClass(this.treeContainer, 'hidden', false/* !this.markersWorkbenchService.markersModel.hasFilteredResources() */); this.renderMessage(); // if (this.markersWorkbenchService.markersModel.hasFilteredResources()) { - // return this.tree.refresh(); + this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); // } } return TPromise.as(null); } - private updateFilter() { - this.markersWorkbenchService.filter({ filterText: this.filterAction.filterText, useFilesExclude: this.filterAction.useFilesExclude }); - } + // private updateFilter() { + // this.markersWorkbenchService.filter({ filterText: this.filterAction.filterText, useFilesExclude: this.filterAction.useFilesExclude }); + // } private createMessageBox(parent: HTMLElement): void { this.messageBoxContainer = dom.append(parent, dom.$('.message-box-container')); @@ -293,27 +290,27 @@ export class MarkersPanel extends Panel { } private createListeners(): void { - this._register(this.markersWorkbenchService.onDidChange(resources => this.onDidChange(resources))); + const onModelChange = debounceEvent(this.markersWorkbenchService.markersModel.onDidChange, (uris, uri) => { if (!uris) { uris = []; } uris.push(uri); return uris; }, 0); + + this._register(onModelChange(this.onDidChangeModel, this)); this._register(this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this)); this._register(this.tree.onDidChangeSelection(() => this.onSelected())); - this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { - if (event.filterText || event.useFilesExclude) { - this.updateFilter(); - } - })); + // this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { + // if (event.filterText || event.useFilesExclude) { + // this.updateFilter(); + // } + // })); this.actions.forEach(a => this._register(a)); } - private onDidChange(resources: URI[]) { + private onDidChangeModel(resources: URI[]) { this.currentResourceGotAddedToMarkersData = this.currentResourceGotAddedToMarkersData || this.isCurrentResourceGotAddedToMarkersData(resources); - this.delayedRefresh.trigger(() => { - this.refreshPanel(); - this.updateRangeHighlights(); - if (this.currentResourceGotAddedToMarkersData) { - this.autoReveal(); - this.currentResourceGotAddedToMarkersData = false; - } - }); + this.refreshPanel(); + this.updateRangeHighlights(); + if (this.currentResourceGotAddedToMarkersData) { + this.autoReveal(); + this.currentResourceGotAddedToMarkersData = false; + } } private isCurrentResourceGotAddedToMarkersData(changedResources: URI[]) { @@ -512,8 +509,6 @@ export class MarkersPanel extends Panel { public dispose(): void { super.dispose(); - - this.delayedRefresh.cancel(); this.tree.dispose(); } } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts index 43aa396694c93..b89e12a0f222b 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts @@ -210,7 +210,7 @@ export class MarkersFilterActionItem extends BaseActionItem { this.filterBadge.style.borderColor = border; })); this.updateBadge(); - this._register(this.markersWorkbenchService.onDidChange(() => this.updateBadge())); + this._register(this.markersWorkbenchService.markersModel.onDidChange(() => this.updateBadge())); } private createFilesExcludeCheckbox(container: HTMLElement): void { diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 6ac37d3586c78..3b9025155c341 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -196,6 +196,8 @@ export class ResourceMarkersRenderer implements ITreeRenderer Date: Mon, 8 Oct 2018 10:18:25 +0100 Subject: [PATCH 07/35] problems: proper rendering --- src/vs/base/browser/ui/tree/abstractTree.ts | 4 +- src/vs/base/browser/ui/tree/tree.css | 6 +-- .../electron-browser/media/markers.css | 54 +++++++++---------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index a953f25ff6cc9..21a7bbf3e4eb1 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -99,8 +99,8 @@ class TreeRenderer implements IRenderer { const el = append(container, $('.monaco-tl-row')); - const twistie = append(el, $('.tl-twistie')); - const contents = append(el, $('.tl-contents')); + const twistie = append(el, $('.monaco-tl-twistie')); + const contents = append(el, $('.monaco-tl-contents')); const templateData = this.renderer.renderTemplate(contents); return { twistie, templateData }; diff --git a/src/vs/base/browser/ui/tree/tree.css b/src/vs/base/browser/ui/tree/tree.css index de6c92a44a20d..24d3bd1f05c8a 100644 --- a/src/vs/base/browser/ui/tree/tree.css +++ b/src/vs/base/browser/ui/tree/tree.css @@ -9,12 +9,12 @@ align-items: center; } -.monaco-tl-row > .tl-twistie, -.monaco-tl-row > .tl-contents { +.monaco-tl-row > .monaco-tl-twistie, +.monaco-tl-row > .monaco-tl-contents { height: 100%; } -.monaco-tl-row > .tl-twistie { +.monaco-tl-row > .monaco-tl-twistie { font-size: 10px; text-align: right; padding-right: 10px; diff --git a/src/vs/workbench/parts/markers/electron-browser/media/markers.css b/src/vs/workbench/parts/markers/electron-browser/media/markers.css index 5ee9e566d0c19..6a47298c02649 100644 --- a/src/vs/workbench/parts/markers/electron-browser/media/markers.css +++ b/src/vs/workbench/parts/markers/electron-browser/media/markers.css @@ -90,58 +90,58 @@ visibility: hidden; } -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry { +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents { display: flex; line-height: 22px; } -.hc-black .markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry { +.hc-black .markers-panel .markers-panel-container .tree-container .monaco-tl-contents { line-height: 20px; } -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-stats { +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-stats { display: inline-block; margin-left: 10px; } -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .count-badge-wrapper { +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .count-badge-wrapper { margin-left: 10px; } -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-description { +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-description { margin-right: 5px; text-overflow: ellipsis; overflow: hidden; } -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-source, -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-code { +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-source, +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-code { margin-right: 5px; } -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-source:before, -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-code:before { +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-source:before, +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-code:before { content: '['; } -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-source:after, -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-code:after { +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-source:after, +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-code:after { content: ']'; } -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-source, -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .related-info-resource, -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .related-info-resource-separator, -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-line, -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .marker-code { +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-source, +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .related-info-resource, +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .related-info-resource-separator, +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-line, +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-code { opacity: 0.7; } -.markers-panel .markers-panel-container .tree-container .markers-panel-tree-entry .highlight { +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .highlight { font-weight: bold; } -.markers-panel .monaco-tree .markers-panel-tree-entry > .icon { +.markers-panel .monaco-tree .monaco-tl-contents > .icon { height: 22px; margin-right: 6px; flex: 0 0 16px; @@ -171,36 +171,36 @@ background: url('status-info-inverse.svg') center center no-repeat; } -.markers-panel .monaco-tree .markers-panel-tree-entry .actions .action-label.icon.markers-panel-action-quickfix { +.markers-panel .monaco-tree .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { background: url('lightbulb.svg') center/80% no-repeat; margin-right: 0px; } -.vs-dark .markers-panel .monaco-tree .markers-panel-tree-entry .actions .action-label.icon.markers-panel-action-quickfix { +.vs-dark .markers-panel .monaco-tree .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { background: url('lightbulb-dark.svg') center/80% no-repeat; } -.markers-panel .monaco-tree .monaco-tree-row .markers-panel-tree-entry > .actions { +.markers-panel .monaco-tree .monaco-tree-row .monaco-tl-contents > .actions { flex: 0 0 16px; } -.markers-panel .monaco-tree .monaco-tree-row .markers-panel-tree-entry > .actions .monaco-action-bar { +.markers-panel .monaco-tree .monaco-tree-row .monaco-tl-contents > .actions .monaco-action-bar { display: none; } -.markers-panel .monaco-tree .monaco-tree-row:hover .markers-panel-tree-entry > .actions .monaco-action-bar, -.markers-panel .monaco-tree .monaco-tree-row.selected .markers-panel-tree-entry > .actions .monaco-action-bar, -.markers-panel .monaco-tree .monaco-tree-row.focused .markers-panel-tree-entry > .actions .monaco-action-bar { +.markers-panel .monaco-tree .monaco-tree-row:hover .monaco-tl-contents > .actions .monaco-action-bar, +.markers-panel .monaco-tree .monaco-tree-row.selected .monaco-tl-contents > .actions .monaco-action-bar, +.markers-panel .monaco-tree .monaco-tree-row.focused .monaco-tl-contents > .actions .monaco-action-bar { display: block; } -.markers-panel .monaco-tree .markers-panel-tree-entry .actions .action-label { +.markers-panel .monaco-tree .monaco-tl-contents .actions .action-label { width: 16px; height: 100%; background-position: 50% 50%; background-repeat: no-repeat; } -.markers-panel .monaco-tree .markers-panel-tree-entry .actions .action-item.disabled { +.markers-panel .monaco-tree .monaco-tl-contents .actions .action-item.disabled { display: none; } \ No newline at end of file From b4681da86aeab043935e16cbbb3a89897e0c87ea Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 8 Oct 2018 11:19:23 +0100 Subject: [PATCH 08/35] wip: WorkbenchObjectTree --- src/vs/base/browser/ui/tree/abstractTree.ts | 41 +++- src/vs/base/browser/ui/tree/indexTreeModel.ts | 69 +++++- .../base/browser/ui/tree/objectTreeModel.ts | 15 ++ src/vs/base/browser/ui/tree/tree.ts | 4 + src/vs/platform/list/browser/listService.ts | 87 +++++++- src/vs/workbench/electron-browser/commands.ts | 208 +++++++++++++++--- 6 files changed, 378 insertions(+), 46 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 21a7bbf3e4eb1..2915ad1a38116 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -5,7 +5,7 @@ import 'vs/css!./tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IListOptions, List, IIdentityProvider, IMultipleSelectionController } from 'vs/base/browser/ui/list/listWidget'; +import { IListOptions, List, IIdentityProvider, IMultipleSelectionController, IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { IVirtualDelegate, IRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list'; import { append, $ } from 'vs/base/browser/dom'; import { Event, Relay, chain, mapEvent } from 'vs/base/common/event'; @@ -183,6 +183,10 @@ export abstract class AbstractTree implements IDisposable return mapEvent(this.view.onSelectionChange, e => e.elements.map(e => e.element)); } + get onDidFocus(): Event { return this.view.onDidFocus; } + get onDidBlur(): Event { return this.view.onDidBlur; } + get onDidDispose(): Event { return this.view.onDidDispose; } + constructor( container: HTMLElement, delegate: IVirtualDelegate, @@ -211,15 +215,41 @@ export abstract class AbstractTree implements IDisposable onKeyDown.filter(e => e.keyCode === KeyCode.Space).on(this.onSpace, this, this.disposables); } + // Widget + // TODO@joao rename to `get domElement` getHTMLElement(): HTMLElement { return this.view.getHTMLElement(); } + domFocus(): void { + this.view.domFocus(); + } + layout(height?: number): void { this.view.layout(height); } + style(styles: IListStyles): void { + this.view.style(styles); + } + + // Tree navigation + + getParentElement(ref: TRef | null = null): T | null { + return this.model.getParentElement(ref); + } + + getFirstElementChild(ref: TRef | null = null): T | null { + return this.model.getFirstElementChild(ref); + } + + getLastElementAncestor(ref: TRef | null = null): T | null { + return this.model.getLastElementAncestor(ref); + } + + // Tree + collapse(location: TRef): boolean { return this.model.setCollapsed(location, true); } @@ -268,11 +298,11 @@ export abstract class AbstractTree implements IDisposable } focusNext(n = 1, loop = false): void { - this.view.focusNext(); + this.view.focusNext(n, loop); } focusPrevious(n = 1, loop = false): void { - this.view.focusPrevious(); + this.view.focusPrevious(n, loop); } focusNextPage(): void { @@ -296,6 +326,11 @@ export abstract class AbstractTree implements IDisposable return nodes.map(n => n.element); } + open(elements: TRef[]): void { + const indexes = elements.map(e => this.model.getListIndex(e)); + this.view.open(indexes); + } + reveal(location: TRef, relativeTop?: number): void { const index = this.model.getListIndex(location); this.view.reveal(index, relativeTop); diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index b57ffece85459..c6b4304bf3239 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -74,7 +74,7 @@ export class IndexTreeModel implements ITreeModel[] = []; const nodesToInsertIterator = Iterator.map(Iterator.from(toInsert), el => this.createTreeNode(el, parentNode, revealed, treeListElementsToInsert, onDidCreateNode)); @@ -109,16 +109,16 @@ export class IndexTreeModel implements ITreeModel implements ITreeModel implements ITreeModel, listIndex: number, revealed: boolean } { - const { parentNode, listIndex, revealed } = this.findParentNode(location); + /** + * Cheaper version of findNode, which doesn't require list indices. + */ + private getNode(location: number[], node: IMutableTreeNode = this.root): IMutableTreeNode { + if (location.length === 0) { + return node; + } + + const [index, ...rest] = location; + + if (index < 0 || index > node.children.length) { + throw new Error('Invalid tree location'); + } + + return this.getNode(rest, node.children[index]); + } + + private getNodeWithListIndex(location: number[]): { node: IMutableTreeNode, listIndex: number, revealed: boolean } { + const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location); const index = location[location.length - 1]; if (index < 0 || index > parentNode.children.length) { @@ -351,7 +368,7 @@ export class IndexTreeModel implements ITreeModel = this.root, listIndex: number = 0, revealed = true): { parentNode: IMutableTreeNode; listIndex: number; revealed: boolean; } { + private getParentNodeWithListIndex(location: number[], node: IMutableTreeNode = this.root, listIndex: number = 0, revealed = true): { parentNode: IMutableTreeNode; listIndex: number; revealed: boolean; } { const [index, ...rest] = location; if (index < 0 || index > node.children.length) { @@ -369,7 +386,7 @@ export class IndexTreeModel implements ITreeModel implements ITreeModel): T | null { + if (node.children.length === 0) { + return node.element; + } + + return this._getLastElementAncestor(node.children[node.children.length - 1]); + } } \ No newline at end of file diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index 03e8c13110965..3eec9482540eb 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -45,6 +45,21 @@ export class ObjectTreeModel, TFilterData = void> imp return this.model.splice([...location, 0], Number.MAX_VALUE, children, onDidCreateNode, onDidDeleteNode); } + getParentElement(ref: T | null = null): T | null { + const location = this.getElementLocation(ref); + return this.model.getParentElement(location); + } + + getFirstElementChild(ref: T | null = null): T | null { + const location = this.getElementLocation(ref); + return this.model.getFirstElementChild(location); + } + + getLastElementAncestor(ref: T | null = null): T | null { + const location = this.getElementLocation(ref); + return this.model.getLastElementAncestor(location); + } + getListIndex(element: T): number { const location = this.getElementLocation(element); return this.model.getListIndex(location); diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 1db1b8671c907..f219267f1229b 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -54,4 +54,8 @@ export interface ITreeModel { getNodeLocation(node: ITreeNode): TRef; getParentNodeLocation(location: TRef): TRef | null; + + getParentElement(location: TRef): T | null; + getFirstElementChild(location: TRef): T | null; + getLastElementAncestor(location: TRef): T | null; } \ No newline at end of file diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 13880be7cf186..d276a9609c6ec 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -32,8 +32,10 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { attachInputBoxStyler, attachListStyler, computeStyles, defaultListStyles } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys'; +import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; +import { ITreeRenderer, ITreeOptions as ITreeOptions2 } from 'vs/base/browser/ui/tree/abstractTree'; -export type ListWidget = List | PagedList | ITree; +export type ListWidget = List | PagedList | ITree | ObjectTree; export const IListService = createDecorator('listService'); @@ -797,6 +799,89 @@ export class HighlightingWorkbenchTree extends WorkbenchTree { } } +export class WorkbenchObjectTree, TFilterData = void> extends ObjectTree { + + readonly contextKeyService: IContextKeyService; + + protected disposables: IDisposable[]; + + private hasSelectionOrFocus: IContextKey; + private hasDoubleSelection: IContextKey; + private hasMultiSelection: IContextKey; + + private _useAltAsMultipleSelectionModifier: boolean; + + constructor( + container: HTMLElement, + delegate: IVirtualDelegate, + renderers: ITreeRenderer[], + options: ITreeOptions2, + @IContextKeyService contextKeyService: IContextKeyService, + @IListService listService: IListService, + @IThemeService themeService: IThemeService, + @IConfigurationService private configurationService: IConfigurationService + ) { + super(container, delegate, renderers, { + keyboardSupport: false, + selectOnMouseDown: true, + styleController: new DefaultStyleController(getSharedListStyleSheet()), + ...computeStyles(themeService.getTheme(), defaultListStyles), + ...handleListControllers(options, configurationService) + }); + + this.contextKeyService = createScopedContextKeyService(contextKeyService, this); + + const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService); + listSupportsMultiSelect.set(!(options.multipleSelectionSupport === false)); + + this.hasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService); + this.hasDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService); + this.hasMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService); + + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); + + this.disposables.push(combinedDisposable([ + this.contextKeyService, + (listService as ListService).register(this), + attachListStyler(this, themeService), + this.onDidChangeSelection(() => { + const selection = this.getSelection(); + const focus = this.getFocus(); + + this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0); + this.hasMultiSelection.set(selection.length > 1); + this.hasDoubleSelection.set(selection.length === 2); + }), + this.onDidChangeFocus(() => { + const selection = this.getSelection(); + const focus = this.getFocus(); + + this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0); + }) + ])); + + this.registerListeners(); + } + + private registerListeners(): void { + this.disposables.push(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(multiSelectModifierSettingKey)) { + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService); + } + })); + } + + get useAltAsMultipleSelectionModifier(): boolean { + return this._useAltAsMultipleSelectionModifier; + } + + dispose(): void { + super.dispose(); + + this.disposables = dispose(this.disposables); + } +} + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/src/vs/workbench/electron-browser/commands.ts b/src/vs/workbench/electron-browser/commands.ts index 45fe4a8eed78e..847028649adc3 100644 --- a/src/vs/workbench/electron-browser/commands.ts +++ b/src/vs/workbench/electron-browser/commands.ts @@ -22,6 +22,7 @@ import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platf import { URI } from 'vs/base/common/uri'; import { IDownloadService } from 'vs/platform/download/common/download'; import { generateUuid } from 'vs/base/common/uuid'; +import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; // --- List Commands @@ -56,6 +57,17 @@ export function registerCommands(): void { } } + // ObjectTree + else if (focused instanceof ObjectTree) { + const list = focused; + + list.focusNext(count); + const listFocus = list.getFocus(); + if (listFocus.length) { + list.reveal(listFocus[0]); + } + } + // Tree else if (focused) { const tree = focused; @@ -77,7 +89,7 @@ export function registerCommands(): void { handler: (accessor, arg2) => focusDown(accessor, arg2) }); - function expandMultiSelection(focused: List | PagedList | ITree, previousFocus: any): void { + function expandMultiSelection(focused: List | PagedList | ITree | ObjectTree, previousFocus: any): void { // List if (focused instanceof List || focused instanceof PagedList) { @@ -92,6 +104,19 @@ export function registerCommands(): void { } } + // ObjectTree + else if (focused instanceof ObjectTree) { + const list = focused; + + const focus = list.getFocus() ? list.getFocus()[0] : void 0; + const selection = list.getSelection(); + if (selection && selection.indexOf(focus) >= 0) { + list.setSelection(selection.filter(s => s !== previousFocus)); + } else { + list.setSelection(selection.concat(focus)); + } + } + // Tree else if (focused) { const tree = focused; @@ -126,6 +151,18 @@ export function registerCommands(): void { expandMultiSelection(focused, previousFocus); } + // ObjectTree + else if (focused instanceof ObjectTree) { + const list = focused; + + // Focus down first + const previousFocus = list.getFocus() ? list.getFocus()[0] : void 0; + focusDown(accessor, arg2); + + // Then adjust selection + expandMultiSelection(focused, previousFocus); + } + // Tree else if (focused) { const tree = focused; @@ -158,6 +195,17 @@ export function registerCommands(): void { } } + // ObjectTree + else if (focused instanceof ObjectTree) { + const list = focused; + + list.focusPrevious(count); + const listFocus = list.getFocus(); + if (listFocus.length) { + list.reveal(listFocus[0]); + } + } + // Tree else if (focused) { const tree = focused; @@ -227,18 +275,38 @@ export function registerCommands(): void { // Tree only if (focused && !(focused instanceof List || focused instanceof PagedList)) { - const tree = focused; - const focus = tree.getFocus(); + if (focused instanceof ObjectTree) { + const tree = focused; + const focusedElements = tree.getFocus(); + + if (focusedElements.length === 0) { + return; + } - tree.collapse(focus).then(didCollapse => { - if (focus && !didCollapse) { - tree.focusParent({ origin: 'keyboard' }); + const focus = focusedElements[0]; - return tree.reveal(tree.getFocus()); + if (!tree.collapse(focus)) { + const parent = tree.getParentElement(focus); + + if (parent) { + tree.setFocus(parent); + tree.reveal(parent); + } } + } else { + const tree = focused; + const focus = tree.getFocus(); + + tree.collapse(focus).then(didCollapse => { + if (focus && !didCollapse) { + tree.focusParent({ origin: 'keyboard' }); + + return tree.reveal(tree.getFocus()); + } - return void 0; - }); + return void 0; + }); + } } } }); @@ -253,18 +321,38 @@ export function registerCommands(): void { // Tree only if (focused && !(focused instanceof List || focused instanceof PagedList)) { - const tree = focused; - const focus = tree.getFocus(); + if (focused instanceof ObjectTree) { + const tree = focused; + const focusedElements = tree.getFocus(); + + if (focusedElements.length === 0) { + return; + } + + const focus = focusedElements[0]; - tree.expand(focus).then(didExpand => { - if (focus && !didExpand) { - tree.focusFirstChild({ origin: 'keyboard' }); + if (!tree.expand(focus)) { + const child = tree.getFirstElementChild(focus); - return tree.reveal(tree.getFocus()); + if (child) { + tree.setFocus(child); + tree.reveal(child); + } } + } else { + const tree = focused; + const focus = tree.getFocus(); - return void 0; - }); + tree.expand(focus).then(didExpand => { + if (focus && !didExpand) { + tree.focusFirstChild({ origin: 'keyboard' }); + + return tree.reveal(tree.getFocus()); + } + + return void 0; + }); + } } } }); @@ -288,6 +376,14 @@ export function registerCommands(): void { list.reveal(list.getFocus()[0]); } + // ObjectTree + else if (focused instanceof ObjectTree) { + const list = focused; + + list.focusPreviousPage(); + list.reveal(list.getFocus()[0]); + } + // Tree else if (focused) { const tree = focused; @@ -317,6 +413,14 @@ export function registerCommands(): void { list.reveal(list.getFocus()[0]); } + // ObjectTree + else if (focused instanceof ObjectTree) { + const list = focused; + + list.focusNextPage(); + list.reveal(list.getFocus()[0]); + } + // Tree else if (focused) { const tree = focused; @@ -357,6 +461,14 @@ export function registerCommands(): void { list.reveal(0); } + // ObjectTree + else if (focused instanceof ObjectTree) { + const list = focused; + + list.setFocus([0]); + list.reveal(0); + } + // Tree else if (focused) { const tree = focused; @@ -396,6 +508,19 @@ export function registerCommands(): void { list.reveal(list.length - 1); } + // ObjectTree + else if (focused instanceof ObjectTree) { + const list = focused; + const last = list.getLastElementAncestor(); + + if (!last) { + return; + } + + list.setFocus([last]); + list.reveal(last); + } + // Tree else if (focused) { const tree = focused; @@ -424,6 +549,13 @@ export function registerCommands(): void { list.open(list.getFocus()); } + // ObjectTree + else if (focused instanceof ObjectTree) { + const list = focused; + list.setSelection(list.getFocus()); + list.open(list.getFocus()); + } + // Tree else if (focused) { const tree = focused; @@ -462,11 +594,22 @@ export function registerCommands(): void { // Tree only if (focused && !(focused instanceof List || focused instanceof PagedList)) { - const tree = focused; - const focus = tree.getFocus(); + if (focused instanceof ObjectTree) { + const tree = focused; + const focus = tree.getFocus(); - if (focus) { - tree.toggleExpansion(focus); + if (focus.length === 0) { + return; + } + + tree.toggleCollapsed(focus); + } else { + const tree = focused; + const focus = tree.getFocus(); + + if (focus) { + tree.toggleExpansion(focus); + } } } } @@ -486,14 +629,19 @@ export function registerCommands(): void { if (list.getSelection().length > 0) { list.setSelection([]); - - return void 0; + } else if (list.getFocus().length > 0) { + list.setFocus([]); } + } - if (list.getFocus().length > 0) { - list.setFocus([]); + // ObjectTree + else if (focused instanceof ObjectTree) { + const list = focused; - return void 0; + if (list.getSelection().length > 0) { + list.setSelection([]); + } else if (list.getFocus().length > 0) { + list.setFocus([]); } } @@ -503,14 +651,8 @@ export function registerCommands(): void { if (tree.getSelection().length) { tree.clearSelection({ origin: 'keyboard' }); - - return void 0; - } - - if (tree.getFocus()) { + } else if (tree.getFocus()) { tree.clearFocus({ origin: 'keyboard' }); - - return void 0; } } } From 07b01bf9ec5edf6b7323d7ff05013d043d893cf0 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 8 Oct 2018 11:24:33 +0100 Subject: [PATCH 09/35] adopt WorkbenchObjectTree in markers panel --- .../parts/markers/electron-browser/markersPanel.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 4b92a012e96a4..5415eb63c788c 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -27,10 +27,10 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { Scope } from 'vs/workbench/common/memento'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { debounceEvent } from 'vs/base/common/event'; +import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -56,7 +56,7 @@ export class MarkersPanel extends Panel { private lastSelectedRelativeTop: number = 0; private currentActiveResource: URI = null; - private tree: ObjectTree; + private tree: WorkbenchObjectTree; private rangeHighlightDecorations: RangeHighlightDecorations; private actions: IAction[]; @@ -240,11 +240,12 @@ export class MarkersPanel extends Panel { this.instantiationService.createInstance(Viewer.RelatedInformationRenderer) ]; - this.tree = new ObjectTree( + this.tree = this.instantiationService.createInstance(WorkbenchObjectTree, this.treeContainer, virtualDelegate, - renderers - ); + renderers, + {} + ) as any as WorkbenchObjectTree; // this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { // dataSource: new Viewer.DataSource(), From d8b79267f5dd4c313d0be81b5251ddd553a62475 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 8 Oct 2018 11:31:06 +0100 Subject: [PATCH 10/35] fix list.focusFirst --- src/vs/workbench/electron-browser/commands.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/electron-browser/commands.ts b/src/vs/workbench/electron-browser/commands.ts index 847028649adc3..2253068a4ee9a 100644 --- a/src/vs/workbench/electron-browser/commands.ts +++ b/src/vs/workbench/electron-browser/commands.ts @@ -464,9 +464,14 @@ export function registerCommands(): void { // ObjectTree else if (focused instanceof ObjectTree) { const list = focused; + const first = list.getFirstElementChild(null); - list.setFocus([0]); - list.reveal(0); + if (!first) { + return; + } + + list.setFocus([first]); + list.reveal(first); } // Tree From ed604d1fe7cb27c79917aca2482ccfb8ca1fa232 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 9 Oct 2018 16:11:24 +0200 Subject: [PATCH 11/35] hook in filter to problems tree --- src/vs/base/browser/ui/tree/indexTreeModel.ts | 8 +- src/vs/base/browser/ui/tree/tree.ts | 8 +- .../electron-browser/markersFilterOptions.ts | 67 ++++++++ .../markers/electron-browser/markersModel.ts | 59 ------- .../markers/electron-browser/markersPanel.ts | 75 ++++++-- .../electron-browser/markersPanelActions.ts | 15 +- .../electron-browser/markersTreeViewer.ts | 162 ++++++++++++------ 7 files changed, 257 insertions(+), 137 deletions(-) create mode 100644 src/vs/workbench/parts/markers/electron-browser/markersFilterOptions.ts diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index c6b4304bf3239..2b3570eb730eb 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -7,7 +7,7 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { Iterator, ISequence } from 'vs/base/common/iterator'; import { Emitter, Event } from 'vs/base/common/event'; import { tail2 } from 'vs/base/common/arrays'; -import { ITreeFilterResult, TreeVisibility, ITreeFilter, ITreeOptions, ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree'; +import { ITreeFilterDataResult, TreeVisibility, ITreeFilter, ITreeOptions, ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree'; interface IMutableTreeNode extends ITreeNode { readonly parent: IMutableTreeNode | undefined; @@ -19,7 +19,7 @@ interface IMutableTreeNode extends ITreeNode { visible: boolean; } -function isFilterResult(obj: any): obj is ITreeFilterResult { +function isFilterResult(obj: any): obj is ITreeFilterDataResult { return typeof obj === 'object' && 'visibility' in obj && 'data' in obj; } @@ -30,8 +30,10 @@ function treeNodeToElement(node: IMutableTreeNode): ITreeElement { return { element, children, collapsed }; } -function getVisibleState(visibility: TreeVisibility): boolean | undefined { +function getVisibleState(visibility: boolean | TreeVisibility): boolean | undefined { switch (visibility) { + case true: return true; + case false: return false; case TreeVisibility.Hidden: return false; case TreeVisibility.Visible: return true; case TreeVisibility.Recurse: return undefined; diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index f219267f1229b..f1b4bb3ce534c 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -12,13 +12,15 @@ export const enum TreeVisibility { Recurse // TODO@joao come up with a better name } -export interface ITreeFilterResult { - visibility: TreeVisibility; +export interface ITreeFilterDataResult { + visibility: boolean | TreeVisibility; data: TFilterData; } +export type TreeFilterResult = boolean | TreeVisibility | ITreeFilterDataResult; + export interface ITreeFilter { - filter(element: T): boolean | TreeVisibility | ITreeFilterResult; + filter(element: T): TreeFilterResult; } export interface ITreeOptions { diff --git a/src/vs/workbench/parts/markers/electron-browser/markersFilterOptions.ts b/src/vs/workbench/parts/markers/electron-browser/markersFilterOptions.ts new file mode 100644 index 0000000000000..0398ddc3e3315 --- /dev/null +++ b/src/vs/workbench/parts/markers/electron-browser/markersFilterOptions.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; +import { IFilter, or, matchesPrefix, matchesContiguousSubString, matchesFuzzy } from 'vs/base/common/filters'; +import { ParsedExpression, IExpression, splitGlobAware, getEmptyExpression, parse } from 'vs/base/common/glob'; +import * as strings from 'vs/base/common/strings'; + +export class FilterOptions { + + static readonly _filter: IFilter = or(matchesPrefix, matchesContiguousSubString); + static readonly _fuzzyFilter: IFilter = or(matchesPrefix, matchesContiguousSubString, matchesFuzzy); + + readonly filterErrors: boolean = false; + readonly filterWarnings: boolean = false; + readonly filterInfos: boolean = false; + readonly excludePattern: ParsedExpression = null; + readonly includePattern: ParsedExpression = null; + readonly textFilter: string = ''; + + constructor(readonly filter: string = '', excludePatterns: IExpression = {}) { + filter = filter.trim(); + for (const key of Object.keys(excludePatterns)) { + if (excludePatterns[key]) { + this.setPattern(excludePatterns, key); + } + delete excludePatterns[key]; + } + const includePatterns: IExpression = getEmptyExpression(); + if (filter) { + const filters = splitGlobAware(filter, ',').map(s => s.trim()).filter(s => !!s.length); + for (const f of filters) { + this.filterErrors = this.filterErrors || this.matches(f, Messages.MARKERS_PANEL_FILTER_ERRORS); + this.filterWarnings = this.filterWarnings || this.matches(f, Messages.MARKERS_PANEL_FILTER_WARNINGS); + this.filterInfos = this.filterInfos || this.matches(f, Messages.MARKERS_PANEL_FILTER_INFOS); + if (strings.startsWith(f, '!')) { + this.setPattern(excludePatterns, strings.ltrim(f, '!')); + } else { + this.setPattern(includePatterns, f); + this.textFilter += ` ${f}`; + } + } + } + if (Object.keys(excludePatterns).length) { + this.excludePattern = parse(excludePatterns); + } + if (Object.keys(includePatterns).length) { + this.includePattern = parse(includePatterns); + } + this.textFilter = this.textFilter.trim(); + } + + private setPattern(expression: IExpression, pattern: string) { + if (pattern[0] === '.') { + pattern = '*' + pattern; // convert ".js" to "*.js" + } + expression[`**/${pattern}/**`] = true; + expression[`**/${pattern}`] = true; + } + + private matches(prefix: string, word: string): boolean { + let result = matchesPrefix(prefix, word); + return result && result.length > 0; + } +} diff --git a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts index 8dfc797ced8f6..c641b020cdd2c 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts @@ -75,65 +75,6 @@ export class RelatedInformation { constructor(readonly raw: IRelatedInformation) { } } -// TODO@joao -// export class FilterOptions { - -// static readonly _filter: IFilter = or(matchesPrefix, matchesContiguousSubString); -// static readonly _fuzzyFilter: IFilter = or(matchesPrefix, matchesContiguousSubString, matchesFuzzy); - -// readonly filterErrors: boolean = false; -// readonly filterWarnings: boolean = false; -// readonly filterInfos: boolean = false; -// readonly excludePattern: glob.ParsedExpression = null; -// readonly includePattern: glob.ParsedExpression = null; -// readonly textFilter: string = ''; - -// constructor(readonly filter: string = '', excludePatterns: glob.IExpression = {}) { -// filter = filter.trim(); -// for (const key of Object.keys(excludePatterns)) { -// if (excludePatterns[key]) { -// this.setPattern(excludePatterns, key); -// } -// delete excludePatterns[key]; -// } -// const includePatterns: glob.IExpression = glob.getEmptyExpression(); -// if (filter) { -// const filters = glob.splitGlobAware(filter, ',').map(s => s.trim()).filter(s => !!s.length); -// for (const f of filters) { -// this.filterErrors = this.filterErrors || this.matches(f, Messages.MARKERS_PANEL_FILTER_ERRORS); -// this.filterWarnings = this.filterWarnings || this.matches(f, Messages.MARKERS_PANEL_FILTER_WARNINGS); -// this.filterInfos = this.filterInfos || this.matches(f, Messages.MARKERS_PANEL_FILTER_INFOS); -// if (strings.startsWith(f, '!')) { -// this.setPattern(excludePatterns, strings.ltrim(f, '!')); -// } else { -// this.setPattern(includePatterns, f); -// this.textFilter += ` ${f}`; -// } -// } -// } -// if (Object.keys(excludePatterns).length) { -// this.excludePattern = glob.parse(excludePatterns); -// } -// if (Object.keys(includePatterns).length) { -// this.includePattern = glob.parse(includePatterns); -// } -// this.textFilter = this.textFilter.trim(); -// } - -// private setPattern(expression: glob.IExpression, pattern: string) { -// if (pattern[0] === '.') { -// pattern = '*' + pattern; // convert ".js" to "*.js" -// } -// expression[`**/${pattern}/**`] = true; -// expression[`**/${pattern}`] = true; -// } - -// private matches(prefix: string, word: string): boolean { -// let result = matchesPrefix(prefix, word); -// return result && result.length > 0; -// } -// } - export class MarkersModel { private cachedSortedResources: ResourceMarkers[] | undefined = undefined; diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 5415eb63c788c..fc6a545875190 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -16,7 +16,7 @@ import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; import { Marker, ResourceMarkers, RelatedInformation, MarkersModel } from 'vs/workbench/parts/markers/electron-browser/markersModel'; import * as Viewer from 'vs/workbench/parts/markers/electron-browser/markersTreeViewer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersFilterActionItem, MarkersFilterAction, QuickFixAction, QuickFixActionItem } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; +import { MarkersFilterActionItem, MarkersFilterAction, QuickFixAction, QuickFixActionItem, IMarkersFilterActionChangeEvent } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; @@ -31,6 +31,11 @@ import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { debounceEvent } from 'vs/base/common/event'; import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; +import { IExpression, getEmptyExpression } from 'vs/base/common/glob'; +import { mixin, deepClone } from 'vs/base/common/objects'; +import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { isAbsolute, join } from 'vs/base/common/paths'; type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -70,6 +75,8 @@ export class MarkersPanel extends Panel { private panelSettings: any; private panelFoucusContextKey: IContextKey; + private filter: Viewer.Filter; + private currentResourceGotAddedToMarkersData: boolean = false; constructor( @@ -80,7 +87,8 @@ export class MarkersPanel extends Panel { @IThemeService themeService: IThemeService, @IMarkersWorkbenchService private markersWorkbenchService: IMarkersWorkbenchService, @IStorageService storageService: IStorageService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, ) { super(Constants.MARKERS_PANEL_ID, telemetryService, themeService); this.panelFoucusContextKey = Constants.MarkerPanelFocusContextKey.bindTo(contextKeyService); @@ -103,7 +111,7 @@ export class MarkersPanel extends Panel { this.createActions(); this.createListeners(); - // this.updateFilter(); + this.updateFilter(); this.onDidFocus(() => this.panelFoucusContextKey.set(true)); this.onDidBlur(() => this.panelFoucusContextKey.set(false)); @@ -211,9 +219,49 @@ export class MarkersPanel extends Panel { return TPromise.as(null); } - // private updateFilter() { - // this.markersWorkbenchService.filter({ filterText: this.filterAction.filterText, useFilesExclude: this.filterAction.useFilesExclude }); - // } + private updateFilter() { + const excludeExpression = this.getExcludeExpression(this.filterAction.useFilesExclude); + this.filter.options = new FilterOptions(this.filterAction.filterText, excludeExpression); + this.tree.refilter(); + } + + private getExcludeExpression(useFilesExclude: boolean): IExpression { + if (!useFilesExclude) { + return {}; + } + + const workspaceFolders = this.workspaceContextService.getWorkspace().folders; + if (workspaceFolders.length) { + const result = getEmptyExpression(); + for (const workspaceFolder of workspaceFolders) { + mixin(result, this.getExcludesForFolder(workspaceFolder)); + } + return result; + } else { + return this.getFilesExclude(); + } + } + + private getExcludesForFolder(workspaceFolder: IWorkspaceFolder): IExpression { + const expression = this.getFilesExclude(workspaceFolder.uri); + return this.getAbsoluteExpression(expression, workspaceFolder.uri.fsPath); + } + + private getFilesExclude(resource?: URI): IExpression { + return deepClone(this.configurationService.getValue('files.exclude', { resource })) || {}; + } + + private getAbsoluteExpression(expr: IExpression, root: string): IExpression { + return Object.keys(expr) + .reduce((absExpr: IExpression, key: string) => { + if (expr[key] && !isAbsolute(key)) { + const absPattern = join(root, key); + absExpr[absPattern] = expr[key]; + } + + return absExpr; + }, Object.create(null)); + } private createMessageBox(parent: HTMLElement): void { this.messageBoxContainer = dom.append(parent, dom.$('.message-box-container')); @@ -239,12 +287,15 @@ export class MarkersPanel extends Panel { this.instantiationService.createInstance(Viewer.MarkerRenderer, a => this.getActionItem(a)), this.instantiationService.createInstance(Viewer.RelatedInformationRenderer) ]; + this.filter = new Viewer.Filter(); this.tree = this.instantiationService.createInstance(WorkbenchObjectTree, this.treeContainer, virtualDelegate, renderers, - {} + { + filter: this.filter + } ) as any as WorkbenchObjectTree; // this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { @@ -296,11 +347,11 @@ export class MarkersPanel extends Panel { this._register(onModelChange(this.onDidChangeModel, this)); this._register(this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this)); this._register(this.tree.onDidChangeSelection(() => this.onSelected())); - // this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { - // if (event.filterText || event.useFilesExclude) { - // this.updateFilter(); - // } - // })); + this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { + if (event.filterText || event.useFilesExclude) { + this.updateFilter(); + } + })); this.actions.forEach(a => this._register(a)); } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts index b89e12a0f222b..da4f41a8eb76c 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts @@ -16,7 +16,6 @@ import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachStylerCallback, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; import { IMarkersWorkbenchService } from 'vs/workbench/parts/markers/electron-browser/markers'; @@ -134,7 +133,7 @@ export class MarkersFilterActionItem extends BaseActionItem { @IContextViewService private contextViewService: IContextViewService, @IThemeService private themeService: IThemeService, @IMarkersWorkbenchService private markersWorkbenchService: IMarkersWorkbenchService, - @ITelemetryService private telemetryService: ITelemetryService, + // @ITelemetryService private telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService ) { super(null, action); @@ -277,12 +276,12 @@ export class MarkersFilterActionItem extends BaseActionItem { } } + // TODO@joao private reportFilteringUsed(): void { - let data = {}; - console.warn('reportFilteringUsed not implemented'); // TODO@joao - // data['errors'] = this.markersWorkbenchService.markersModel.filterOptions.filterErrors; - // data['warnings'] = this.markersWorkbenchService.markersModel.filterOptions.filterWarnings; - // data['infos'] = this.markersWorkbenchService.markersModel.filterOptions.filterInfos; + // let data = {}; + // data['errors'] = this.filterOptions.filterErrors; + // data['warnings'] = this.filterOptions.filterWarnings; + // data['infos'] = this.filterOptions.filterInfos; /* __GDPR__ "problems.filter" : { "errors" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -290,7 +289,7 @@ export class MarkersFilterActionItem extends BaseActionItem { "infos": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } */ - this.telemetryService.publicLog('problems.filter', data); + // this.telemetryService.publicLog('problems.filter', data); } } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 3b9025155c341..31a4c1939bb03 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -3,16 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TPromise, Promise } from 'vs/base/common/winjs.base'; import * as dom from 'vs/base/browser/dom'; import * as network from 'vs/base/common/network'; import * as paths from 'vs/base/common/paths'; -import { IDataSource, ITree, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree'; +import { ITree, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { FileLabel, ResourceLabel } from 'vs/workbench/browser/labels'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IMarker, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { MarkersModel, ResourceMarkers, Marker, RelatedInformation } from 'vs/workbench/parts/markers/electron-browser/markersModel'; +import { ResourceMarkers, Marker, RelatedInformation } from 'vs/workbench/parts/markers/electron-browser/markersModel'; import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; @@ -24,6 +23,9 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { dirname } from 'vs/base/common/resources'; import { IVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer } from 'vs/base/browser/ui/tree/abstractTree'; +import { ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree'; +import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; +import { IMatch } from 'vs/base/common/filters'; interface IResourceMarkersTemplateData { resourceLabel: ResourceLabel; @@ -46,55 +48,6 @@ interface IRelatedInformationTemplateData { description: HighlightedLabel; } -export class DataSource implements IDataSource { - public getId(tree: ITree, element: any): string { - if (element instanceof MarkersModel) { - return 'root'; - } - // if (element instanceof NodeWithId) { - // return element.id; - // } - return ''; - } - - public hasChildren(tree: ITree, element: any): boolean { - return element instanceof MarkersModel || element instanceof ResourceMarkers || (element instanceof Marker && element.relatedInformation.length > 0); - } - - public getChildren(tree: ITree, element: any): Promise { - if (element instanceof MarkersModel) { - return Promise.as(element.resourceMarkers); - } - if (element instanceof ResourceMarkers) { - return Promise.as(element.markers); - } - if (element instanceof Marker && element.relatedInformation.length > 0) { - return Promise.as(element.relatedInformation); - } - return null; - } - - public getParent(tree: ITree, element: any): Promise { - return TPromise.as(null); - } - - public shouldAutoexpand(tree: ITree, element: any): boolean { - if (element instanceof MarkersModel) { - return true; - } - - if (element instanceof ResourceMarkers) { - return true; - } - - if (element instanceof Marker && element.relatedInformation.length > 0) { - return true; - } - - return false; - } -} - export class MarkersTreeAccessibilityProvider implements IAccessibilityProvider { constructor( @@ -313,4 +266,109 @@ export class RelatedInformationRenderer implements ITreeRenderer { + + options = new FilterOptions(); + + filter(element: ResourceMarkers | Marker | RelatedInformation): TreeFilterResult { + if (element instanceof ResourceMarkers) { + return this.filterResourceMarkers(element); + } else if (element instanceof Marker) { + return this.filterMarker(element); + } else { + return this.filterRelatedInformation(element); + } + } + + private filterResourceMarkers(resourceMarkers: ResourceMarkers): TreeFilterResult { + if (resourceMarkers.resource.scheme === network.Schemas.walkThrough || resourceMarkers.resource.scheme === network.Schemas.walkThroughSnippet) { + return false; + } + + if (this.options.excludePattern && !!this.options.excludePattern(resourceMarkers.resource.fsPath)) { + return false; + } + + if (this.options.includePattern && this.options.includePattern(resourceMarkers.resource.fsPath)) { + return true; + } + + const uriMatches = FilterOptions._filter(this.options.textFilter, paths.basename(resourceMarkers.resource.fsPath)); + + if (this.options.textFilter && uriMatches) { + return { visibility: true, data: { type: FilterDataType.ResourceMarkers, uriMatches } }; + } + + return false; + } + + + private filterMarker(marker: Marker): TreeFilterResult { + if (this.options.filterErrors && MarkerSeverity.Error === marker.marker.severity) { + return true; + } + + if (this.options.filterWarnings && MarkerSeverity.Warning === marker.marker.severity) { + return true; + } + + if (this.options.filterInfos && MarkerSeverity.Info === marker.marker.severity) { + return true; + } + + if (!this.options.textFilter) { + return true; + } + + const messageMatches = FilterOptions._fuzzyFilter(this.options.textFilter, marker.marker.message); + const sourceMatches = marker.marker.source && FilterOptions._filter(this.options.textFilter, marker.marker.source); + const codeMatches = marker.marker.code && FilterOptions._filter(this.options.textFilter, marker.marker.code); + + if (messageMatches || sourceMatches || codeMatches) { + return { visibility: true, data: { type: FilterDataType.Marker, messageMatches: messageMatches || [], sourceMatches: sourceMatches || [], codeMatches: codeMatches || [] } }; + } + + return TreeVisibility.Recurse; + } + + private filterRelatedInformation(relatedInformation: RelatedInformation): TreeFilterResult { + if (!this.options.textFilter) { + return true; + } + + const uriMatches = FilterOptions._filter(this.options.textFilter, paths.basename(relatedInformation.raw.resource.fsPath)); + const messageMatches = FilterOptions._filter(this.options.textFilter, paths.basename(relatedInformation.raw.message)); + + if (uriMatches || messageMatches) { + return { visibility: true, data: { type: FilterDataType.RelatedInformation, uriMatches: uriMatches || [], messageMatches: messageMatches || [] } }; + } + + return false; + } } \ No newline at end of file From 43f42a4ab69aa629648ac1ee204c9d0f236268dc Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 9 Oct 2018 17:56:59 +0200 Subject: [PATCH 12/35] markers: fix styles --- src/vs/base/browser/ui/tree/tree.css | 4 ++++ .../electron-browser/media/markers.css | 20 +++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/vs/base/browser/ui/tree/tree.css b/src/vs/base/browser/ui/tree/tree.css index 24d3bd1f05c8a..b79ef78dbd406 100644 --- a/src/vs/base/browser/ui/tree/tree.css +++ b/src/vs/base/browser/ui/tree/tree.css @@ -18,4 +18,8 @@ font-size: 10px; text-align: right; padding-right: 10px; +} + +.monaco-tl-row > .monaco-tl-contents { + flex: 1; } \ No newline at end of file diff --git a/src/vs/workbench/parts/markers/electron-browser/media/markers.css b/src/vs/workbench/parts/markers/electron-browser/media/markers.css index 6a47298c02649..774fc16986614 100644 --- a/src/vs/workbench/parts/markers/electron-browser/media/markers.css +++ b/src/vs/workbench/parts/markers/electron-browser/media/markers.css @@ -141,7 +141,7 @@ font-weight: bold; } -.markers-panel .monaco-tree .monaco-tl-contents > .icon { +.markers-panel .monaco-tl-contents > .icon { height: 22px; margin-right: 6px; flex: 0 0 16px; @@ -171,36 +171,36 @@ background: url('status-info-inverse.svg') center center no-repeat; } -.markers-panel .monaco-tree .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { +.markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { background: url('lightbulb.svg') center/80% no-repeat; margin-right: 0px; } -.vs-dark .markers-panel .monaco-tree .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { +.vs-dark .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { background: url('lightbulb-dark.svg') center/80% no-repeat; } -.markers-panel .monaco-tree .monaco-tree-row .monaco-tl-contents > .actions { +.markers-panel .monaco-tl-contents > .actions { flex: 0 0 16px; } -.markers-panel .monaco-tree .monaco-tree-row .monaco-tl-contents > .actions .monaco-action-bar { +.markers-panel .monaco-tl-contents > .actions .monaco-action-bar { display: none; } -.markers-panel .monaco-tree .monaco-tree-row:hover .monaco-tl-contents > .actions .monaco-action-bar, -.markers-panel .monaco-tree .monaco-tree-row.selected .monaco-tl-contents > .actions .monaco-action-bar, -.markers-panel .monaco-tree .monaco-tree-row.focused .monaco-tl-contents > .actions .monaco-action-bar { +.markers-panel .monaco-tl-row:hover .monaco-tl-contents > .actions .monaco-action-bar, +.markers-panel .monaco-tl-row.selected .monaco-tl-contents > .actions .monaco-action-bar, +.markers-panel .monaco-tl-row.focused .monaco-tl-contents > .actions .monaco-action-bar { display: block; } -.markers-panel .monaco-tree .monaco-tl-contents .actions .action-label { +.markers-panel .monaco-tl-contents .actions .action-label { width: 16px; height: 100%; background-position: 50% 50%; background-repeat: no-repeat; } -.markers-panel .monaco-tree .monaco-tl-contents .actions .action-item.disabled { +.markers-panel .monaco-tl-contents .actions .action-item.disabled { display: none; } \ No newline at end of file From e9809b3cff727b883dbe342bd23c038cdc58365e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 9 Oct 2018 18:18:58 +0200 Subject: [PATCH 13/35] fix markers filter --- src/vs/base/browser/ui/tree/indexTreeModel.ts | 3 ++- .../parts/markers/electron-browser/markersTreeViewer.ts | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index 2b3570eb730eb..0e0d2db0e2faa 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -203,7 +203,8 @@ export class IndexTreeModel implements ITreeModel this.createTreeNode(el, node, revealed && !treeElement.collapsed, treeListElements, onDidCreateNode)); + const childRevealed = revealed && visible !== false && !node.collapsed; + const childNodes = Iterator.map(childElements, el => this.createTreeNode(el, node, childRevealed, treeListElements, onDidCreateNode)); let hasVisibleDescendants = false; let revealedCount = 1; diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 31a4c1939bb03..5be3b01463f33 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -325,10 +325,9 @@ export class Filter implements ITreeFilter { if (this.options.filterErrors && MarkerSeverity.Error === marker.marker.severity) { return true; From 0da607307df38da18ec6b70906b56aaac9504e06 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 9 Oct 2018 22:41:32 +0200 Subject: [PATCH 14/35] markers: filter with highlights --- src/vs/base/browser/ui/tree/abstractTree.ts | 15 +-- src/vs/base/browser/ui/tree/dataTree.ts | 27 +++-- src/vs/base/browser/ui/tree/tree.ts | 11 ++ src/vs/platform/list/browser/listService.ts | 5 +- .../electron-browser/markersTreeViewer.ts | 102 ++++++++++-------- 5 files changed, 92 insertions(+), 68 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 2915ad1a38116..9c3966683bcb5 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -11,7 +11,7 @@ import { append, $ } from 'vs/base/browser/dom'; import { Event, Relay, chain, mapEvent } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { ITreeModel, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { ITreeModel, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel'; import { memoize } from 'vs/base/common/decorators'; @@ -72,11 +72,6 @@ function renderDefaultTwistie(node: ITreeNode, twistie: HTMLElement): } } -export interface ITreeRenderer extends IRenderer { - renderTwistie?(element: TElement, twistieElement: HTMLElement): boolean; - onDidChangeTwistieState?: Event; -} - class TreeRenderer implements IRenderer, ITreeListTemplateData> { readonly templateId: string; @@ -85,7 +80,7 @@ class TreeRenderer implements IRenderer, + private renderer: ITreeRenderer, onDidChangeCollapseState: Event> ) { this.templateId = renderer.templateId; @@ -113,11 +108,11 @@ class TreeRenderer implements IRenderer, index: number, templateData: ITreeListTemplateData): void { - this.renderer.disposeElement(node.element, index, templateData.templateData); + this.renderer.disposeElement(node, index, templateData.templateData); this.renderedNodes.delete(node); this.renderedElements.set(node.element); } @@ -190,7 +185,7 @@ export abstract class AbstractTree implements IDisposable constructor( container: HTMLElement, delegate: IVirtualDelegate, - renderers: ITreeRenderer[], + renderers: ITreeRenderer[], options?: ITreeOptions ) { const treeDelegate = new ComposedTreeDelegate>(delegate); diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index edb99d425854a..c082233b7cf2c 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITreeOptions, ComposedTreeDelegate, createComposedTreeListOptions, ITreeRenderer } from 'vs/base/browser/ui/tree/abstractTree'; +import { ITreeOptions, ComposedTreeDelegate, createComposedTreeListOptions } from 'vs/base/browser/ui/tree/abstractTree'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; -import { IVirtualDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; -import { ITreeElement, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { IVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ITreeElement, ITreeNode, ITreeRenderer, ITreeRenderElement } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; @@ -39,14 +39,21 @@ interface IDataTreeListTemplateData { templateData: T; } -class DataTreeRenderer implements ITreeRenderer, IDataTreeListTemplateData> { +function unpack(node: ITreeRenderElement, TFilterData>): ITreeRenderElement { + return { + get element() { return node.element.element; }, + get filterData() { return node.filterData; } + }; +} + +class DataTreeRenderer implements ITreeRenderer, TFilterData, IDataTreeListTemplateData> { readonly templateId: string; private renderedNodes = new Map, IDataTreeListTemplateData>(); private disposables: IDisposable[] = []; constructor( - private renderer: IRenderer, + private renderer: ITreeRenderer, readonly onDidChangeTwistieState: Event> ) { this.templateId = renderer.templateId; @@ -57,8 +64,8 @@ class DataTreeRenderer implements ITreeRenderer, index: number, templateData: IDataTreeListTemplateData): void { - this.renderer.renderElement(node.element, index, templateData.templateData); + renderElement(element: ITreeRenderElement, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { + this.renderer.renderElement(unpack(element), index, templateData.templateData); } renderTwistie(element: IDataTreeNode, twistieElement: HTMLElement): boolean { @@ -70,8 +77,8 @@ class DataTreeRenderer implements ITreeRenderer, index: number, templateData: IDataTreeListTemplateData): void { - this.renderer.disposeElement(node.element, index, templateData.templateData); + disposeElement(element: ITreeRenderElement, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { + this.renderer.disposeElement(unpack(element), index, templateData.templateData); } disposeTemplate(templateData: IDataTreeListTemplateData): void { @@ -97,7 +104,7 @@ export class DataTree, TFilterData = void> implements constructor( container: HTMLElement, delegate: IVirtualDelegate, - renderers: ITreeRenderer[], + renderers: ITreeRenderer[], private dataSource: IDataSource, options?: ITreeOptions ) { diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index f1b4bb3ce534c..488ada3c4b28d 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -5,6 +5,7 @@ import { Event } from 'vs/base/common/event'; import { Iterator } from 'vs/base/common/iterator'; +import { IRenderer } from 'vs/base/browser/ui/list/list'; export const enum TreeVisibility { Hidden, @@ -60,4 +61,14 @@ export interface ITreeModel { getParentElement(location: TRef): T | null; getFirstElementChild(location: TRef): T | null; getLastElementAncestor(location: TRef): T | null; +} + +export interface ITreeRenderElement { + readonly element: T; + readonly filterData: TFilterData | undefined; +} + +export interface ITreeRenderer extends IRenderer, TTemplateData> { + renderTwistie?(element: T, twistieElement: HTMLElement): boolean; + onDidChangeTwistieState?: Event; } \ No newline at end of file diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index d276a9609c6ec..75c937010bb72 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -33,7 +33,8 @@ import { attachInputBoxStyler, attachListStyler, computeStyles, defaultListStyle import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; -import { ITreeRenderer, ITreeOptions as ITreeOptions2 } from 'vs/base/browser/ui/tree/abstractTree'; +import { ITreeOptions as ITreeOptions2 } from 'vs/base/browser/ui/tree/abstractTree'; +import { ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; export type ListWidget = List | PagedList | ITree | ObjectTree; @@ -814,7 +815,7 @@ export class WorkbenchObjectTree, TFilterData = void> constructor( container: HTMLElement, delegate: IVirtualDelegate, - renderers: ITreeRenderer[], + renderers: ITreeRenderer[], options: ITreeOptions2, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 5be3b01463f33..4ee903af0cfb4 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -22,8 +22,7 @@ import { QuickFixAction } from 'vs/workbench/parts/markers/electron-browser/mark import { ILabelService } from 'vs/platform/label/common/label'; import { dirname } from 'vs/base/common/resources'; import { IVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeRenderer } from 'vs/base/browser/ui/tree/abstractTree'; -import { ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree'; +import { ITreeFilter, TreeVisibility, TreeFilterResult, ITreeRenderer, ITreeRenderElement } from 'vs/base/browser/ui/tree/tree'; import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; import { IMatch } from 'vs/base/common/filters'; @@ -99,7 +98,33 @@ export class VirtualDelegate implements IVirtualDelegate { +const enum FilterDataType { + ResourceMarkers, + Marker, + RelatedInformation +} + +interface ResourceMarkersFilterData { + type: FilterDataType.ResourceMarkers; + uriMatches: IMatch[]; +} + +interface MarkerFilterData { + type: FilterDataType.Marker; + messageMatches: IMatch[]; + sourceMatches: IMatch[]; + codeMatches: IMatch[]; +} + +interface RelatedInformationFilterData { + type: FilterDataType.RelatedInformation; + uriMatches: IMatch[]; + messageMatches: IMatch[]; +} + +type FilterData = ResourceMarkersFilterData | MarkerFilterData | RelatedInformationFilterData; + +export class ResourceMarkersRenderer implements ITreeRenderer { constructor( @IInstantiationService protected instantiationService: IInstantiationService, @@ -123,14 +148,17 @@ export class ResourceMarkersRenderer implements ITreeRenderer, _: number, templateData: IResourceMarkersTemplateData): void { + const resourceMarkers = element.element; + const uriMatches = element.filterData && element.filterData.uriMatches || []; + if (templateData.resourceLabel instanceof FileLabel) { - templateData.resourceLabel.setFile(element.resource/* , { matches: element.uriMatches } */); + templateData.resourceLabel.setFile(resourceMarkers.resource, { matches: uriMatches }); } else { - templateData.resourceLabel.setLabel({ name: element.name, description: this.labelService.getUriLabel(dirname(element.resource), { relative: true }), resource: element.resource }/* , { matches: element.uriMatches } */); + templateData.resourceLabel.setLabel({ name: resourceMarkers.name, description: this.labelService.getUriLabel(dirname(resourceMarkers.resource), { relative: true }), resource: resourceMarkers.resource }, { matches: uriMatches }); } - templateData.count.setCount(element.markers.length/* filteredCount */); + // templateData.count.setCount(element.markers.length/* filteredCount */); } disposeElement(): void { @@ -156,7 +184,7 @@ export class FileResourceMarkersRenderer extends ResourceMarkersRenderer { } } -export class MarkerRenderer implements ITreeRenderer { +export class MarkerRenderer implements ITreeRenderer { constructor( private actionItemProvider: IActionItemProvider, @@ -177,24 +205,26 @@ export class MarkerRenderer implements ITreeRenderer, _: number, templateData: IMarkerTemplateData): void { + const marker = element.element.marker; + const sourceMatches = element.filterData && element.filterData.sourceMatches || []; + const messageMatches = element.filterData && element.filterData.messageMatches || []; + const codeMatches = element.filterData && element.filterData.codeMatches || []; templateData.icon.className = 'icon ' + MarkerRenderer.iconClassNameFor(marker); - templateData.source.set(marker.source/* , element.sourceMatches */); + templateData.source.set(marker.source, sourceMatches); dom.toggleClass(templateData.source.element, 'marker-source', !!marker.source); templateData.actionBar.clear(); - const quickFixAction = this.instantiationService.createInstance(QuickFixAction, element); + const quickFixAction = this.instantiationService.createInstance(QuickFixAction, element.element); templateData.actionBar.push([quickFixAction], { icon: true, label: false }); - templateData.description.set(marker.message/* , element.messageMatches */); + templateData.description.set(marker.message, messageMatches); templateData.description.element.title = marker.message; dom.toggleClass(templateData.code.element, 'marker-code', !!marker.code); - templateData.code.set(marker.code || ''/* , element.codeMatches */); + templateData.code.set(marker.code || '', codeMatches); templateData.lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(marker.startLineNumber, marker.startColumn); } @@ -224,7 +254,7 @@ export class MarkerRenderer implements ITreeRenderer { +export class RelatedInformationRenderer implements ITreeRenderer { constructor( @ILabelService private labelService: ILabelService @@ -250,12 +280,16 @@ export class RelatedInformationRenderer implements ITreeRenderer, _: number, templateData: IRelatedInformationTemplateData): void { + const relatedInformation = element.element.raw; + const uriMatches = element.filterData && element.filterData.uriMatches || []; + const messageMatches = element.filterData && element.filterData.messageMatches || []; + + templateData.resourceLabel.set(paths.basename(relatedInformation.resource.fsPath), uriMatches); + templateData.resourceLabel.element.title = this.labelService.getUriLabel(relatedInformation.resource, { relative: true }); + templateData.lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(relatedInformation.startLineNumber, relatedInformation.startColumn); + templateData.description.set(relatedInformation.message, messageMatches); + templateData.description.element.title = relatedInformation.message; } disposeElement(): void { @@ -268,30 +302,6 @@ export class RelatedInformationRenderer implements ITreeRenderer { options = new FilterOptions(); From b2bee0c2c0a365b581182dc4fb40657a77749f09 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 9 Oct 2018 22:53:57 +0200 Subject: [PATCH 15/35] :lipstick: --- src/vs/base/browser/ui/tree/abstractTree.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 9c3966683bcb5..3de8f8e4a87c6 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -212,7 +212,6 @@ export abstract class AbstractTree implements IDisposable // Widget - // TODO@joao rename to `get domElement` getHTMLElement(): HTMLElement { return this.view.getHTMLElement(); } From f477b54fb4c8783dd99586fdcfa9c1b5d69eb013 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 9 Oct 2018 22:54:59 +0200 Subject: [PATCH 16/35] remove todo --- .../parts/markers/electron-browser/markersTreeViewer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 4ee903af0cfb4..edb5de3814f2f 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -279,7 +279,6 @@ export class RelatedInformationRenderer implements ITreeRenderer, _: number, templateData: IRelatedInformationTemplateData): void { const relatedInformation = element.element.raw; const uriMatches = element.filterData && element.filterData.uriMatches || []; From d4faab68b07b8d368379ab3dbf53994ef817de98 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 9 Oct 2018 23:06:04 +0200 Subject: [PATCH 17/35] add ITreeNode.visible --- src/vs/base/browser/ui/tree/indexTreeModel.ts | 2 +- src/vs/base/browser/ui/tree/tree.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index 0e0d2db0e2faa..2e980f16196e7 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -15,8 +15,8 @@ interface IMutableTreeNode extends ITreeNode { collapsible: boolean; collapsed: boolean; revealedCount: number; - filterData: TFilterData | undefined; visible: boolean; + filterData: TFilterData | undefined; } function isFilterResult(obj: any): obj is ITreeFilterDataResult { diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 488ada3c4b28d..efd55ce224bd8 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -43,6 +43,7 @@ export interface ITreeNode { readonly collapsible: boolean; readonly collapsed: boolean; readonly revealedCount: number; + readonly visible: boolean; readonly filterData: TFilterData | undefined; } From 5f69bcbc0373c3998ee5b666a16c3664552cc074 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 9 Oct 2018 23:08:32 +0200 Subject: [PATCH 18/35] revealedCount -> renderNodeCount --- src/vs/base/browser/ui/tree/indexTreeModel.ts | 56 +++++++++---------- src/vs/base/browser/ui/tree/tree.ts | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index 2e980f16196e7..fc26697285703 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -14,7 +14,7 @@ interface IMutableTreeNode extends ITreeNode { readonly children: IMutableTreeNode[]; collapsible: boolean; collapsed: boolean; - revealedCount: number; + renderNodeCount: number; visible: boolean; filterData: TFilterData | undefined; } @@ -51,7 +51,7 @@ export class IndexTreeModel implements ITreeModel implements ITreeModel this.createTreeNode(el, parentNode, revealed, treeListElementsToInsert, onDidCreateNode)); const nodesToInsert: IMutableTreeNode[] = []; - let revealedCount = 0; + let renderNodeCount = 0; Iterator.forEach(nodesToInsertIterator, node => { nodesToInsert.push(node); - revealedCount += node.revealedCount; + renderNodeCount += node.renderNodeCount; }); const lastIndex = location[location.length - 1]; const deletedNodes = parentNode.children.splice(lastIndex, deleteCount, ...nodesToInsert); if (revealed) { - const visibleDeleteCount = deletedNodes.reduce((r, node) => r + node.revealedCount, 0); + const visibleDeleteCount = deletedNodes.reduce((r, node) => r + node.renderNodeCount, 0); - this._updateAncestorsRevealedCount(parentNode, revealedCount - visibleDeleteCount); + this._updateAncestorsRenderNodeCount(parentNode, renderNodeCount - visibleDeleteCount); this.list.splice(listIndex, visibleDeleteCount, treeListElementsToInsert); } @@ -146,9 +146,9 @@ export class IndexTreeModel implements ITreeModel, listIndex: number, revealed: boolean, collapsed?: boolean | undefined): boolean { @@ -167,10 +167,10 @@ export class IndexTreeModel implements ITreeModel implements ITreeModel implements ITreeModel this.createTreeNode(el, node, childRevealed, treeListElements, onDidCreateNode)); let hasVisibleDescendants = false; - let revealedCount = 1; + let renderNodeCount = 1; Iterator.forEach(childNodes, child => { node.children.push(child); hasVisibleDescendants = hasVisibleDescendants || child.visible; - revealedCount += child.revealedCount; + renderNodeCount += child.renderNodeCount; }); node.collapsible = node.collapsible || node.children.length > 0; node.visible = typeof visible === 'undefined' ? hasVisibleDescendants : visible; if (!node.visible) { - node.revealedCount = 0; + node.renderNodeCount = 0; if (revealed) { treeListElements.pop(); } } else if (!node.collapsed) { - node.revealedCount = revealedCount; + node.renderNodeCount = renderNodeCount; } if (onDidCreateNode) { @@ -236,11 +236,11 @@ export class IndexTreeModel implements ITreeModel): ITreeNode[] { - const previousRevealedCount = node.revealedCount; + const previousRenderNodeCount = node.renderNodeCount; const result: ITreeNode[] = []; this._updateNodeAfterCollapseChange(node, result); - this._updateAncestorsRevealedCount(node.parent, result.length - previousRevealedCount); + this._updateAncestorsRenderNodeCount(node.parent, result.length - previousRenderNodeCount); return result; } @@ -251,23 +251,23 @@ export class IndexTreeModel implements ITreeModel): ITreeNode[] { - const previousRevealedCount = node.revealedCount; + const previousRenderNodeCount = node.renderNodeCount; const result: ITreeNode[] = []; this._updateNodeAfterFilterChange(node, result); - this._updateAncestorsRevealedCount(node.parent, result.length - previousRevealedCount); + this._updateAncestorsRenderNodeCount(node.parent, result.length - previousRenderNodeCount); return result; } @@ -289,7 +289,7 @@ export class IndexTreeModel implements ITreeModel implements ITreeModel, diff: number): void { + private _updateAncestorsRenderNodeCount(node: IMutableTreeNode, diff: number): void { if (diff === 0) { return; } while (node) { - node.revealedCount += diff; + node.renderNodeCount += diff; node = node.parent; } } @@ -380,7 +380,7 @@ export class IndexTreeModel implements ITreeModel { readonly depth: number; readonly collapsible: boolean; readonly collapsed: boolean; - readonly revealedCount: number; + readonly renderNodeCount: number; readonly visible: boolean; readonly filterData: TFilterData | undefined; } From e6d219aa149d4e6cfa85b09317a5ac71fbc4fd70 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 9 Oct 2018 23:43:49 +0200 Subject: [PATCH 19/35] markers tree: filter count --- src/vs/base/browser/ui/tree/abstractTree.ts | 2 + src/vs/base/browser/ui/tree/dataTree.ts | 20 +++--- src/vs/base/browser/ui/tree/indexTreeModel.ts | 16 +++-- .../base/browser/ui/tree/objectTreeModel.ts | 2 + src/vs/base/browser/ui/tree/tree.ts | 8 +-- src/vs/base/common/event.ts | 5 +- .../markers/electron-browser/markersPanel.ts | 12 ++-- .../electron-browser/markersTreeViewer.ts | 65 +++++++++++++------ 8 files changed, 86 insertions(+), 44 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 3de8f8e4a87c6..291d9052e589a 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -169,6 +169,7 @@ export abstract class AbstractTree implements IDisposable protected disposables: IDisposable[] = []; readonly onDidChangeCollapseState: Event>; + readonly onDidChangeRenderNodeCount: Event>; @memoize get onDidChangeFocus(): Event { return mapEvent(this.view.onFocusChange, e => e.elements.map(e => e.element)); @@ -198,6 +199,7 @@ export abstract class AbstractTree implements IDisposable this.model = this.createModel(this.view, options); onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState; this.onDidChangeCollapseState = this.model.onDidChangeCollapseState; + this.onDidChangeRenderNodeCount = this.model.onDidChangeRenderNodeCount; this.view.onMouseClick(this.onMouseClick, this, this.disposables); diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index c082233b7cf2c..741eb39633d3a 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -6,7 +6,7 @@ import { ITreeOptions, ComposedTreeDelegate, createComposedTreeListOptions } from 'vs/base/browser/ui/tree/abstractTree'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { IVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeElement, ITreeNode, ITreeRenderer, ITreeRenderElement } from 'vs/base/browser/ui/tree/tree'; +import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; @@ -39,11 +39,15 @@ interface IDataTreeListTemplateData { templateData: T; } -function unpack(node: ITreeRenderElement, TFilterData>): ITreeRenderElement { - return { - get element() { return node.element.element; }, - get filterData() { return node.filterData; } - }; +function unpack(node: ITreeNode, TFilterData>): ITreeNode { + return new Proxy(Object.create(null), { + get: (_: any, name: string) => { + switch (name) { + case 'element': return node.element.element; + default: return node[name]; + } + } + }); } class DataTreeRenderer implements ITreeRenderer, TFilterData, IDataTreeListTemplateData> { @@ -64,7 +68,7 @@ class DataTreeRenderer implements ITreeRenderer, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { + renderElement(element: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { this.renderer.renderElement(unpack(element), index, templateData.templateData); } @@ -77,7 +81,7 @@ class DataTreeRenderer implements ITreeRenderer, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { + disposeElement(element: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { this.renderer.disposeElement(unpack(element), index, templateData.templateData); } diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index fc26697285703..c8eed42dfa6e1 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -5,7 +5,7 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { Iterator, ISequence } from 'vs/base/common/iterator'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter, Event, EventBufferer } from 'vs/base/common/event'; import { tail2 } from 'vs/base/common/arrays'; import { ITreeFilterDataResult, TreeVisibility, ITreeFilter, ITreeOptions, ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree'; @@ -56,8 +56,13 @@ export class IndexTreeModel implements ITreeModel>(); - readonly onDidChangeCollapseState: Event> = this._onDidChangeCollapseState.event; + readonly onDidChangeCollapseState: Event> = this.eventBufferer.wrapEvent(this._onDidChangeCollapseState.event); + + private _onDidChangeRenderNodeCount = new Emitter>(); + readonly onDidChangeRenderNodeCount: Event> = this.eventBufferer.wrapEvent(this._onDidChangeRenderNodeCount.event); private filter?: ITreeFilter; @@ -116,12 +121,12 @@ export class IndexTreeModel implements ITreeModel this._setCollapsed(node, listIndex, revealed, collapsed)); } toggleCollapsed(location: number[]): void { const { node, listIndex, revealed } = this.getNodeWithListIndex(location); - this._setCollapsed(node, listIndex, revealed); + this.eventBufferer.bufferEvents(() => this._setCollapsed(node, listIndex, revealed)); } // // TODO@joao cleanup @@ -259,6 +264,7 @@ export class IndexTreeModel implements ITreeModel implements ITreeModel implements ITreeModel, TFilterData = void> imp private nodes = new Map>(); readonly onDidChangeCollapseState: Event>; + readonly onDidChangeRenderNodeCount: Event>; get size(): number { return this.nodes.size; } constructor(list: ISpliceable>, options: IObjectTreeModelOptions = {}) { this.model = new IndexTreeModel(list, options); this.onDidChangeCollapseState = this.model.onDidChangeCollapseState; + this.onDidChangeRenderNodeCount = this.model.onDidChangeRenderNodeCount; } setChildren(element: T | null, children?: ISequence>): Iterator> { diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index cd0cbd0ed11ea..c808c77840ef5 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -49,6 +49,7 @@ export interface ITreeNode { export interface ITreeModel { readonly onDidChangeCollapseState: Event>; + readonly onDidChangeRenderNodeCount: Event>; getListIndex(ref: TRef): number; setCollapsed(ref: TRef, collapsed: boolean): boolean; @@ -64,12 +65,7 @@ export interface ITreeModel { getLastElementAncestor(location: TRef): T | null; } -export interface ITreeRenderElement { - readonly element: T; - readonly filterData: TFilterData | undefined; -} - -export interface ITreeRenderer extends IRenderer, TTemplateData> { +export interface ITreeRenderer extends IRenderer, TTemplateData> { renderTwistie?(element: T, twistieElement: HTMLElement): boolean; onDidChangeTwistieState?: Event; } \ No newline at end of file diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 431960a43f2e6..2238586c097a5 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -398,12 +398,13 @@ export class EventBufferer { }; } - bufferEvents(fn: () => void): void { + bufferEvents(fn: () => R): R { const buffer: Function[] = []; this.buffers.push(buffer); - fn(); + const r = fn(); this.buffers.pop(); buffer.forEach(flush => flush()); + return r; } } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index fc6a545875190..6c06bb14f9ac3 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -28,8 +28,8 @@ import { Scope } from 'vs/workbench/common/memento'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Iterator } from 'vs/base/common/iterator'; -import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; -import { debounceEvent } from 'vs/base/common/event'; +import { ITreeElement, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { debounceEvent, Relay } from 'vs/base/common/event'; import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; import { IExpression, getEmptyExpression } from 'vs/base/common/glob'; @@ -280,10 +280,12 @@ export class MarkersPanel extends Panel { // const dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, obj => obj instanceof ResourceMarkers ? obj.resource : void 0); // const controller = this.instantiationService.createInstance(Controller, () => this.focusFilter()); + const onDidChangeRenderNodeCount = new Relay>(); + const virtualDelegate = new Viewer.VirtualDelegate(); const renderers = [ - this.instantiationService.createInstance(Viewer.FileResourceMarkersRenderer), - this.instantiationService.createInstance(Viewer.ResourceMarkersRenderer), + this.instantiationService.createInstance(Viewer.FileResourceMarkersRenderer, onDidChangeRenderNodeCount.event), + this.instantiationService.createInstance(Viewer.ResourceMarkersRenderer, onDidChangeRenderNodeCount.event), this.instantiationService.createInstance(Viewer.MarkerRenderer, a => this.getActionItem(a)), this.instantiationService.createInstance(Viewer.RelatedInformationRenderer) ]; @@ -298,6 +300,8 @@ export class MarkersPanel extends Panel { } ) as any as WorkbenchObjectTree; + onDidChangeRenderNodeCount.input = this.tree.onDidChangeRenderNodeCount; + // this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { // dataSource: new Viewer.DataSource(), // renderer, diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index edb5de3814f2f..0ed1a62ea488f 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -16,15 +16,16 @@ import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { QuickFixAction } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname } from 'vs/base/common/resources'; import { IVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeFilter, TreeVisibility, TreeFilterResult, ITreeRenderer, ITreeRenderElement } from 'vs/base/browser/ui/tree/tree'; +import { ITreeFilter, TreeVisibility, TreeFilterResult, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; import { IMatch } from 'vs/base/common/filters'; +import { Event } from 'vs/base/common/event'; interface IResourceMarkersTemplateData { resourceLabel: ResourceLabel; @@ -126,11 +127,17 @@ type FilterData = ResourceMarkersFilterData | MarkerFilterData | RelatedInformat export class ResourceMarkersRenderer implements ITreeRenderer { + private renderedNodes = new Map, IResourceMarkersTemplateData>(); + private disposables: IDisposable[] = []; + constructor( + onDidChangeRenderNodeCount: Event>, @IInstantiationService protected instantiationService: IInstantiationService, @IThemeService private themeService: IThemeService, @ILabelService private labelService: ILabelService - ) { } + ) { + onDidChangeRenderNodeCount(this.onDidChangeRenderNodeCount, this, this.disposables); + } templateId = TemplateId.ResourceMarkers; @@ -147,10 +154,9 @@ export class ResourceMarkersRenderer implements ITreeRenderer, _: number, templateData: IResourceMarkersTemplateData): void { - const resourceMarkers = element.element; - const uriMatches = element.filterData && element.filterData.uriMatches || []; + renderElement(node: ITreeNode, _: number, templateData: IResourceMarkersTemplateData): void { + const resourceMarkers = node.element; + const uriMatches = node.filterData && node.filterData.uriMatches || []; if (templateData.resourceLabel instanceof FileLabel) { templateData.resourceLabel.setFile(resourceMarkers.resource, { matches: uriMatches }); @@ -158,11 +164,12 @@ export class ResourceMarkersRenderer implements ITreeRenderer): void { + this.renderedNodes.delete(node); } disposeTemplate(templateData: IResourceMarkersTemplateData): void { @@ -173,6 +180,24 @@ export class ResourceMarkersRenderer implements ITreeRenderer): void { + const templateData = this.renderedNodes.get(node); + + if (!templateData) { + return; + } + + this.updateCount(node, templateData); + } + + private updateCount(node: ITreeNode, templateData: IResourceMarkersTemplateData): void { + templateData.count.setCount(node.children.reduce((r, n) => r + (n.visible ? 1 : 0), 0)); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } } export class FileResourceMarkersRenderer extends ResourceMarkersRenderer { @@ -205,11 +230,11 @@ export class MarkerRenderer implements ITreeRenderer, _: number, templateData: IMarkerTemplateData): void { - const marker = element.element.marker; - const sourceMatches = element.filterData && element.filterData.sourceMatches || []; - const messageMatches = element.filterData && element.filterData.messageMatches || []; - const codeMatches = element.filterData && element.filterData.codeMatches || []; + renderElement(node: ITreeNode, _: number, templateData: IMarkerTemplateData): void { + const marker = node.element.marker; + const sourceMatches = node.filterData && node.filterData.sourceMatches || []; + const messageMatches = node.filterData && node.filterData.messageMatches || []; + const codeMatches = node.filterData && node.filterData.codeMatches || []; templateData.icon.className = 'icon ' + MarkerRenderer.iconClassNameFor(marker); @@ -217,7 +242,7 @@ export class MarkerRenderer implements ITreeRenderer, _: number, templateData: IRelatedInformationTemplateData): void { - const relatedInformation = element.element.raw; - const uriMatches = element.filterData && element.filterData.uriMatches || []; - const messageMatches = element.filterData && element.filterData.messageMatches || []; + renderElement(node: ITreeNode, _: number, templateData: IRelatedInformationTemplateData): void { + const relatedInformation = node.element.raw; + const uriMatches = node.filterData && node.filterData.uriMatches || []; + const messageMatches = node.filterData && node.filterData.messageMatches || []; templateData.resourceLabel.set(paths.basename(relatedInformation.resource.fsPath), uriMatches); templateData.resourceLabel.element.title = this.labelService.getUriLabel(relatedInformation.resource, { relative: true }); From 3a88cefde40f8b138894c82f4ec714250d6c32cc Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 10 Oct 2018 16:20:18 +0200 Subject: [PATCH 20/35] remove unused code --- .../electron-browser/extensionsViews.ts | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index ab9ac9cad4e71..162f8f2bfc74d 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -144,30 +144,6 @@ export class ExtensionsListView extends ViewletPanel { return await this.queryGallery(parsedQuery, options).then(successCallback).catch(errorCallback); } - select(): void { - this.list.setSelection(this.list.getFocus()); - } - - showPrevious(): void { - this.list.focusPrevious(); - this.list.reveal(this.list.getFocus()[0]); - } - - showPreviousPage(): void { - this.list.focusPreviousPage(); - this.list.reveal(this.list.getFocus()[0]); - } - - showNext(): void { - this.list.focusNext(); - this.list.reveal(this.list.getFocus()[0]); - } - - showNextPage(): void { - this.list.focusNextPage(); - this.list.reveal(this.list.getFocus()[0]); - } - count(): number { return this.list.length; } From 3aba97617cf0d7733a9e140b1152961c8dfb88e3 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 11 Oct 2018 12:39:00 +0200 Subject: [PATCH 21/35] remove IListOpenEvent --- src/vs/base/browser/ui/list/list.ts | 7 +------ src/vs/base/browser/ui/list/listPaging.ts | 4 ++-- src/vs/base/browser/ui/list/listWidget.ts | 6 +++--- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 53651ccc2f971..fb1800047c56a 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -19,15 +19,10 @@ export interface IRenderer { disposeTemplate(templateData: TTemplateData): void; } -export interface IListOpenEvent { - elements: T[]; - indexes: number[]; - browserEvent?: UIEvent; -} - export interface IListEvent { elements: T[]; indexes: number[]; + browserEvent?: UIEvent; } export interface IListMouseEvent { diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 98e3acf5bf23e..14ad8c8acd235 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -6,7 +6,7 @@ import 'vs/css!./list'; import { IDisposable } from 'vs/base/common/lifecycle'; import { range } from 'vs/base/common/arrays'; -import { IVirtualDelegate, IRenderer, IListEvent, IListOpenEvent, IListContextMenuEvent } from './list'; +import { IVirtualDelegate, IRenderer, IListEvent, IListContextMenuEvent } from './list'; import { List, IListStyles, IListOptions } from './listWidget'; import { IPagedModel } from 'vs/base/common/paging'; import { Event, mapEvent } from 'vs/base/common/event'; @@ -111,7 +111,7 @@ export class PagedList implements IDisposable { return mapEvent(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } - get onOpen(): Event> { + get onOpen(): Event> { return mapEvent(this.list.onOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent })); } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 2b2ce107d5536..e596bd8f3935d 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -16,7 +16,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Event, Emitter, EventBufferer, chain, mapEvent, anyEvent } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { IVirtualDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListOpenEvent } from './list'; +import { IVirtualDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list'; import { ListView, IListViewOptions } from './listView'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -865,8 +865,8 @@ export class List implements ISpliceable, IDisposable { readonly onContextMenu: Event> = Event.None; - private _onOpen = new Emitter>(); - readonly onOpen: Event> = this._onOpen.event; + private _onOpen = new Emitter>(); + readonly onOpen: Event> = this._onOpen.event; private _onPin = new Emitter(); @memoize get onPin(): Event> { From 52bb5aebbedf25bcf5b65a464ca4b5aeb5c4ec89 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 11 Oct 2018 14:04:44 +0200 Subject: [PATCH 22/35] cleanup --- src/vs/base/browser/ui/tree/abstractTree.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 291d9052e589a..4430f83c849f0 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -275,14 +275,6 @@ export abstract class AbstractTree implements IDisposable this.view.setSelection(indexes); } - selectNext(n = 1, loop = false): void { - this.view.selectNext(n, loop); - } - - selectPrevious(n = 1, loop = false): void { - this.view.selectPrevious(n, loop); - } - getSelection(): T[] { const nodes = this.view.getSelectedElements(); return nodes.map(n => n.element); From afe13242dd7347e6d0a119f7a991d6ccc2e1d30e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 11 Oct 2018 14:05:05 +0200 Subject: [PATCH 23/35] expose browser event in list widget events --- src/vs/base/browser/ui/list/listPaging.ts | 8 --- src/vs/base/browser/ui/list/listWidget.ts | 74 +++++++++-------------- 2 files changed, 29 insertions(+), 53 deletions(-) diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 14ad8c8acd235..780c6396561ce 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -164,14 +164,6 @@ export class PagedList implements IDisposable { this.list.focusPrevious(n, loop); } - selectNext(n?: number, loop?: boolean): void { - this.list.selectNext(n, loop); - } - - selectPrevious(n?: number, loop?: boolean): void { - this.list.selectPrevious(n, loop); - } - focusNextPage(): void { this.list.focusNextPage(); } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index e596bd8f3935d..39ba0119f842c 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -31,6 +31,7 @@ export interface IIdentityProvider { interface ITraitChangeEvent { indexes: number[]; + browserEvent?: UIEvent; } type ITraitTemplateData = HTMLElement; @@ -159,14 +160,14 @@ class Trait implements ISpliceable, IDisposable { * @param indexes Indexes which should have this trait. * @return The old indexes which had this trait. */ - set(indexes: number[]): number[] { + set(indexes: number[], browserEvent?: UIEvent): number[] { const result = this.indexes; this.indexes = indexes; const toRender = disjunction(result, indexes); this.renderer.renderIndexes(toRender); - this._onChange.fire({ indexes }); + this._onChange.fire({ indexes, browserEvent }); return result; } @@ -268,7 +269,7 @@ class KeyboardController implements IDisposable { private onEnter(e: StandardKeyboardEvent): void { e.preventDefault(); e.stopPropagation(); - this.list.setSelection(this.list.getFocus()); + this.list.setSelection(this.list.getFocus(), e.browserEvent); if (this.openController.shouldOpen(e.browserEvent)) { this.list.open(this.list.getFocus(), e.browserEvent); @@ -278,7 +279,7 @@ class KeyboardController implements IDisposable { private onUpArrow(e: StandardKeyboardEvent): void { e.preventDefault(); e.stopPropagation(); - this.list.focusPrevious(); + this.list.focusPrevious(1, false, e.browserEvent); this.list.reveal(this.list.getFocus()[0]); this.view.domNode.focus(); } @@ -286,7 +287,7 @@ class KeyboardController implements IDisposable { private onDownArrow(e: StandardKeyboardEvent): void { e.preventDefault(); e.stopPropagation(); - this.list.focusNext(); + this.list.focusNext(1, false, e.browserEvent); this.list.reveal(this.list.getFocus()[0]); this.view.domNode.focus(); } @@ -294,7 +295,7 @@ class KeyboardController implements IDisposable { private onPageUpArrow(e: StandardKeyboardEvent): void { e.preventDefault(); e.stopPropagation(); - this.list.focusPreviousPage(); + this.list.focusPreviousPage(e.browserEvent); this.list.reveal(this.list.getFocus()[0]); this.view.domNode.focus(); } @@ -302,7 +303,7 @@ class KeyboardController implements IDisposable { private onPageDownArrow(e: StandardKeyboardEvent): void { e.preventDefault(); e.stopPropagation(); - this.list.focusNextPage(); + this.list.focusNextPage(e.browserEvent); this.list.reveal(this.list.getFocus()[0]); this.view.domNode.focus(); } @@ -310,14 +311,14 @@ class KeyboardController implements IDisposable { private onCtrlA(e: StandardKeyboardEvent): void { e.preventDefault(); e.stopPropagation(); - this.list.setSelection(range(this.list.length)); + this.list.setSelection(range(this.list.length), e.browserEvent); this.view.domNode.focus(); } private onEscape(e: StandardKeyboardEvent): void { e.preventDefault(); e.stopPropagation(); - this.list.setSelection([]); + this.list.setSelection([], e.browserEvent); this.view.domNode.focus(); } @@ -502,7 +503,7 @@ class MouseController implements IDisposable { const focus = e.index; if (selection.every(s => s !== focus)) { - this.list.setFocus([focus]); + this.list.setFocus([focus], e.browserEvent); } if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) { @@ -510,7 +511,7 @@ class MouseController implements IDisposable { } if (this.options.selectOnMouseDown && !isMouseRightClick(e.browserEvent)) { - this.list.setSelection([focus]); + this.list.setSelection([focus], e.browserEvent); if (this.openController.shouldOpen(e.browserEvent)) { this.list.open([focus], e.browserEvent); @@ -525,7 +526,7 @@ class MouseController implements IDisposable { if (!this.options.selectOnMouseDown) { const focus = this.list.getFocus(); - this.list.setSelection(focus); + this.list.setSelection(focus, e.browserEvent); if (this.openController.shouldOpen(e.browserEvent)) { this.list.open(focus, e.browserEvent); @@ -539,7 +540,7 @@ class MouseController implements IDisposable { } const focus = this.list.getFocus(); - this.list.setSelection(focus); + this.list.setSelection(focus, e.browserEvent); this.list.pin(focus); } @@ -558,16 +559,16 @@ class MouseController implements IDisposable { } const newSelection = disjunction(rangeSelection, relativeComplement(selection, contiguousRange)); - this.list.setSelection(newSelection); + this.list.setSelection(newSelection, e.browserEvent); } else if (this.isSelectionSingleChangeEvent(e)) { const selection = this.list.getSelection(); const newSelection = selection.filter(i => i !== focus); if (selection.length === newSelection.length) { - this.list.setSelection([...newSelection, focus]); + this.list.setSelection([...newSelection, focus], e.browserEvent); } else { - this.list.setSelection(newSelection); + this.list.setSelection(newSelection, e.browserEvent); } } } @@ -993,7 +994,7 @@ export class List implements ISpliceable, IDisposable { this.view.layout(height); } - setSelection(indexes: number[]): void { + setSelection(indexes: number[], browserEvent?: UIEvent): void { for (const index of indexes) { if (index < 0 || index >= this.length) { throw new Error(`Invalid index ${index}`); @@ -1001,24 +1002,7 @@ export class List implements ISpliceable, IDisposable { } indexes = indexes.sort(numericSort); - this.selection.set(indexes); - } - - selectNext(n = 1, loop = false): void { - if (this.length === 0) { return; } - const selection = this.selection.get(); - let index = selection.length > 0 ? selection[0] + n : 0; - this.setSelection(loop ? [index % this.length] : [Math.min(index, this.length - 1)]); - } - - selectPrevious(n = 1, loop = false): void { - if (this.length === 0) { return; } - const selection = this.selection.get(); - let index = selection.length > 0 ? selection[0] - n : 0; - if (loop && index < 0) { - index = this.length + (index % this.length); - } - this.setSelection([Math.max(index, 0)]); + this.selection.set(indexes, browserEvent); } getSelection(): number[] { @@ -1029,7 +1013,7 @@ export class List implements ISpliceable, IDisposable { return this.getSelection().map(i => this.view.element(i)); } - setFocus(indexes: number[]): void { + setFocus(indexes: number[], browserEvent?: UIEvent): void { for (const index of indexes) { if (index < 0 || index >= this.length) { throw new Error(`Invalid index ${index}`); @@ -1037,17 +1021,17 @@ export class List implements ISpliceable, IDisposable { } indexes = indexes.sort(numericSort); - this.focus.set(indexes); + this.focus.set(indexes, browserEvent); } - focusNext(n = 1, loop = false): void { + focusNext(n = 1, loop = false, browserEvent?: UIEvent): void { if (this.length === 0) { return; } const focus = this.focus.get(); let index = focus.length > 0 ? focus[0] + n : 0; this.setFocus(loop ? [index % this.length] : [Math.min(index, this.length - 1)]); } - focusPrevious(n = 1, loop = false): void { + focusPrevious(n = 1, loop = false, browserEvent?: UIEvent): void { if (this.length === 0) { return; } const focus = this.focus.get(); let index = focus.length > 0 ? focus[0] - n : 0; @@ -1055,7 +1039,7 @@ export class List implements ISpliceable, IDisposable { this.setFocus([Math.max(index, 0)]); } - focusNextPage(): void { + focusNextPage(browserEvent?: UIEvent): void { let lastPageIndex = this.view.indexAt(this.view.getScrollTop() + this.view.renderHeight); lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1; const lastPageElement = this.view.element(lastPageIndex); @@ -1074,7 +1058,7 @@ export class List implements ISpliceable, IDisposable { } } - focusPreviousPage(): void { + focusPreviousPage(browserEvent?: UIEvent): void { let firstPageIndex: number; const scrollTop = this.view.getScrollTop(); @@ -1100,12 +1084,12 @@ export class List implements ISpliceable, IDisposable { } } - focusLast(): void { + focusLast(browserEvent?: UIEvent): void { if (this.length === 0) { return; } this.setFocus([this.length - 1]); } - focusFirst(): void { + focusFirst(browserEvent?: UIEvent): void { if (this.length === 0) { return; } this.setFocus([0]); } @@ -1201,8 +1185,8 @@ export class List implements ISpliceable, IDisposable { this.styleController.style(styles); } - private toListEvent({ indexes }: ITraitChangeEvent) { - return { indexes, elements: indexes.map(i => this.view.element(i)) }; + private toListEvent({ indexes, browserEvent }: ITraitChangeEvent) { + return { indexes, elements: indexes.map(i => this.view.element(i)), browserEvent }; } private _onFocusChange(): void { From 4728d11f8d812cbfc37cf81e61616641147df092 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 11 Oct 2018 17:25:30 +0200 Subject: [PATCH 24/35] markers panel: finally mouse and keyboard navigation very very hacky --- src/vs/base/browser/ui/tree/abstractTree.ts | 23 ++-- src/vs/platform/list/browser/listService.ts | 107 ++++++++++++++++-- .../markers/electron-browser/markersPanel.ts | 10 +- .../electron-browser/markersTreeController.ts | 3 +- 4 files changed, 112 insertions(+), 31 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 4430f83c849f0..d47a7ac845e5a 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -6,15 +6,14 @@ import 'vs/css!./tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IListOptions, List, IIdentityProvider, IMultipleSelectionController, IListStyles } from 'vs/base/browser/ui/list/listWidget'; -import { IVirtualDelegate, IRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list'; +import { IVirtualDelegate, IRenderer, IListMouseEvent, IListEvent } from 'vs/base/browser/ui/list/list'; import { append, $ } from 'vs/base/browser/dom'; -import { Event, Relay, chain, mapEvent } from 'vs/base/common/event'; +import { Event, Relay, chain } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ITreeModel, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel'; -import { memoize } from 'vs/base/common/decorators'; export function createComposedTreeListOptions(options?: IListOptions): IListOptions { if (!options) { @@ -161,6 +160,7 @@ function isInputElement(e: HTMLElement): boolean { } export interface ITreeOptions extends IListOptions, IIndexTreeModelOptions { } +export interface ITreeEvent extends IListEvent> { } export abstract class AbstractTree implements IDisposable { @@ -170,14 +170,8 @@ export abstract class AbstractTree implements IDisposable readonly onDidChangeCollapseState: Event>; readonly onDidChangeRenderNodeCount: Event>; - - @memoize get onDidChangeFocus(): Event { - return mapEvent(this.view.onFocusChange, e => e.elements.map(e => e.element)); - } - - @memoize get onDidChangeSelection(): Event { - return mapEvent(this.view.onSelectionChange, e => e.elements.map(e => e.element)); - } + readonly onDidChangeFocus: Event>; + readonly onDidChangeSelection: Event>; get onDidFocus(): Event { return this.view.onDidFocus; } get onDidBlur(): Event { return this.view.onDidBlur; } @@ -196,6 +190,9 @@ export abstract class AbstractTree implements IDisposable this.disposables.push(...treeRenderers); this.view = new List(container, treeDelegate, treeRenderers, createComposedTreeListOptions>(options)); + this.onDidChangeFocus = this.view.onFocusChange; + this.onDidChangeSelection = this.view.onSelectionChange; + this.model = this.createModel(this.view, options); onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState; this.onDidChangeCollapseState = this.model.onDidChangeCollapseState; @@ -270,9 +267,9 @@ export abstract class AbstractTree implements IDisposable this.model.refilter(); } - setSelection(elements: TRef[]): void { + setSelection(elements: TRef[], browserEvent?: UIEvent): void { const indexes = elements.map(e => this.model.getListIndex(e)); - this.view.setSelection(indexes); + this.view.setSelection(indexes, browserEvent); } getSelection(): T[] { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 75c937010bb72..cd66bf3e9d9bf 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -33,7 +33,7 @@ import { attachInputBoxStyler, attachListStyler, computeStyles, defaultListStyle import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; -import { ITreeOptions as ITreeOptions2 } from 'vs/base/browser/ui/tree/abstractTree'; +import { ITreeOptions as ITreeOptions2, ITreeEvent } from 'vs/base/browser/ui/tree/abstractTree'; import { ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; export type ListWidget = List | PagedList | ITree | ObjectTree; @@ -565,6 +565,84 @@ export class TreeResourceNavigator extends Disposable { } } +export interface IOpenEvent { + editorOptions: IEditorOptions; + sideBySide: boolean; + element: T; +} + +export interface IResourceResultsNavigationOptions { + openOnFocus: boolean; +} + +export class ObjectTreeResourceNavigator extends Disposable { + + private readonly _openResource: Emitter> = new Emitter>(); + readonly openResource: Event> = this._openResource.event; + + constructor(private tree: WorkbenchObjectTree, private options?: IResourceResultsNavigationOptions) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + if (this.options && this.options.openOnFocus) { + this._register(this.tree.onDidChangeFocus(e => this.onFocus(e))); + } + + this._register(this.tree.onDidChangeSelection(e => this.onSelection(e))); + } + + private onFocus(e: ITreeEvent): void { + const focus = this.tree.getFocus(); + + this.tree.setSelection(focus, e.browserEvent); + + const isMouseEvent = e.browserEvent instanceof MouseEvent; + const isDoubleClick = isMouseEvent && e.browserEvent && e.browserEvent.detail === 2; + + if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) { + this._openResource.fire({ + editorOptions: { + preserveFocus: true, + pinned: false, + revealIfVisible: true + }, + sideBySide: false, + element: focus[0] + }); + } + } + + private onSelection(e: ITreeEvent): void { + const isMouseEvent = e.browserEvent && e.browserEvent instanceof MouseEvent; + if (!isMouseEvent || this.tree.openOnSingleClick) { + return; + } + + const isDoubleClick = isMouseEvent && e.browserEvent && e.browserEvent.detail === 2; + + if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) { + if (isDoubleClick && e.browserEvent) { + e.browserEvent.preventDefault(); // focus moves to editor, we need to prevent default + } + + const sideBySide = e.browserEvent && e.browserEvent instanceof KeyboardEvent && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey); + + this._openResource.fire({ + editorOptions: { + preserveFocus: isDoubleClick, + pinned: isDoubleClick, + revealIfVisible: true + }, + sideBySide, + element: this.tree.getSelection()[0] + }); + } + } +} + export interface IHighlighter { getHighlights(tree: ITree, element: any, pattern: string): FuzzyScore; getHighlightsStorageKey?(element: any): any; @@ -810,6 +888,7 @@ export class WorkbenchObjectTree, TFilterData = void> private hasDoubleSelection: IContextKey; private hasMultiSelection: IContextKey; + private _openOnSingleClick: boolean; private _useAltAsMultipleSelectionModifier: boolean; constructor( @@ -820,7 +899,7 @@ export class WorkbenchObjectTree, TFilterData = void> @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, - @IConfigurationService private configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService ) { super(container, delegate, renderers, { keyboardSupport: false, @@ -839,9 +918,10 @@ export class WorkbenchObjectTree, TFilterData = void> this.hasDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService); this.hasMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService); + this._openOnSingleClick = useSingleClickToOpen(configurationService); this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); - this.disposables.push(combinedDisposable([ + this.disposables.push( this.contextKeyService, (listService as ListService).register(this), attachListStyler(this, themeService), @@ -858,18 +938,21 @@ export class WorkbenchObjectTree, TFilterData = void> const focus = this.getFocus(); this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0); - }) - ])); + }), + configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(openModeSettingKey)) { + this._openOnSingleClick = useSingleClickToOpen(configurationService); + } - this.registerListeners(); + if (e.affectsConfiguration(multiSelectModifierSettingKey)) { + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); + } + }) + ); } - private registerListeners(): void { - this.disposables.push(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(multiSelectModifierSettingKey)) { - this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService); - } - })); + get openOnSingleClick(): boolean { + return this._openOnSingleClick; } get useAltAsMultipleSelectionModifier(): boolean { diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 6c06bb14f9ac3..bdeacd24cc3da 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -30,7 +30,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { debounceEvent, Relay } from 'vs/base/common/event'; -import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { WorkbenchObjectTree, ObjectTreeResourceNavigator } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; import { IExpression, getEmptyExpression } from 'vs/base/common/glob'; import { mixin, deepClone } from 'vs/base/common/objects'; @@ -325,10 +325,10 @@ export class MarkersPanel extends Panel { // relatedInformationFocusContextKey.set(false); // })); - // const markersNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); - // this._register(debounceEvent(markersNavigator.openResource, (last, event) => event, 75, true)(options => { - // this.openFileAtElement(options.element, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned); - // })); + const markersNavigator = this._register(new ObjectTreeResourceNavigator(this.tree, { openOnFocus: true })); + this._register(debounceEvent(markersNavigator.openResource, (last, event) => event, 75, true)(options => { + this.openFileAtElement(options.element, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned); + })); } // TODO@joao diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts index 4ec0d2fb1ac54..88accb29a8777 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts @@ -61,8 +61,9 @@ export class Controller extends WorkbenchTreeController { return false; } + // TODO@Joao public onContextMenu(tree: WorkbenchTree, element: any, event: tree.ContextMenuEvent): boolean { - tree.setFocus(element, { preventOpenOnFocus: true }); + tree.setFocus(element/* , { preventOpenOnFocus: true } */); const anchor = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ From b885360659dcef8f10d26773f0a7dc5d26e9648a Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 11 Oct 2018 17:31:03 +0200 Subject: [PATCH 25/35] markers panel: bring back context keys --- .../markers/electron-browser/markersPanel.ts | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index bdeacd24cc3da..96b1e48f27de7 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -274,11 +274,8 @@ export class MarkersPanel extends Panel { this.ariaLabelElement.setAttribute('aria-live', 'polite'); } - // TODO@joao private createTree(parent: HTMLElement): void { this.treeContainer = dom.append(parent, dom.$('.tree-container.show-file-icons')); - // const dnd = this.instantiationService.createInstance(SimpleFileResourceDragAndDrop, obj => obj instanceof ResourceMarkers ? obj.resource : void 0); - // const controller = this.instantiationService.createInstance(Controller, () => this.focusFilter()); const onDidChangeRenderNodeCount = new Relay>(); @@ -302,28 +299,17 @@ export class MarkersPanel extends Panel { onDidChangeRenderNodeCount.input = this.tree.onDidChangeRenderNodeCount; - // this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { - // dataSource: new Viewer.DataSource(), - // renderer, - // controller, - // accessibilityProvider: this.instantiationService.createInstance(Viewer.MarkersTreeAccessibilityProvider), - // dnd - // }, { - // twistiePixels: 20, - // ariaLabel: Messages.MARKERS_PANEL_ARIA_LABEL_PROBLEMS_TREE - // }); - - // const markerFocusContextKey = Constants.MarkerFocusContextKey.bindTo(this.tree.contextKeyService); - // const relatedInformationFocusContextKey = Constants.RelatedInformationFocusContextKey.bindTo(this.tree.contextKeyService); - // this._register(this.tree.onDidChangeFocus(elements => { - // markerFocusContextKey.set(elements.some(e => e instanceof Marker)); - // relatedInformationFocusContextKey.set(elements.some(e => e instanceof RelatedInformation)); - // })); - // const focusTracker = this._register(dom.trackFocus(this.tree.getHTMLElement())); - // this._register(focusTracker.onDidBlur(() => { - // markerFocusContextKey.set(false); - // relatedInformationFocusContextKey.set(false); - // })); + const markerFocusContextKey = Constants.MarkerFocusContextKey.bindTo(this.tree.contextKeyService); + const relatedInformationFocusContextKey = Constants.RelatedInformationFocusContextKey.bindTo(this.tree.contextKeyService); + this._register(this.tree.onDidChangeFocus(focus => { + markerFocusContextKey.set(focus.elements.some(e => e.element instanceof Marker)); + relatedInformationFocusContextKey.set(focus.elements.some(e => e.element instanceof RelatedInformation)); + })); + const focusTracker = this._register(dom.trackFocus(this.tree.getHTMLElement())); + this._register(focusTracker.onDidBlur(() => { + markerFocusContextKey.set(false); + relatedInformationFocusContextKey.set(false); + })); const markersNavigator = this._register(new ObjectTreeResourceNavigator(this.tree, { openOnFocus: true })); this._register(debounceEvent(markersNavigator.openResource, (last, event) => event, 75, true)(options => { From 9b1013acc0a29d3fb2db05be9731bebcc50f1e4a Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 11 Oct 2018 17:54:52 +0200 Subject: [PATCH 26/35] markers context menus --- src/vs/base/browser/ui/list/list.ts | 1 + src/vs/base/browser/ui/list/listPaging.ts | 2 +- src/vs/base/browser/ui/list/listWidget.ts | 14 ++- src/vs/base/browser/ui/tree/abstractTree.ts | 6 +- .../markers/electron-browser/markersPanel.ts | 84 +++++++++++-- .../electron-browser/markersTreeController.ts | 117 ------------------ .../electron-browser/markersTreeViewer.ts | 2 +- 7 files changed, 91 insertions(+), 135 deletions(-) delete mode 100644 src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index fb1800047c56a..add0f1682938e 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -44,6 +44,7 @@ export interface IListGestureEvent { } export interface IListContextMenuEvent { + browserEvent: UIEvent; element: T; index: number; anchor: HTMLElement | { x: number; y: number; }; diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 780c6396561ce..14bf8b9404c4e 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -124,7 +124,7 @@ export class PagedList implements IDisposable { } get onContextMenu(): Event> { - return mapEvent(this.list.onContextMenu, ({ element, index, anchor }) => ({ element: this._model.get(element), index, anchor })); + return mapEvent(this.list.onContextMenu, ({ element, index, anchor, browserEvent }) => ({ element: this._model.get(element), index, anchor, browserEvent })); } get model(): IPagedModel { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 39ba0119f842c..47631a32dac87 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -418,7 +418,13 @@ class MouseController implements IDisposable { .map(e => new StandardKeyboardEvent(e)) .filter(e => this.didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) .filter(e => { e.preventDefault(); e.stopPropagation(); return false; }) - .event as Event; + .map(event => { + const index = this.list.getFocus()[0]; + const element = this.view.element(index); + const anchor = this.view.domElement(index); + return { index, element, anchor, browserEvent: event.browserEvent }; + }) + .event; const fromKeyup = chain(domEvent(this.view.domNode, 'keyup')) .filter(() => { @@ -427,18 +433,18 @@ class MouseController implements IDisposable { return didJustPressContextMenuKey; }) .filter(() => this.list.getFocus().length > 0) - .map(() => { + .map(browserEvent => { const index = this.list.getFocus()[0]; const element = this.view.element(index); const anchor = this.view.domElement(index); - return { index, element, anchor }; + return { index, element, anchor, browserEvent }; }) .filter(({ anchor }) => !!anchor) .event; const fromMouse = chain(this.view.onContextMenu) .filter(() => !this.didJustPressContextMenuKey) - .map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.clientX + 1, y: browserEvent.clientY } })) + .map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.clientX + 1, y: browserEvent.clientY }, browserEvent })) .event; return anyEvent>(fromKeydown, fromKeyup, fromMouse); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index d47a7ac845e5a..feaf5c7fd6e66 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -6,7 +6,7 @@ import 'vs/css!./tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IListOptions, List, IIdentityProvider, IMultipleSelectionController, IListStyles } from 'vs/base/browser/ui/list/listWidget'; -import { IVirtualDelegate, IRenderer, IListMouseEvent, IListEvent } from 'vs/base/browser/ui/list/list'; +import { IVirtualDelegate, IRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { append, $ } from 'vs/base/browser/dom'; import { Event, Relay, chain } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -161,6 +161,7 @@ function isInputElement(e: HTMLElement): boolean { export interface ITreeOptions extends IListOptions, IIndexTreeModelOptions { } export interface ITreeEvent extends IListEvent> { } +export interface ITreeContextMenuEvent extends IListContextMenuEvent> { } export abstract class AbstractTree implements IDisposable { @@ -173,6 +174,8 @@ export abstract class AbstractTree implements IDisposable readonly onDidChangeFocus: Event>; readonly onDidChangeSelection: Event>; + readonly onContextMenu: Event>; + get onDidFocus(): Event { return this.view.onDidFocus; } get onDidBlur(): Event { return this.view.onDidBlur; } get onDidDispose(): Event { return this.view.onDidDispose; } @@ -192,6 +195,7 @@ export abstract class AbstractTree implements IDisposable this.view = new List(container, treeDelegate, treeRenderers, createComposedTreeListOptions>(options)); this.onDidChangeFocus = this.view.onFocusChange; this.onDidChangeSelection = this.view.onSelectionChange; + this.onContextMenu = this.view.onContextMenu; this.model = this.createModel(this.view, options); onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState; diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 96b1e48f27de7..5620e3a685f59 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -14,7 +14,6 @@ import { Panel } from 'vs/workbench/browser/panel'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; import { Marker, ResourceMarkers, RelatedInformation, MarkersModel } from 'vs/workbench/parts/markers/electron-browser/markersModel'; -import * as Viewer from 'vs/workbench/parts/markers/electron-browser/markersTreeViewer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MarkersFilterActionItem, MarkersFilterAction, QuickFixAction, QuickFixActionItem, IMarkersFilterActionChangeEvent } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -36,6 +35,12 @@ import { IExpression, getEmptyExpression } from 'vs/base/common/glob'; import { mixin, deepClone } from 'vs/base/common/objects'; import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isAbsolute, join } from 'vs/base/common/paths'; +import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/abstractTree'; +import { FilterData, FileResourceMarkersRenderer, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer } from 'vs/workbench/parts/markers/electron-browser/markersTreeViewer'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { Separator, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -61,7 +66,7 @@ export class MarkersPanel extends Panel { private lastSelectedRelativeTop: number = 0; private currentActiveResource: URI = null; - private tree: WorkbenchObjectTree; + private tree: WorkbenchObjectTree; private rangeHighlightDecorations: RangeHighlightDecorations; private actions: IAction[]; @@ -75,7 +80,7 @@ export class MarkersPanel extends Panel { private panelSettings: any; private panelFoucusContextKey: IContextKey; - private filter: Viewer.Filter; + private filter: Filter; private currentResourceGotAddedToMarkersData: boolean = false; @@ -89,6 +94,9 @@ export class MarkersPanel extends Panel { @IStorageService storageService: IStorageService, @IContextKeyService contextKeyService: IContextKeyService, @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + @IContextMenuService private contextMenuService: IContextMenuService, + @IMenuService private menuService: IMenuService, + @IKeybindingService private keybindingService: IKeybindingService, ) { super(Constants.MARKERS_PANEL_ID, telemetryService, themeService); this.panelFoucusContextKey = Constants.MarkerPanelFocusContextKey.bindTo(contextKeyService); @@ -279,14 +287,14 @@ export class MarkersPanel extends Panel { const onDidChangeRenderNodeCount = new Relay>(); - const virtualDelegate = new Viewer.VirtualDelegate(); + const virtualDelegate = new VirtualDelegate(); const renderers = [ - this.instantiationService.createInstance(Viewer.FileResourceMarkersRenderer, onDidChangeRenderNodeCount.event), - this.instantiationService.createInstance(Viewer.ResourceMarkersRenderer, onDidChangeRenderNodeCount.event), - this.instantiationService.createInstance(Viewer.MarkerRenderer, a => this.getActionItem(a)), - this.instantiationService.createInstance(Viewer.RelatedInformationRenderer) + this.instantiationService.createInstance(FileResourceMarkersRenderer, onDidChangeRenderNodeCount.event), + this.instantiationService.createInstance(ResourceMarkersRenderer, onDidChangeRenderNodeCount.event), + this.instantiationService.createInstance(MarkerRenderer, a => this.getActionItem(a)), + this.instantiationService.createInstance(RelatedInformationRenderer) ]; - this.filter = new Viewer.Filter(); + this.filter = new Filter(); this.tree = this.instantiationService.createInstance(WorkbenchObjectTree, this.treeContainer, @@ -295,7 +303,7 @@ export class MarkersPanel extends Panel { { filter: this.filter } - ) as any as WorkbenchObjectTree; + ) as any as WorkbenchObjectTree; onDidChangeRenderNodeCount.input = this.tree.onDidChangeRenderNodeCount; @@ -315,6 +323,8 @@ export class MarkersPanel extends Panel { this._register(debounceEvent(markersNavigator.openResource, (last, event) => event, 75, true)(options => { this.openFileAtElement(options.element, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned); })); + + this.tree.onContextMenu(this.onContextMenu, this, this._toDispose); } // TODO@joao @@ -525,7 +535,59 @@ export class MarkersPanel extends Panel { this.rangeHighlightDecorations.highlightRange(selection); } - public getFocusElement(): ResourceMarkers | Marker | RelatedInformation { + private onContextMenu(e: ITreeContextMenuEvent): void { + if (!e.element) { + return; + } + + e.browserEvent.preventDefault(); + e.browserEvent.stopPropagation(); + + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => TPromise.wrap(this._getMenuActions(e.element.element)), + getActionItem: (action) => { + const keybinding = this.keybindingService.lookupKeybinding(action.id); + if (keybinding) { + return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + } + return null; + }, + onHide: (wasCancelled?: boolean) => { + if (wasCancelled) { + this.tree.domFocus(); + } + } + }); + } + + private async _getMenuActions(element: TreeElement): Promise { + const result: IAction[] = []; + + if (element instanceof Marker) { + const quickFixAction = this.instantiationService.createInstance(QuickFixAction, element); + const quickFixActions = await quickFixAction.getQuickFixActions(); + if (quickFixActions.length) { + result.push(...quickFixActions); + result.push(new Separator()); + } + } + + const menu = this.menuService.createMenu(MenuId.ProblemsPanelContext, this.tree.contextKeyService); + const groups = menu.getActions(); + menu.dispose(); + + for (let group of groups) { + const [, actions] = group; + result.push(...actions); + result.push(new Separator()); + } + + result.pop(); // remove last separator + return result; + } + + public getFocusElement(): TreeElement { return this.tree.getFocus()[0]; } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts deleted file mode 100644 index 88accb29a8777..0000000000000 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts +++ /dev/null @@ -1,117 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { TPromise } from 'vs/base/common/winjs.base'; -import * as mouse from 'vs/base/browser/mouseEvent'; -import * as tree from 'vs/base/parts/tree/browser/tree'; -import { MarkersModel, Marker } from 'vs/workbench/parts/markers/electron-browser/markersModel'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { IAction } from 'vs/base/common/actions'; -import { ActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { QuickFixAction } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; - -export class Controller extends WorkbenchTreeController { - - constructor( - private readonly onType: () => any, - @IContextMenuService private contextMenuService: IContextMenuService, - @IMenuService private menuService: IMenuService, - @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService private instantiationService: IInstantiationService - ) { - super({}, configurationService); - } - - onKeyDown(tree: tree.ITree, event: IKeyboardEvent) { - let handled = super.onKeyDown(tree, event); - if (handled) { - return true; - } - if (this.upKeyBindingDispatcher.has(event.keyCode)) { - return false; - } - if (this._keybindingService.mightProducePrintableCharacter(event)) { - this.onType(); - return true; - } - return false; - } - - protected onLeftClick(tree: tree.ITree, element: any, event: mouse.IMouseEvent): boolean { - let currentFoucssed = tree.getFocus(); - if (super.onLeftClick(tree, element, event)) { - if (element instanceof MarkersModel) { - if (currentFoucssed) { - tree.setFocus(currentFoucssed); - } else { - tree.focusFirst(); - } - } - return true; - } - return false; - } - - // TODO@Joao - public onContextMenu(tree: WorkbenchTree, element: any, event: tree.ContextMenuEvent): boolean { - tree.setFocus(element/* , { preventOpenOnFocus: true } */); - - const anchor = { x: event.posx, y: event.posy }; - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - - getActions: () => TPromise.wrap(this._getMenuActions(tree, element)), - - getActionItem: (action) => { - const keybinding = this._keybindingService.lookupKeybinding(action.id); - if (keybinding) { - return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() }); - } - return null; - }, - - onHide: (wasCancelled?: boolean) => { - if (wasCancelled) { - tree.domFocus(); - } - } - }); - - return true; - } - - private async _getMenuActions(tree: WorkbenchTree, element: any): Promise { - const result: IAction[] = []; - - if (element instanceof Marker) { - const quickFixAction = this.instantiationService.createInstance(QuickFixAction, element); - const quickFixActions = await quickFixAction.getQuickFixActions(); - if (quickFixActions.length) { - result.push(...quickFixActions); - result.push(new Separator()); - } - } - - const menu = this.menuService.createMenu(MenuId.ProblemsPanelContext, tree.contextKeyService); - const groups = menu.getActions(); - menu.dispose(); - - for (let group of groups) { - const [, actions] = group; - result.push(...actions); - result.push(new Separator()); - } - - result.pop(); // remove last separator - return result; - } -} diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 0ed1a62ea488f..e21f5a96a638e 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -123,7 +123,7 @@ interface RelatedInformationFilterData { messageMatches: IMatch[]; } -type FilterData = ResourceMarkersFilterData | MarkerFilterData | RelatedInformationFilterData; +export type FilterData = ResourceMarkersFilterData | MarkerFilterData | RelatedInformationFilterData; export class ResourceMarkersRenderer implements ITreeRenderer { From 90cedfc1ef240164f8fa55b0325db44dae16c16e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 11 Oct 2018 17:59:17 +0200 Subject: [PATCH 27/35] markers panel: collapse all --- src/vs/base/browser/ui/tree/abstractTree.ts | 4 +++ src/vs/base/browser/ui/tree/indexTreeModel.ts | 31 +++++++++---------- .../base/browser/ui/tree/objectTreeModel.ts | 4 +++ src/vs/base/browser/ui/tree/tree.ts | 1 + .../markers/electron-browser/markersPanel.ts | 3 +- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index feaf5c7fd6e66..acd15d7f6e742 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -259,6 +259,10 @@ export abstract class AbstractTree implements IDisposable this.model.toggleCollapsed(ref); } + collapseAll(): void { + this.model.collapseAll(); + } + isCollapsed(ref: TRef): boolean { return this.model.isCollapsed(ref); } diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index c8eed42dfa6e1..f867745634aa8 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -129,22 +129,21 @@ export class IndexTreeModel implements ITreeModel this._setCollapsed(node, listIndex, revealed)); } - // // TODO@joao cleanup - // setCollapsedAll(collapsed: boolean): void { - // if (collapsed) { - // const queue = [...this.root.children]; // TODO@joao use a linked list - // let listIndex = 0; - - // while (queue.length > 0) { - // const node = queue.shift(); - // const revealed = listIndex < this.root.children.length; - // this._setCollapsed(node, listIndex, revealed, collapsed); - - // queue.push(...node.children); - // listIndex++; - // } - // } - // } + collapseAll(): void { + const queue = [...this.root.children]; // TODO@joao use a linked list + let listIndex = 0; + + this.eventBufferer.bufferEvents(() => { + while (queue.length > 0) { + const node = queue.shift(); + const revealed = listIndex < this.root.children.length; + this._setCollapsed(node, listIndex, revealed, true); + + queue.push(...node.children); + listIndex++; + } + }); + } isCollapsed(location: number[]): boolean { return this.getNode(location).collapsed; diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index 73eb79c80aff2..356a1820a363d 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -77,6 +77,10 @@ export class ObjectTreeModel, TFilterData = void> imp this.model.toggleCollapsed(location); } + collapseAll(): void { + this.model.collapseAll(); + } + isCollapsed(element: T): boolean { const location = this.getElementLocation(element); return this.model.isCollapsed(location); diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index c808c77840ef5..fac7b7fa13035 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -54,6 +54,7 @@ export interface ITreeModel { getListIndex(ref: TRef): number; setCollapsed(ref: TRef, collapsed: boolean): boolean; toggleCollapsed(ref: TRef): void; + collapseAll(): void; isCollapsed(ref: TRef): boolean; refilter(): void; diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 5620e3a685f59..f4c1105f1d965 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -327,10 +327,9 @@ export class MarkersPanel extends Panel { this.tree.onContextMenu(this.onContextMenu, this, this._toDispose); } - // TODO@joao private createActions(): void { this.collapseAllAction = new Action('vs.tree.collapse', localize('collapse', "Collapse"), 'monaco-tree-action collapse-all', true, async () => { - // this.tree.collapseAll(); + this.tree.collapseAll(); this.tree.setSelection([]); this.tree.setFocus([]); this.tree.getHTMLElement().focus(); From f593797269cb82da6f0eafaebef630b2760e3072 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 11 Oct 2018 18:02:33 +0200 Subject: [PATCH 28/35] markers: cleanup --- .../parts/markers/electron-browser/markers.ts | 61 ------------------- .../markers/electron-browser/markersPanel.ts | 6 ++ 2 files changed, 6 insertions(+), 61 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markers.ts b/src/vs/workbench/parts/markers/electron-browser/markers.ts index b851088dd6809..db28de0d47197 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markers.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markers.ts @@ -23,10 +23,7 @@ export interface IFilter { export interface IMarkersWorkbenchService { _serviceBrand: any; - readonly markersModel: MarkersModel; - - // filter(filter: IFilter): void; } export class MarkersWorkbenchService extends Disposable implements IMarkersWorkbenchService { @@ -34,12 +31,8 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb readonly markersModel: MarkersModel; - // private useFilesExclude: boolean = false; - constructor( @IMarkerService private markerService: IMarkerService, - // @IConfigurationService private configurationService: IConfigurationService, - // @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, @IActivityService private activityService: IActivityService, @IInstantiationService instantiationService: IInstantiationService ) { @@ -51,19 +44,8 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb } this._register(markerService.onMarkerChanged(resources => this.onMarkerChanged(resources))); - // TODO@joao - // this._register(configurationService.onDidChangeConfiguration(e => { - // if (this.useFilesExclude && e.affectsConfiguration('files.exclude')) { - // this.doFilter(this.markersModel.filterOptions.filter, this.getExcludeExpression()); - // } - // })); } - // filter(filter: IFilter): void { - // this.useFilesExclude = filter.useFilesExclude; - // this.doFilter(filter.filterText, this.getExcludeExpression()); - // } - private onMarkerChanged(resources: URI[]): void { for (const resource of resources) { this.markersModel.setResourceMarkers(resource, this.readMarkers(resource)); @@ -76,54 +58,11 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb return this.markerService.read({ resource, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }); } - // private getExcludeExpression(): IExpression { - // if (this.useFilesExclude) { - // const workspaceFolders = this.workspaceContextService.getWorkspace().folders; - // if (workspaceFolders.length) { - // const result = getEmptyExpression(); - // for (const workspaceFolder of workspaceFolders) { - // mixin(result, this.getExcludesForFolder(workspaceFolder)); - // } - // return result; - // } else { - // return this.getFilesExclude(); - // } - // } - // return {}; - // } - - // private doFilter(filterText: string, filesExclude: IExpression): void { - // console.warn('marker filter not implemented'); - // this.refreshBadge(); - // this._onDidChange.fire([]); - // } - private refreshBadge(): void { const { total } = this.markersModel.stats(); const message = localize('totalProblems', 'Total {0} Problems', total); this.activityService.showActivity(Constants.MARKERS_PANEL_ID, new NumberBadge(total, () => message)); } - - // private getExcludesForFolder(workspaceFolder: IWorkspaceFolder): IExpression { - // const expression = this.getFilesExclude(workspaceFolder.uri); - // return this.getAbsoluteExpression(expression, workspaceFolder.uri.fsPath); - // } - - // private getFilesExclude(resource?: URI): IExpression { - // return deepClone(this.configurationService.getValue('files.exclude', { resource })) || {}; - // } - - // private getAbsoluteExpression(expr: IExpression, root: string): IExpression { - // return Object.keys(expr) - // .reduce((absExpr: IExpression, key: string) => { - // if (expr[key] && !isAbsolute(key)) { - // const absPattern = join(root, key); - // absExpr[absPattern] = expr[key]; - // } - - // return absExpr; - // }, Object.create(null)); - // } } registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService); \ No newline at end of file diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index f4c1105f1d965..918f1ae5f62d0 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -325,6 +325,12 @@ export class MarkersPanel extends Panel { })); this.tree.onContextMenu(this.onContextMenu, this, this._toDispose); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (this.filterAction.useFilesExclude && e.affectsConfiguration('files.exclude')) { + this.updateFilter(); + } + })); } private createActions(): void { From 87e290d04099cd4e7b1ee0450e545c401f679044 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 11 Oct 2018 18:15:58 +0200 Subject: [PATCH 29/35] markers cleanup --- .../markers/electron-browser/markersModel.ts | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts index c641b020cdd2c..22128fadadb7d 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts @@ -96,17 +96,6 @@ export class MarkersModel { this.resourcesByUri = new Map(); } - // updateMarkers(callback: (updater: (resource: URI, markers: IMarker[]) => any) => void): void { - // callback((resource, markers) => { - // if (isFalsyOrEmpty(markers)) { - // this.resourcesByUri.delete(resource.toString()); - // } else { - // this.resourcesByUri.set(resource.toString(), this.createResource(resource, markers)); - // } - // }); - // this.cachedSortedResources = undefined; - // } - getResourceMarkers(resource: URI): ResourceMarkers | null { return this.resourcesByUri.get(resource.toString()) || null; } @@ -136,24 +125,6 @@ export class MarkersModel { this._onDidChange.fire(resource); } - // TODO@joao - // forEachFilteredResource(callback: (resource: ResourceMarkers) => any) { - // this._markersByResource.forEach(resource => { - // if (resource.filteredCount > 0) { - // callback(resource); - // } - // }); - // } - - // TODO@joao - // hasFilteredResources(): boolean { - // let res = false; - // this._markersByResource.forEach(resource => { - // res = res || resource.filteredCount > 0; - // }); - // return res; - // } - stats(): { total: number, filtered: number } { let total = 0; // let filtered = 0; @@ -161,7 +132,6 @@ export class MarkersModel { total += resource.markers.length; // filtered += resource.filteredCount; // TODO@joao }); - console.warn('stats not implemented'); // TODO@joao return { total, filtered: total }; } From 99f78e83d0bbd805dc788bb38f6273e7edabef4e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 11 Oct 2018 19:10:52 +0200 Subject: [PATCH 30/35] markers: filter stats --- src/vs/base/browser/ui/tree/abstractTree.ts | 4 ++ src/vs/base/browser/ui/tree/indexTreeModel.ts | 29 ++++++----- .../base/browser/ui/tree/objectTreeModel.ts | 5 ++ src/vs/base/browser/ui/tree/tree.ts | 1 + .../parts/markers/electron-browser/markers.ts | 2 +- .../markers/electron-browser/markersModel.ts | 10 ---- .../markers/electron-browser/markersPanel.ts | 51 ++++++++++++++++--- .../electron-browser/markersPanelActions.ts | 29 +++++++---- 8 files changed, 89 insertions(+), 42 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index acd15d7f6e742..dd577c948edcf 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -247,6 +247,10 @@ export abstract class AbstractTree implements IDisposable // Tree + getNode(location?: TRef): ITreeNode { + return this.model.getNode(location); + } + collapse(location: TRef): boolean { return this.model.setCollapsed(location, true); } diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index f867745634aa8..ccda698af3b2e 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -116,16 +116,16 @@ export class IndexTreeModel implements ITreeModel this._setCollapsed(node, listIndex, revealed, collapsed)); } toggleCollapsed(location: number[]): void { - const { node, listIndex, revealed } = this.getNodeWithListIndex(location); + const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location); this.eventBufferer.bufferEvents(() => this._setCollapsed(node, listIndex, revealed)); } @@ -146,7 +146,7 @@ export class IndexTreeModel implements ITreeModel implements ITreeModel = this.root): IMutableTreeNode { + // cheap + private getTreeNode(location: number[], node: IMutableTreeNode = this.root): IMutableTreeNode { if (location.length === 0) { return node; } @@ -362,10 +360,11 @@ export class IndexTreeModel implements ITreeModel, listIndex: number, revealed: boolean } { + // expensive + private getTreeNodeWithListIndex(location: number[]): { node: IMutableTreeNode, listIndex: number, revealed: boolean } { const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location); const index = location[location.length - 1]; @@ -399,6 +398,10 @@ export class IndexTreeModel implements ITreeModel { + return this.getTreeNode(location); + } + // TODO@joao perf! getNodeLocation(node: ITreeNode): number[] { const location = []; @@ -421,12 +424,12 @@ export class IndexTreeModel implements ITreeModel implements ITreeModel, TFilterData = void> imp this.model.refilter(); } + getNode(element: T = null): ITreeNode { + const location = this.getElementLocation(element); + return this.model.getNode(location); + } + getNodeLocation(node: ITreeNode): T { return node.element; } diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index fac7b7fa13035..a0b2b5deed0f8 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -58,6 +58,7 @@ export interface ITreeModel { isCollapsed(ref: TRef): boolean; refilter(): void; + getNode(location?: TRef): ITreeNode; getNodeLocation(node: ITreeNode): TRef; getParentNodeLocation(location: TRef): TRef | null; diff --git a/src/vs/workbench/parts/markers/electron-browser/markers.ts b/src/vs/workbench/parts/markers/electron-browser/markers.ts index db28de0d47197..0ff5025eb6e1c 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markers.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markers.ts @@ -59,7 +59,7 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb } private refreshBadge(): void { - const { total } = this.markersModel.stats(); + const total = this.markersModel.resourceMarkers.reduce((r, rm) => r + rm.markers.length, 0); const message = localize('totalProblems', 'Total {0} Problems', total); this.activityService.showActivity(Constants.MARKERS_PANEL_ID, new NumberBadge(total, () => message)); } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts index 22128fadadb7d..68eeff1f910da 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts @@ -125,16 +125,6 @@ export class MarkersModel { this._onDidChange.fire(resource); } - stats(): { total: number, filtered: number } { - let total = 0; - // let filtered = 0; - this.resourcesByUri.forEach(resource => { - total += resource.markers.length; - // filtered += resource.filteredCount; // TODO@joao - }); - return { total, filtered: total }; - } - dispose(): void { this._onDidChange.dispose(); this.resourcesByUri.clear(); diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 918f1ae5f62d0..2d93031d6489f 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -15,7 +15,7 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/ import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; import { Marker, ResourceMarkers, RelatedInformation, MarkersModel } from 'vs/workbench/parts/markers/electron-browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersFilterActionItem, MarkersFilterAction, QuickFixAction, QuickFixActionItem, IMarkersFilterActionChangeEvent } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; +import { MarkersFilterActionItem, MarkersFilterAction, QuickFixAction, QuickFixActionItem, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; @@ -28,7 +28,7 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import { debounceEvent, Relay } from 'vs/base/common/event'; +import { debounceEvent, Relay, Event, Emitter } from 'vs/base/common/event'; import { WorkbenchObjectTree, ObjectTreeResourceNavigator } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; import { IExpression, getEmptyExpression } from 'vs/base/common/glob'; @@ -61,7 +61,7 @@ function createModelIterator(model: MarkersModel): Iterator(); + readonly onDidFilter: Event = this._onDidFilter.event; + private cachedFilterStats: { total: number; filtered: number; } | undefined = undefined; + private currentResourceGotAddedToMarkersData: boolean = false; constructor( @@ -217,20 +221,22 @@ export class MarkersPanel extends Panel { // TODO@joao private refreshPanel(): TPromise { if (this.isVisible()) { + this.cachedFilterStats = undefined; this.collapseAllAction.enabled = true /* this.markersWorkbenchService.markersModel.hasFilteredResources() */; dom.toggleClass(this.treeContainer, 'hidden', false/* !this.markersWorkbenchService.markersModel.hasFilteredResources() */); this.renderMessage(); - // if (this.markersWorkbenchService.markersModel.hasFilteredResources()) { this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); - // } + this._onDidFilter.fire(); } return TPromise.as(null); } private updateFilter() { + this.cachedFilterStats = undefined; const excludeExpression = this.getExcludeExpression(this.filterAction.useFilesExclude); this.filter.options = new FilterOptions(this.filterAction.filterText, excludeExpression); this.tree.refilter(); + this._onDidFilter.fire(); } private getExcludeExpression(useFilesExclude: boolean): IExpression { @@ -408,10 +414,9 @@ export class MarkersPanel extends Panel { // TODO@joao private renderMessage(): void { dom.clearNode(this.messageBoxContainer); - const markersModel = this.markersWorkbenchService.markersModel; // if (markersModel.hasFilteredResources()) { this.messageBoxContainer.style.display = 'none'; - const { total, filtered } = markersModel.stats(); + const { total, filtered } = this.getFilterStats(); if (filtered === total) { this.ariaLabelElement.setAttribute('aria-label', localize('No problems filtered', "Showing {0} problems", total)); } else { @@ -598,7 +603,7 @@ export class MarkersPanel extends Panel { public getActionItem(action: IAction): IActionItem { if (action.id === MarkersFilterAction.ID) { - this.filterInputActionItem = this.instantiationService.createInstance(MarkersFilterActionItem, this.filterAction); + this.filterInputActionItem = this.instantiationService.createInstance(MarkersFilterActionItem, this.filterAction, this); return this.filterInputActionItem; } if (action.id === QuickFixAction.ID) { @@ -607,6 +612,36 @@ export class MarkersPanel extends Panel { return super.getActionItem(action); } + getFilterOptions(): FilterOptions { + return this.filter.options; + } + + getFilterStats(): { total: number; filtered: number; } { + if (!this.cachedFilterStats) { + this.cachedFilterStats = this.computeFilterStats(); + } + + return this.cachedFilterStats; + } + + private computeFilterStats(): { total: number; filtered: number; } { + const root = this.tree.getNode(); + let total = 0; + let filtered = 0; + + for (const resourceMarkerNode of root.children) { + for (const markerNode of resourceMarkerNode.children) { + total++; + + if (resourceMarkerNode.visible && markerNode.visible) { + filtered++; + } + } + } + + return { total, filtered }; + } + public shutdown(): void { // store memento this.panelSettings['filter'] = this.filterAction.filterText; diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts index da4f41a8eb76c..b91f4ed2702a6 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts @@ -40,6 +40,9 @@ import { IMarkerData } from 'vs/platform/markers/common/markers'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { URI } from 'vs/base/common/uri'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { Event } from 'vs/base/common/event'; +import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class ToggleMarkersPanelAction extends TogglePanelAction { @@ -118,6 +121,12 @@ export class MarkersFilterAction extends Action { } } +export interface IMarkerFilterController { + onDidFilter: Event; + getFilterOptions(): FilterOptions; + getFilterStats(): { total: number, filtered: number }; +} + export class MarkersFilterActionItem extends BaseActionItem { private delayedFilterUpdate: Delayer; @@ -129,11 +138,11 @@ export class MarkersFilterActionItem extends BaseActionItem { constructor( readonly action: MarkersFilterAction, + private filterController: IMarkerFilterController, @IInstantiationService private instantiationService: IInstantiationService, @IContextViewService private contextViewService: IContextViewService, @IThemeService private themeService: IThemeService, - @IMarkersWorkbenchService private markersWorkbenchService: IMarkersWorkbenchService, - // @ITelemetryService private telemetryService: ITelemetryService, + @ITelemetryService private telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService ) { super(null, action); @@ -209,7 +218,7 @@ export class MarkersFilterActionItem extends BaseActionItem { this.filterBadge.style.borderColor = border; })); this.updateBadge(); - this._register(this.markersWorkbenchService.markersModel.onDidChange(() => this.updateBadge())); + this._register(this.filterController.onDidFilter(() => this.updateBadge())); } private createFilesExcludeCheckbox(container: HTMLElement): void { @@ -240,7 +249,7 @@ export class MarkersFilterActionItem extends BaseActionItem { } private updateBadge(): void { - const { total, filtered } = this.markersWorkbenchService.markersModel.stats(); + const { total, filtered } = this.filterController.getFilterStats(); DOM.toggleClass(this.filterBadge, 'hidden', total === filtered || filtered === 0); this.filterBadge.textContent = localize('showing filtered problems', "Showing {0} of {1}", filtered, total); this.adjustInputBox(); @@ -276,12 +285,12 @@ export class MarkersFilterActionItem extends BaseActionItem { } } - // TODO@joao private reportFilteringUsed(): void { - // let data = {}; - // data['errors'] = this.filterOptions.filterErrors; - // data['warnings'] = this.filterOptions.filterWarnings; - // data['infos'] = this.filterOptions.filterInfos; + const filterOptions = this.filterController.getFilterOptions(); + const data = {}; + data['errors'] = filterOptions.filterErrors; + data['warnings'] = filterOptions.filterWarnings; + data['infos'] = filterOptions.filterInfos; /* __GDPR__ "problems.filter" : { "errors" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -289,7 +298,7 @@ export class MarkersFilterActionItem extends BaseActionItem { "infos": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } */ - // this.telemetryService.publicLog('problems.filter', data); + this.telemetryService.publicLog('problems.filter', data); } } From 20ce98368311b48cb345c24b3bc0cdf1c4c20896 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 11 Oct 2018 19:18:09 +0200 Subject: [PATCH 31/35] markers: messages --- .../markers/electron-browser/markersPanel.ts | 145 ++++++++---------- 1 file changed, 68 insertions(+), 77 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 2d93031d6489f..c00e649729e02 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -41,6 +41,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { Separator, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -151,18 +153,6 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } this.tree.getHTMLElement().focus(); - - // TODO@joao - // if (this.markersWorkbenchService.markersModel.hasFilteredResources()) { - // this.tree.domFocus(); - // if (this.tree.getSelection().length === 0) { - // this.tree.focusFirst(); - // } - // this.highlightCurrentSelectedMarkerRange(); - // this.autoReveal(true); - // } else { - // this.messageBoxContainer.focus(); - // } } public focusFilter(): void { @@ -218,14 +208,14 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return false; } - // TODO@joao private refreshPanel(): TPromise { if (this.isVisible()) { this.cachedFilterStats = undefined; - this.collapseAllAction.enabled = true /* this.markersWorkbenchService.markersModel.hasFilteredResources() */; - dom.toggleClass(this.treeContainer, 'hidden', false/* !this.markersWorkbenchService.markersModel.hasFilteredResources() */); - this.renderMessage(); this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); + + const { total, filtered } = this.getFilterStats(); + dom.toggleClass(this.treeContainer, 'hidden', total > 0 && filtered === 0); + this.renderMessage(); this._onDidFilter.fire(); } return TPromise.as(null); @@ -237,6 +227,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.filter.options = new FilterOptions(this.filterAction.filterText, excludeExpression); this.tree.refilter(); this._onDidFilter.fire(); + this.renderMessage(); } private getExcludeExpression(useFilesExclude: boolean): IExpression { @@ -404,77 +395,77 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } } - // TODO@joao private render(): void { - dom.toggleClass(this.treeContainer, 'hidden', false/* !this.markersWorkbenchService.markersModel.hasFilteredResources() */); this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); + const { total, filtered } = this.getFilterStats(); + dom.toggleClass(this.treeContainer, 'hidden', total > 0 && filtered === 0); this.renderMessage(); } - // TODO@joao private renderMessage(): void { dom.clearNode(this.messageBoxContainer); - // if (markersModel.hasFilteredResources()) { - this.messageBoxContainer.style.display = 'none'; const { total, filtered } = this.getFilterStats(); - if (filtered === total) { - this.ariaLabelElement.setAttribute('aria-label', localize('No problems filtered', "Showing {0} problems", total)); + + if (filtered === 0) { + this.messageBoxContainer.style.display = 'block'; + this.messageBoxContainer.setAttribute('tabIndex', '0'); + if (total > 0) { + if (this.filter.options.filter) { + this.renderFilteredByFilterMessage(this.messageBoxContainer); + } else { + this.renderFilteredByFilesExcludeMessage(this.messageBoxContainer); + } + } else { + this.renderNoProblemsMessage(this.messageBoxContainer); + } } else { - this.ariaLabelElement.setAttribute('aria-label', localize('problems filtered', "Showing {0} of {1} problems", filtered, total)); + this.messageBoxContainer.style.display = 'none'; + if (filtered === total) { + this.ariaLabelElement.setAttribute('aria-label', localize('No problems filtered', "Showing {0} problems", total)); + } else { + this.ariaLabelElement.setAttribute('aria-label', localize('problems filtered', "Showing {0} of {1} problems", filtered, total)); + } + this.messageBoxContainer.removeAttribute('tabIndex'); } - this.messageBoxContainer.removeAttribute('tabIndex'); - // } else { - // this.messageBoxContainer.style.display = 'block'; - // this.messageBoxContainer.setAttribute('tabIndex', '0'); - // if (markersModel.hasResources()) { - // if (markersModel.filterOptions.filter) { - // this.renderFilteredByFilterMessage(this.messageBoxContainer); - // } else { - // this.renderFilteredByFilesExcludeMessage(this.messageBoxContainer); - // } - // } else { - // this.renderNoProblemsMessage(this.messageBoxContainer); - // } - // } - } - - // private renderFilteredByFilesExcludeMessage(container: HTMLElement) { - // const span1 = dom.append(container, dom.$('span')); - // span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER; - // const link = dom.append(container, dom.$('a.messageAction')); - // link.textContent = localize('disableFilesExclude', "Disable Files Exclude Filter."); - // link.setAttribute('tabIndex', '0'); - // dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.useFilesExclude = false); - // dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { - // if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { - // this.filterAction.useFilesExclude = false; - // e.stopPropagation(); - // } - // }); - // this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER); - // } - - // private renderFilteredByFilterMessage(container: HTMLElement) { - // const span1 = dom.append(container, dom.$('span')); - // span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS; - // const link = dom.append(container, dom.$('a.messageAction')); - // link.textContent = localize('clearFilter', "Clear Filter."); - // link.setAttribute('tabIndex', '0'); - // dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.filterText = ''); - // dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { - // if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { - // this.filterAction.filterText = ''; - // e.stopPropagation(); - // } - // }); - // this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); - // } - - // private renderNoProblemsMessage(container: HTMLElement) { - // const span = dom.append(container, dom.$('span')); - // span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT; - // this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT); - // } + } + + private renderFilteredByFilesExcludeMessage(container: HTMLElement) { + const span1 = dom.append(container, dom.$('span')); + span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER; + const link = dom.append(container, dom.$('a.messageAction')); + link.textContent = localize('disableFilesExclude', "Disable Files Exclude Filter."); + link.setAttribute('tabIndex', '0'); + dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.useFilesExclude = false); + dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { + this.filterAction.useFilesExclude = false; + e.stopPropagation(); + } + }); + this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILE_EXCLUSIONS_FILTER); + } + + private renderFilteredByFilterMessage(container: HTMLElement) { + const span1 = dom.append(container, dom.$('span')); + span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS; + const link = dom.append(container, dom.$('a.messageAction')); + link.textContent = localize('clearFilter', "Clear Filter."); + link.setAttribute('tabIndex', '0'); + dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.filterText = ''); + dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { + this.filterAction.filterText = ''; + e.stopPropagation(); + } + }); + this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); + } + + private renderNoProblemsMessage(container: HTMLElement) { + const span = dom.append(container, dom.$('span')); + span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT; + this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT); + } private autoReveal(focus: boolean = false): void { let autoReveal = this.configurationService.getValue('problems.autoReveal'); From b368ea76fb91c017961597c32bc9a03a45d586a8 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 12 Oct 2018 12:38:31 +0200 Subject: [PATCH 32/35] list: renames --- src/vs/base/browser/ui/list/list.ts | 9 ++++----- src/vs/base/browser/ui/list/listPaging.ts | 8 ++++---- src/vs/base/browser/ui/list/listView.ts | 8 ++++---- src/vs/base/browser/ui/list/listWidget.ts | 12 ++++++------ src/vs/base/browser/ui/list/rowCache.ts | 4 ++-- src/vs/base/browser/ui/selectBox/selectBoxCustom.ts | 6 +++--- src/vs/base/browser/ui/tree/abstractTree.ts | 10 +++++----- src/vs/base/browser/ui/tree/dataTree.ts | 4 ++-- src/vs/base/browser/ui/tree/tree.ts | 4 ++-- src/vs/base/test/browser/ui/list/listView.test.ts | 6 +++--- src/vs/editor/contrib/suggest/suggestWidget.ts | 6 +++--- src/vs/platform/list/browser/listService.ts | 10 +++++----- .../parts/notifications/notificationsViewer.ts | 6 +++--- .../browser/parts/quickinput/quickInputList.ts | 6 +++--- .../workbench/parts/debug/browser/breakpointsView.ts | 12 ++++++------ .../extensions/electron-browser/extensionsList.ts | 4 ++-- .../electron-browser/runtimeExtensionsEditor.ts | 6 +++--- .../files/electron-browser/views/openEditorsView.ts | 8 ++++---- .../markers/electron-browser/markersTreeViewer.ts | 4 ++-- .../parts/preferences/browser/keybindingsEditor.ts | 8 ++++---- .../parts/scm/electron-browser/scmViewlet.ts | 12 ++++++------ 21 files changed, 76 insertions(+), 77 deletions(-) diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index add0f1682938e..b9815b6ef0b31 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -5,17 +5,16 @@ import { GestureEvent } from 'vs/base/browser/touch'; -export interface IVirtualDelegate { +export interface IListVirtualDelegate { getHeight(element: T): number; getTemplateId(element: T): string; } -// TODO@joao rename to IListRenderer -export interface IRenderer { +export interface IListRenderer { templateId: string; renderTemplate(container: HTMLElement): TTemplateData; - renderElement(element: TElement, index: number, templateData: TTemplateData): void; - disposeElement(element: TElement, index: number, templateData: TTemplateData): void; + renderElement(element: T, index: number, templateData: TTemplateData): void; + disposeElement(element: T, index: number, templateData: TTemplateData): void; disposeTemplate(templateData: TTemplateData): void; } diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 14bf8b9404c4e..1317c4f5b4366 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -6,13 +6,13 @@ import 'vs/css!./list'; import { IDisposable } from 'vs/base/common/lifecycle'; import { range } from 'vs/base/common/arrays'; -import { IVirtualDelegate, IRenderer, IListEvent, IListContextMenuEvent } from './list'; +import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent } from './list'; import { List, IListStyles, IListOptions } from './listWidget'; import { IPagedModel } from 'vs/base/common/paging'; import { Event, mapEvent } from 'vs/base/common/event'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -export interface IPagedRenderer extends IRenderer { +export interface IPagedRenderer extends IListRenderer { renderPlaceholder(index: number, templateData: TTemplateData): void; } @@ -21,7 +21,7 @@ export interface ITemplateData { disposable: IDisposable; } -class PagedRenderer implements IRenderer> { +class PagedRenderer implements IListRenderer> { get templateId(): string { return this.renderer.templateId; } @@ -71,7 +71,7 @@ export class PagedList implements IDisposable { constructor( container: HTMLElement, - virtualDelegate: IVirtualDelegate, + virtualDelegate: IListVirtualDelegate, renderers: IPagedRenderer[], options: IListOptions = {} ) { diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index eb310e11352e0..61b6d478b2637 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -12,7 +12,7 @@ import { domEvent } from 'vs/base/browser/event'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollEvent, ScrollbarVisibility } from 'vs/base/common/scrollable'; import { RangeMap, shift } from './rangeMap'; -import { IVirtualDelegate, IRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list'; +import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list'; import { RowCache, IRow } from './rowCache'; import { isWindows } from 'vs/base/common/platform'; import * as browser from 'vs/base/browser/browser'; @@ -60,7 +60,7 @@ export class ListView implements ISpliceable, IDisposable { private itemId: number; private rangeMap: RangeMap; private cache: RowCache; - private renderers = new Map>(); + private renderers = new Map>(); private lastRenderTop: number; private lastRenderHeight: number; private _domNode: HTMLElement; @@ -78,8 +78,8 @@ export class ListView implements ISpliceable, IDisposable { constructor( container: HTMLElement, - private virtualDelegate: IVirtualDelegate, - renderers: IRenderer[], + private virtualDelegate: IListVirtualDelegate, + renderers: IListRenderer[], options: IListViewOptions = DefaultOptions ) { this.items = []; diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 47631a32dac87..13e8c75126ee1 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -16,7 +16,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Event, Emitter, EventBufferer, chain, mapEvent, anyEvent } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { IVirtualDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list'; +import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list'; import { ListView, IListViewOptions } from './listView'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -41,7 +41,7 @@ interface IRenderedContainer { index: number; } -class TraitRenderer implements IRenderer +class TraitRenderer implements IListRenderer { private renderedElements: IRenderedContainer[] = []; @@ -807,11 +807,11 @@ function relativeComplement(one: number[], other: number[]): number[] { const numericSort = (a: number, b: number) => a - b; -class PipelineRenderer implements IRenderer { +class PipelineRenderer implements IListRenderer { constructor( private _templateId: string, - private renderers: IRenderer[] + private renderers: IListRenderer[] ) { } get templateId(): string { @@ -903,8 +903,8 @@ export class List implements ISpliceable, IDisposable { constructor( container: HTMLElement, - virtualDelegate: IVirtualDelegate, - renderers: IRenderer[], + virtualDelegate: IListVirtualDelegate, + renderers: IListRenderer[], options: IListOptions = DefaultOptions ) { this.focus = new FocusTrait(i => this.getElementDomId(i)); diff --git a/src/vs/base/browser/ui/list/rowCache.ts b/src/vs/base/browser/ui/list/rowCache.ts index e35a0a5e1ca1f..756b1f9a67843 100644 --- a/src/vs/base/browser/ui/list/rowCache.ts +++ b/src/vs/base/browser/ui/list/rowCache.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IRenderer } from './list'; +import { IListRenderer } from './list'; import { IDisposable } from 'vs/base/common/lifecycle'; import { $, removeClass } from 'vs/base/browser/dom'; @@ -25,7 +25,7 @@ export class RowCache implements IDisposable { private cache = new Map(); - constructor(private renderers: Map>) { } + constructor(private renderers: Map>) { } /** * Returns a row either by creating a new one or reusing diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index fe455555e109a..8a56e2dcb60e4 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -14,7 +14,7 @@ import * as dom from 'vs/base/browser/dom'; import * as arrays from 'vs/base/common/arrays'; import { IContextViewProvider, AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { List } from 'vs/base/browser/ui/list/listWidget'; -import { IVirtualDelegate, IRenderer, IListEvent } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer, IListEvent } from 'vs/base/browser/ui/list/list'; import { domEvent } from 'vs/base/browser/event'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ISelectBoxDelegate, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox'; @@ -38,7 +38,7 @@ interface ISelectListTemplateData { disposables: IDisposable[]; } -class SelectListRenderer implements IRenderer { +class SelectListRenderer implements IListRenderer { get templateId(): string { return SELECT_OPTION_ENTRY_TEMPLATE_ID; } @@ -90,7 +90,7 @@ class SelectListRenderer implements IRenderer { +export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate { private static readonly DEFAULT_DROPDOWN_MINIMUM_BOTTOM_MARGIN = 32; private static readonly DEFAULT_DROPDOWN_MINIMUM_TOP_MARGIN = 2; diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index dd577c948edcf..b41038a342dcc 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -6,7 +6,7 @@ import 'vs/css!./tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IListOptions, List, IIdentityProvider, IMultipleSelectionController, IListStyles } from 'vs/base/browser/ui/list/listWidget'; -import { IVirtualDelegate, IRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { append, $ } from 'vs/base/browser/dom'; import { Event, Relay, chain } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -45,9 +45,9 @@ export function createComposedTreeListOptions(optio }; } -export class ComposedTreeDelegate implements IVirtualDelegate { +export class ComposedTreeDelegate implements IListVirtualDelegate { - constructor(private delegate: IVirtualDelegate) { } + constructor(private delegate: IListVirtualDelegate) { } getHeight(element: N): number { return this.delegate.getHeight(element.element); @@ -71,7 +71,7 @@ function renderDefaultTwistie(node: ITreeNode, twistie: HTMLElement): } } -class TreeRenderer implements IRenderer, ITreeListTemplateData> { +class TreeRenderer implements IListRenderer, ITreeListTemplateData> { readonly templateId: string; private renderedElements = new Map>(); @@ -182,7 +182,7 @@ export abstract class AbstractTree implements IDisposable constructor( container: HTMLElement, - delegate: IVirtualDelegate, + delegate: IListVirtualDelegate, renderers: ITreeRenderer[], options?: ITreeOptions ) { diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index 741eb39633d3a..e19800f7aa81a 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -5,7 +5,7 @@ import { ITreeOptions, ComposedTreeDelegate, createComposedTreeListOptions } from 'vs/base/browser/ui/tree/abstractTree'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; -import { IVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -107,7 +107,7 @@ export class DataTree, TFilterData = void> implements constructor( container: HTMLElement, - delegate: IVirtualDelegate, + delegate: IListVirtualDelegate, renderers: ITreeRenderer[], private dataSource: IDataSource, options?: ITreeOptions diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index a0b2b5deed0f8..566ba1343b7a7 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { Iterator } from 'vs/base/common/iterator'; -import { IRenderer } from 'vs/base/browser/ui/list/list'; +import { IListRenderer } from 'vs/base/browser/ui/list/list'; export const enum TreeVisibility { Hidden, @@ -67,7 +67,7 @@ export interface ITreeModel { getLastElementAncestor(location: TRef): T | null; } -export interface ITreeRenderer extends IRenderer, TTemplateData> { +export interface ITreeRenderer extends IListRenderer, TTemplateData> { renderTwistie?(element: T, twistieElement: HTMLElement): boolean; onDidChangeTwistieState?: Event; } \ No newline at end of file diff --git a/src/vs/base/test/browser/ui/list/listView.test.ts b/src/vs/base/test/browser/ui/list/listView.test.ts index 2dc54b4253f80..997b336f3cb18 100644 --- a/src/vs/base/test/browser/ui/list/listView.test.ts +++ b/src/vs/base/test/browser/ui/list/listView.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { ListView } from 'vs/base/browser/ui/list/listView'; -import { IVirtualDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import { range } from 'vs/base/common/arrays'; suite('ListView', function () { @@ -14,14 +14,14 @@ suite('ListView', function () { element.style.height = '200px'; element.style.width = '200px'; - const delegate: IVirtualDelegate = { + const delegate: IListVirtualDelegate = { getHeight() { return 20; }, getTemplateId() { return 'template'; } }; let templatesCount = 0; - const renderer: IRenderer = { + const renderer: IListRenderer = { templateId: 'template', renderTemplate() { templatesCount++; }, renderElement() { }, diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 803d95a7a58da..f83520d22867d 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -12,7 +12,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { addClass, append, $, hide, removeClass, show, toggleClass, getDomNodePagePosition, hasClass } from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import { IVirtualDelegate, IListEvent, IRenderer } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -73,7 +73,7 @@ function canExpandCompletionItem(item: ICompletionItem) { return (suggestion.detail && suggestion.detail !== suggestion.label); } -class Renderer implements IRenderer { +class Renderer implements IListRenderer { constructor( private widget: SuggestWidget, @@ -351,7 +351,7 @@ export interface ISelectedSuggestion { model: CompletionModel; } -export class SuggestWidget implements IContentWidget, IVirtualDelegate, IDisposable { +export class SuggestWidget implements IContentWidget, IListVirtualDelegate, IDisposable { private static readonly ID: string = 'editor.widget.suggestWidget'; diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index cd66bf3e9d9bf..003dba14a2eeb 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -6,7 +6,7 @@ import { addClass, addStandardDisposableListener, createStyleSheet, getTotalHeight, removeClass } from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IInputOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; -import { IListMouseEvent, IListTouchEvent, IRenderer, IVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IListMouseEvent, IListTouchEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer, PagedList } from 'vs/base/browser/ui/list/listPaging'; import { DefaultStyleController, IListOptions, IMultipleSelectionController, IOpenController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List } from 'vs/base/browser/ui/list/listWidget'; import { canceled, onUnexpectedError } from 'vs/base/common/errors'; @@ -213,8 +213,8 @@ export class WorkbenchList extends List { constructor( container: HTMLElement, - delegate: IVirtualDelegate, - renderers: IRenderer[], + delegate: IListVirtualDelegate, + renderers: IListRenderer[], options: IListOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @@ -288,7 +288,7 @@ export class WorkbenchPagedList extends PagedList { constructor( container: HTMLElement, - delegate: IVirtualDelegate, + delegate: IListVirtualDelegate, renderers: IPagedRenderer[], options: IListOptions, @IContextKeyService contextKeyService: IContextKeyService, @@ -893,7 +893,7 @@ export class WorkbenchObjectTree, TFilterData = void> constructor( container: HTMLElement, - delegate: IVirtualDelegate, + delegate: IListVirtualDelegate, renderers: ITreeRenderer[], options: ITreeOptions2, @IContextKeyService contextKeyService: IContextKeyService, diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index fa575fceb2745..c7ae08eb60db3 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IVirtualDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import { clearNode, addClass, removeClass, toggleClass, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; @@ -24,7 +24,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Severity } from 'vs/platform/notification/common/notification'; -export class NotificationsListDelegate implements IVirtualDelegate { +export class NotificationsListDelegate implements IListVirtualDelegate { private static readonly ROW_HEIGHT = 42; private static readonly LINE_HEIGHT = 22; @@ -177,7 +177,7 @@ class NotificationMessageRenderer { } } -export class NotificationRenderer implements IRenderer { +export class NotificationRenderer implements IListRenderer { static readonly TEMPLATE_ID = 'notification'; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index 10d7747b4cc73..21eef9b50852f 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./quickInput'; -import { IVirtualDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import * as dom from 'vs/base/browser/dom'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; @@ -83,7 +83,7 @@ interface IListElementTemplateData { toDisposeTemplate: IDisposable[]; } -class ListElementRenderer implements IRenderer { +class ListElementRenderer implements IListRenderer { static readonly ID = 'listelement'; @@ -198,7 +198,7 @@ class ListElementRenderer implements IRenderer { +class ListElementDelegate implements IListVirtualDelegate { getHeight(element: ListElement): number { return element.saneDetail ? 44 : 22; diff --git a/src/vs/workbench/parts/debug/browser/breakpointsView.ts b/src/vs/workbench/parts/debug/browser/breakpointsView.ts index dbe14c463ffad..1c31a5d240320 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointsView.ts @@ -18,7 +18,7 @@ import { Constants } from 'vs/editor/common/core/uint'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IVirtualDelegate, IListContextMenuEvent, IRenderer } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListContextMenuEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; import { IEditor } from 'vs/workbench/common/editor'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -236,7 +236,7 @@ export class BreakpointsView extends ViewletPanel { } } -class BreakpointsDelegate implements IVirtualDelegate { +class BreakpointsDelegate implements IListVirtualDelegate { constructor(private debugService: IDebugService) { // noop @@ -292,7 +292,7 @@ interface IInputTemplateData { toDispose: IDisposable[]; } -class BreakpointsRenderer implements IRenderer { +class BreakpointsRenderer implements IListRenderer { constructor( @IDebugService private debugService: IDebugService, @@ -361,7 +361,7 @@ class BreakpointsRenderer implements IRenderer { +class ExceptionBreakpointsRenderer implements IListRenderer { constructor( private debugService: IDebugService @@ -409,7 +409,7 @@ class ExceptionBreakpointsRenderer implements IRenderer { +class FunctionBreakpointsRenderer implements IListRenderer { constructor( @IDebugService private debugService: IDebugService @@ -468,7 +468,7 @@ class FunctionBreakpointsRenderer implements IRenderer { +class FunctionBreakpointInputRenderer implements IListRenderer { constructor( private debugService: IDebugService, diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts index 5ded3ba53d76c..e21bcb21873e4 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts @@ -9,7 +9,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Action } from 'vs/base/common/actions'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { once } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; @@ -36,7 +36,7 @@ export interface ITemplateData { extensionDisposables: IDisposable[]; } -export class Delegate implements IVirtualDelegate { +export class Delegate implements IListVirtualDelegate { getHeight() { return 62; } getTemplateId() { return 'extension'; } } diff --git a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts index e34883ba45500..fe8c8f5d469c6 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -16,7 +16,7 @@ import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/parts/exte import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService, IExtensionDescription, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; -import { IVirtualDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { append, $, addClass, toggleClass, Dimension } from 'vs/base/browser/dom'; import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -230,7 +230,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { const TEMPLATE_ID = 'runtimeExtensionElementTemplate'; - const delegate = new class implements IVirtualDelegate{ + const delegate = new class implements IListVirtualDelegate{ getHeight(element: IRuntimeExtension): number { return 62; } @@ -257,7 +257,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { elementDisposables: IDisposable[]; } - const renderer: IRenderer = { + const renderer: IListRenderer = { templateId: TEMPLATE_ID, renderTemplate: (root: HTMLElement): IRuntimeExtensionTemplateData => { const element = append(root, $('.extension')); diff --git a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts index 3f5eb10bacb94..b6a1ed7cd8b2c 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts @@ -25,7 +25,7 @@ import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; -import { IVirtualDelegate, IRenderer, IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { EditorLabel } from 'vs/workbench/browser/labels'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -499,7 +499,7 @@ class OpenEditorActionRunner extends ActionRunner { } } -class OpenEditorsDelegate implements IVirtualDelegate { +class OpenEditorsDelegate implements IListVirtualDelegate { public static readonly ITEM_HEIGHT = 22; @@ -542,7 +542,7 @@ function dropOnEditorSupported(e: DragEvent): boolean { } } -class EditorGroupRenderer implements IRenderer { +class EditorGroupRenderer implements IListRenderer { static readonly ID = 'editorgroup'; private transfer = LocalSelectionTransfer.getInstance(); @@ -615,7 +615,7 @@ class EditorGroupRenderer implements IRenderer { +class OpenEditorRenderer implements IListRenderer { static readonly ID = 'openeditor'; private transfer = LocalSelectionTransfer.getInstance(); diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index e21f5a96a638e..04195ef23de2d 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -21,7 +21,7 @@ import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/act import { QuickFixAction } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname } from 'vs/base/common/resources'; -import { IVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeFilter, TreeVisibility, TreeFilterResult, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; import { IMatch } from 'vs/base/common/filters'; @@ -78,7 +78,7 @@ const enum TemplateId { RelatedInformation = 'ri' } -export class VirtualDelegate implements IVirtualDelegate { +export class VirtualDelegate implements IListVirtualDelegate { getHeight(): number { return 22; diff --git a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts index 65964459198f1..6b5d4a5410b24 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts @@ -31,7 +31,7 @@ import { import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { List } from 'vs/base/browser/ui/list/listWidget'; -import { IVirtualDelegate, IRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -653,7 +653,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } } -class Delegate implements IVirtualDelegate { +class Delegate implements IListVirtualDelegate { getHeight(element: IListEntry) { if (element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) { @@ -686,7 +686,7 @@ interface KeybindingItemTemplate { when: WhenColumn; } -class KeybindingHeaderRenderer implements IRenderer { +class KeybindingHeaderRenderer implements IListRenderer { get templateId(): string { return KEYBINDING_HEADER_TEMPLATE_ID; } @@ -713,7 +713,7 @@ class KeybindingHeaderRenderer implements IRenderer { } } -class KeybindingItemRenderer implements IRenderer { +class KeybindingItemRenderer implements IListRenderer { get templateId(): string { return KEYBINDING_ENTRY_TEMPLATE_ID; } diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 6ba2dc7f90b0d..796594713fc48 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -14,7 +14,7 @@ import { PanelViewlet, ViewletPanel, IViewletPanelOptions } from 'vs/workbench/b import { append, $, addClass, toggleClass, trackFocus, Dimension, addDisposableListener } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { List } from 'vs/base/browser/ui/list/listWidget'; -import { IVirtualDelegate, IRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/parts/scm/common/scm'; import { FileLabel } from 'vs/workbench/browser/labels'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; @@ -70,7 +70,7 @@ export interface IViewModel { hide(repository: ISCMRepository): void; } -class ProvidersListDelegate implements IVirtualDelegate { +class ProvidersListDelegate implements IListVirtualDelegate { getHeight(element: ISCMRepository): number { return 22; @@ -119,7 +119,7 @@ interface RepositoryTemplateData { templateDisposable: IDisposable; } -class ProviderRenderer implements IRenderer { +class ProviderRenderer implements IListRenderer { readonly templateId = 'provider'; @@ -368,7 +368,7 @@ interface ResourceGroupTemplate { dispose: () => void; } -class ResourceGroupRenderer implements IRenderer { +class ResourceGroupRenderer implements IListRenderer { static TEMPLATE_ID = 'resource group'; get templateId(): string { return ResourceGroupRenderer.TEMPLATE_ID; } @@ -477,7 +477,7 @@ class MultipleSelectionActionRunner extends ActionRunner { } } -class ResourceRenderer implements IRenderer { +class ResourceRenderer implements IListRenderer { static TEMPLATE_ID = 'resource'; get templateId(): string { return ResourceRenderer.TEMPLATE_ID; } @@ -570,7 +570,7 @@ class ResourceRenderer implements IRenderer { } } -class ProviderListDelegate implements IVirtualDelegate { +class ProviderListDelegate implements IListVirtualDelegate { getHeight() { return 22; } From 4c781f050a92cb32be98f79b82d4a811471c7475 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 12 Oct 2018 16:33:44 +0200 Subject: [PATCH 33/35] markers: tree element --- .../parts/markers/electron-browser/markersPanel.ts | 4 +--- .../markers/electron-browser/markersTreeViewer.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index c00e649729e02..6b954c07dd3ae 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -36,7 +36,7 @@ import { mixin, deepClone } from 'vs/base/common/objects'; import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isAbsolute, join } from 'vs/base/common/paths'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/abstractTree'; -import { FilterData, FileResourceMarkersRenderer, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer } from 'vs/workbench/parts/markers/electron-browser/markersTreeViewer'; +import { FilterData, FileResourceMarkersRenderer, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement } from 'vs/workbench/parts/markers/electron-browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Separator, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; @@ -44,8 +44,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -type TreeElement = ResourceMarkers | Marker | RelatedInformation; - function createModelIterator(model: MarkersModel): Iterator> { const resourcesIt = Iterator.fromArray(model.resourceMarkers); diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 04195ef23de2d..a3ea8e78eb7bf 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -27,6 +27,8 @@ import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/marke import { IMatch } from 'vs/base/common/filters'; import { Event } from 'vs/base/common/event'; +export type TreeElement = ResourceMarkers | Marker | RelatedInformation; + interface IResourceMarkersTemplateData { resourceLabel: ResourceLabel; count: CountBadge; @@ -78,13 +80,13 @@ const enum TemplateId { RelatedInformation = 'ri' } -export class VirtualDelegate implements IListVirtualDelegate { +export class VirtualDelegate implements IListVirtualDelegate { getHeight(): number { return 22; } - getTemplateId(element: ResourceMarkers | Marker | RelatedInformation): string { + getTemplateId(element: TreeElement): string { if (element instanceof ResourceMarkers) { if ((element).resource.scheme === network.Schemas.file || (element).resource.scheme === network.Schemas.untitled) { return TemplateId.FileResourceMarkers; @@ -326,11 +328,11 @@ export class RelatedInformationRenderer implements ITreeRenderer { +export class Filter implements ITreeFilter { options = new FilterOptions(); - filter(element: ResourceMarkers | Marker | RelatedInformation): TreeFilterResult { + filter(element: TreeElement): TreeFilterResult { if (element instanceof ResourceMarkers) { return this.filterResourceMarkers(element); } else if (element instanceof Marker) { From 19f030b2ceef1adbb134393b564232138d5a8603 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 12 Oct 2018 16:42:00 +0200 Subject: [PATCH 34/35] markers panel should focus input when typing --- .../parts/markers/electron-browser/markersPanel.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 6b954c07dd3ae..fb59137e197a3 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -43,6 +43,7 @@ import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { domEvent } from 'vs/base/browser/event'; function createModelIterator(model: MarkersModel): Iterator> { const resourcesIt = Iterator.fromArray(model.resourceMarkers); @@ -326,6 +327,13 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.updateFilter(); } })); + + // move focus to input, whenever a key is pressed in the panel container + this._register(domEvent(parent, 'keydown', true)(() => { + if (this.filterInputActionItem) { + this.filterInputActionItem.focus(); + } + })); } private createActions(): void { From 9ef5f082e4f4128e55ee7395884f8c619a1b9c03 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 12 Oct 2018 16:45:17 +0200 Subject: [PATCH 35/35] remember to clear cache --- src/vs/workbench/parts/markers/electron-browser/markersPanel.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index fb59137e197a3..b4f178eedb914 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -402,6 +402,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private render(): void { + this.cachedFilterStats = undefined; this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); const { total, filtered } = this.getFilterStats(); dom.toggleClass(this.treeContainer, 'hidden', total > 0 && filtered === 0);