diff --git a/src/vs/workbench/parts/markers/electron-browser/constants.ts b/src/vs/workbench/parts/markers/electron-browser/constants.ts index a9e0b9e8f6de7f5041bf0a4059ac2e7d1754b69e..8d4348b87a7afe787826ad1bd460c1a301701984 100644 --- a/src/vs/workbench/parts/markers/electron-browser/constants.ts +++ b/src/vs/workbench/parts/markers/electron-browser/constants.ts @@ -16,6 +16,7 @@ export default { MARKERS_PANEL_SHOW_SINGLELINE_MESSAGE: 'problems.action.showSinglelineMessage', MARKER_OPEN_SIDE_ACTION_ID: 'problems.action.openToSide', MARKER_SHOW_PANEL_ID: 'workbench.action.showErrorsWarnings', + MARKER_SHOW_QUICK_FIX: 'problems.action.showQuickFixes', MarkerPanelFocusContextKey: new RawContextKey('problemsViewFocus', false), MarkerFocusContextKey: new RawContextKey('problemFocus', false), 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 4afdc0bf6c376d3d4e055cba2d071599f75e8985..765a7908f837d4a6594f464ba0115d89b26945e1 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts @@ -53,6 +53,20 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: Constants.MARKER_SHOW_QUICK_FIX, + weight: KeybindingWeight.WorkbenchContrib, + when: Constants.MarkerFocusContextKey, + primary: KeyMod.CtrlCmd | KeyCode.US_DOT, + handler: (accessor, args: any) => { + const markersPanel = (accessor.get(IPanelService).getActivePanel()); + const focusedElement = markersPanel.getFocusElement(); + if (focusedElement instanceof Marker) { + markersPanel.showQuickFixes(focusedElement); + } + } +}); + // configuration Registry.as(Extensions.Configuration).registerConfiguration({ 'id': 'problems', @@ -161,7 +175,7 @@ registerAction({ const panelService = accessor.get(IPanelService); const panel = panelService.getActivePanel(); if (panel instanceof MarkersPanel) { - panel.markersViewState.multiline = true; + panel.markersViewModel.multiline = true; } }, title: localize('show multiline', "Show message in multiple lines"), @@ -177,7 +191,7 @@ registerAction({ const panelService = accessor.get(IPanelService); const panel = panelService.getActivePanel(); if (panel instanceof MarkersPanel) { - panel.markersViewState.multiline = false; + panel.markersViewModel.multiline = false; } }, title: localize('show singleline', "Show message in single line"), diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index a87ac486e8c1128cd7383a95491260e87bd1c42f..128c751cd08b9102599ddee3067120c479bb4899 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -14,7 +14,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, IMarkerFilterController } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; +import { MarkersFilterActionItem, MarkersFilterAction, 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'; @@ -33,7 +33,7 @@ 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 { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewState } from 'vs/workbench/parts/markers/electron-browser/markersTreeViewer'; +import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel } 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'; @@ -89,7 +89,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private cachedFilterStats: { total: number; filtered: number; } | undefined = undefined; private currentResourceGotAddedToMarkersData: boolean = false; - readonly markersViewState: MarkersViewState; + readonly markersViewModel: MarkersViewModel; private disposables: IDisposable[] = []; constructor( @@ -109,8 +109,8 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { super(Constants.MARKERS_PANEL_ID, telemetryService, themeService, storageService); this.panelFoucusContextKey = Constants.MarkerPanelFocusContextKey.bindTo(contextKeyService); this.panelState = this.getMemento(StorageScope.WORKSPACE); - this.markersViewState = new MarkersViewState(this.panelState['multiline']); - this.markersViewState.onDidChangeViewState(this.onDidChangeViewState, this, this.disposables); + this.markersViewModel = instantiationService.createInstance(MarkersViewModel, this.panelState['multiline']); + this.markersViewModel.onDidChange(this.onDidChangeViewState, this, this.disposables); this.setCurrentActiveEditor(); } @@ -178,6 +178,13 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return this.actions; } + public showQuickFixes(marker: Marker): void { + const viewModel = this.markersViewModel.getViewModel(marker); + if (viewModel) { + viewModel.quickFixAction.run(); + } + } + public openFileAtElement(element: any, preserveFocus: boolean, sideByside: boolean, pinned: boolean): boolean { const { resource, selection, event, data } = element instanceof Marker ? { resource: element.resource, selection: element.range, event: 'problems.selectDiagnostic', data: this.getTelemetryData(element.marker) } : element instanceof RelatedInformation ? { resource: element.raw.resource, selection: element.raw, event: 'problems.selectRelatedInformation', data: this.getTelemetryData(element.marker) } : { resource: null, selection: null, event: null, data: null }; @@ -306,10 +313,10 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); - const virtualDelegate = new VirtualDelegate(this.markersViewState); + const virtualDelegate = new VirtualDelegate(this.markersViewModel); const renderers = [ this.instantiationService.createInstance(ResourceMarkersRenderer, this.treeLabels, onDidChangeRenderNodeCount.event), - this.instantiationService.createInstance(MarkerRenderer, this.markersViewState, a => this.getActionItem(a)), + this.instantiationService.createInstance(MarkerRenderer, this.markersViewModel), this.instantiationService.createInstance(RelatedInformationRenderer) ]; this.filter = new Filter(); @@ -408,11 +415,11 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private onDidChangeModel(resources: URI[]) { for (const resource of resources) { - this.markersViewState.remove(resource); + this.markersViewModel.remove(resource); const resourceMarkers = this.markersWorkbenchService.markersModel.getResourceMarkers(resource); if (resourceMarkers) { for (const marker of resourceMarkers.markers) { - this.markersViewState.add(marker); + this.markersViewModel.add(marker); } } } @@ -657,9 +664,6 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.filterInputActionItem = this.instantiationService.createInstance(MarkersFilterActionItem, this.filterAction, this); return this.filterInputActionItem; } - if (action.id === QuickFixAction.ID) { - return this.instantiationService.createInstance(QuickFixActionItem, action); - } return super.getActionItem(action); } @@ -701,7 +705,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.panelState['filter'] = this.filterAction.filterText; this.panelState['filterHistory'] = this.filterAction.filterHistory; this.panelState['useFilesExclude'] = this.filterAction.useFilesExclude; - this.panelState['multiline'] = this.markersViewState.multiline; + this.panelState['multiline'] = this.markersViewModel.multiline; super.saveState(); } @@ -709,7 +713,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { public dispose(): void { super.dispose(); this.tree.dispose(); - this.markersViewState.dispose(); + this.markersViewModel.dispose(); this.disposables = dispose(this.disposables); } } \ No newline at end of file diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts index b81abdf184c5ba7684e08360ba9dacb84516dcef..278570fc877cd3aee33f51c629c63aa264967c20 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts @@ -29,7 +29,7 @@ import { Marker } from 'vs/workbench/parts/markers/electron-browser/markersModel 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 { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -297,12 +297,16 @@ export class QuickFixAction extends Action { private updated: boolean = false; private disposables: IDisposable[] = []; + private _onShowQuickFixes: Emitter = new Emitter(); + readonly onShowQuickFixes: Event = this._onShowQuickFixes.event; + constructor( readonly marker: Marker, @IModelService modelService: IModelService, @IMarkersWorkbenchService private readonly markerWorkbenchService: IMarkersWorkbenchService, ) { super(QuickFixAction.ID, Messages.MARKERS_PANEL_ACTION_TOOLTIP_QUICKFIX, 'markers-panel-action-quickfix', false); + this.disposables.push(this._onShowQuickFixes); if (modelService.getModel(this.marker.resource)) { this.update(); } else { @@ -314,6 +318,11 @@ export class QuickFixAction extends Action { } } + run(): Promise { + this._onShowQuickFixes.fire(); + return Promise.resolve(); + } + private update(): void { if (!this.updated) { this.markerWorkbenchService.hasQuickFixes(this.marker).then(hasFixes => this.enabled = hasFixes); @@ -338,13 +347,17 @@ export class QuickFixActionItem extends ActionItem { public onClick(event: DOM.EventLike): void { DOM.EventHelper.stop(event, true); + this.showQuickFixes(); + } + + public showQuickFixes(): void { if (!this.element) { return; } const elementPosition = DOM.getDomNodePagePosition(this.element); this.markerWorkbenchService.getQuickFixActions((this.getAction()).marker).then(actions => { this.contextMenuService.showContextMenu({ - getAnchor: () => ({ x: elementPosition.left + 10, y: elementPosition.top + elementPosition.height }), + getAnchor: () => ({ x: elementPosition.left + 10, y: elementPosition.top + elementPosition.height + 4 }), getActions: () => actions }); }); diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index c18c2778053763bebe1763c5b8e56b5d82520a58..a284d526c5e37c8682803733ce336df659a5f1ca 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -16,8 +16,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IDisposable, dispose, Disposable, toDisposable } 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 { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { QuickFixAction, QuickFixActionItem } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname } from 'vs/base/common/resources'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -76,12 +76,12 @@ const enum TemplateId { export class VirtualDelegate implements IListVirtualDelegate { - constructor(private readonly markersViewState: MarkersViewState) { } + constructor(private readonly markersViewState: MarkersViewModel) { } getHeight(element: TreeElement): number { if (element instanceof Marker) { - const viewState = this.markersViewState.getViewState(element); - const noOfLines = !viewState || viewState.multiline ? element.lines.length : 1; + const viewModel = this.markersViewState.getViewModel(element); + const noOfLines = !viewModel || viewModel.multiline ? element.lines.length : 1; return noOfLines * 22; } return 22; @@ -201,8 +201,7 @@ export class FileResourceMarkersRenderer extends ResourceMarkersRenderer { export class MarkerRenderer implements ITreeRenderer { constructor( - private readonly markersViewState: MarkersViewState, - private actionItemProvider: IActionItemProvider, + private readonly markersViewState: MarkersViewModel, @IInstantiationService protected instantiationService: IInstantiationService ) { } @@ -210,7 +209,7 @@ export class MarkerRenderer implements ITreeRenderer action.id === QuickFixAction.ID ? instantiationService.createInstance(QuickFixActionItem, action) : null + })); this.icon = dom.append(parent, dom.$('.icon')); - this.multilineActionbar = this._register(new ActionBar(dom.append(parent, dom.$('.multiline-actions')), { actionItemProvider })); + this.multilineActionbar = this._register(new ActionBar(dom.append(parent, dom.$('.multiline-actions')))); this.messageAndDetailsContainer = dom.append(parent, dom.$('.marker-message-details')); this._register(toDisposable(() => this.disposables = dispose(this.disposables))); } @@ -254,38 +254,47 @@ class MarkerWidget extends Disposable { } dom.clearNode(this.messageAndDetailsContainer); - this.renderQuickfixActionbar(element); this.icon.className = 'marker-icon ' + MarkerWidget.iconClassNameFor(element.marker); + this.renderQuickfixActionbar(element); this.renderMultilineActionbar(element); this.renderMessageAndDetails(element, filterData); } private renderQuickfixActionbar(marker: Marker): void { - const quickFixAction = this.instantiationService.createInstance(QuickFixAction, marker); - this.actionBar.push([quickFixAction], { icon: true, label: false }); - dom.toggleClass(this.icon, 'quickFix', quickFixAction.enabled); - quickFixAction.onDidChange(({ enabled }) => { - if (!isUndefinedOrNull(enabled)) { - dom.toggleClass(this.icon, 'quickFix', enabled); - } - }, this, this.disposables); + const viewModel = this.markersViewModel.getViewModel(marker); + if (viewModel) { + const quickFixAction = viewModel.quickFixAction; + this.actionBar.push([quickFixAction], { icon: true, label: false }); + dom.toggleClass(this.icon, 'quickFix', quickFixAction.enabled); + quickFixAction.onDidChange(({ enabled }) => { + if (!isUndefinedOrNull(enabled)) { + dom.toggleClass(this.icon, 'quickFix', enabled); + } + }, this, this.disposables); + quickFixAction.onShowQuickFixes(() => { + const quickFixActionItem = this.actionBar.items[0]; + if (quickFixActionItem) { + quickFixActionItem.showQuickFixes(); + } + }, this, this.disposables); + } } private renderMultilineActionbar(marker: Marker): void { - const viewState = this.markersViewState.getViewState(marker); - const multiline = viewState && viewState.multiline; + const viewModel = this.markersViewModel.getViewModel(marker); + const multiline = viewModel && viewModel.multiline; const action = new Action('problems.action.toggleMultiline'); - action.enabled = viewState && marker.lines.length > 1; + action.enabled = viewModel && marker.lines.length > 1; action.tooltip = multiline ? localize('single line', "Show message in single line") : localize('multi line', "Show message in multiple lines"); action.class = multiline ? 'octicon octicon-chevron-up' : 'octicon octicon-chevron-down'; - action.run = () => { if (viewState) { viewState.multiline = !viewState.multiline; } return Promise.resolve(); }; + action.run = () => { if (viewModel) { viewModel.multiline = !viewModel.multiline; } return Promise.resolve(); }; this.multilineActionbar.push([action], { icon: true, label: false }); } private renderMessageAndDetails(element: Marker, filterData: MarkerFilterData) { const { marker, lines } = element; - const viewState = this.markersViewState.getViewState(element); + const viewState = this.markersViewModel.getViewModel(element); const multiline = !viewState || viewState.multiline; const lineMatches = filterData && filterData.lineMatches || []; const messageContainer = dom.append(this.messageAndDetailsContainer, dom.$('.marker-message')); @@ -461,10 +470,17 @@ export class Filter implements ITreeFilter { } } -export class MarkerViewState extends Disposable { +export class MarkerViewModel extends Disposable { - private readonly _onDidChangeViewState: Emitter = this._register(new Emitter()); - readonly onDidChangeViewState: Event = this._onDidChangeViewState.event; + private readonly _onDidChange: Emitter = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + constructor( + private readonly marker: Marker, + @IInstantiationService private instantiationService: IInstantiationService + ) { + super(); + } private _multiline: boolean = true; get multiline(): boolean { @@ -474,37 +490,48 @@ export class MarkerViewState extends Disposable { set multiline(value: boolean) { if (this._multiline !== value) { this._multiline = value; - this._onDidChangeViewState.fire(); + this._onDidChange.fire(); + } + } + + private _quickFixAction: QuickFixAction; + get quickFixAction(): QuickFixAction { + if (!this._quickFixAction) { + this._quickFixAction = this._register(this.instantiationService.createInstance(QuickFixAction, this.marker)); } + return this._quickFixAction; } } -export class MarkersViewState extends Disposable { +export class MarkersViewModel extends Disposable { - private readonly _onDidChangeViewState: Emitter = this._register(new Emitter()); - readonly onDidChangeViewState: Event = this._onDidChangeViewState.event; + private readonly _onDidChange: Emitter = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; - private readonly markersViewStates: Map = new Map(); + private readonly markersViewStates: Map = new Map(); private readonly markersPerResource: Map = new Map(); private bulkUpdate: boolean = false; - constructor(multiline: boolean = true) { + constructor( + multiline: boolean = true, + @IInstantiationService private instantiationService: IInstantiationService + ) { super(); this._multiline = multiline; } add(marker: Marker): void { if (!this.markersViewStates.has(marker.hash)) { - const disposables: IDisposable[] = []; - const viewState = new MarkerViewState(); - viewState.multiline = this.multiline; - viewState.onDidChangeViewState(() => { + const viewModel = this.instantiationService.createInstance(MarkerViewModel, marker); + const disposables: IDisposable[] = [viewModel]; + viewModel.multiline = this.multiline; + viewModel.onDidChange(() => { if (!this.bulkUpdate) { - this._onDidChangeViewState.fire(marker); + this._onDidChange.fire(marker); } }, this, disposables); - this.markersViewStates.set(marker.hash, { viewState, disposables }); + this.markersViewStates.set(marker.hash, { viewModel, disposables }); const markers = this.markersPerResource.get(marker.resource.toString()) || []; markers.push(marker); @@ -524,9 +551,9 @@ export class MarkersViewState extends Disposable { this.markersPerResource.delete(resource.toString()); } - getViewState(marker: Marker): MarkerViewState | null { + getViewModel(marker: Marker): MarkerViewModel | null { const value = this.markersViewStates.get(marker.hash); - return value ? value.viewState : null; + return value ? value.viewModel : null; } private _multiline: boolean = true; @@ -541,15 +568,15 @@ export class MarkersViewState extends Disposable { changed = true; } this.bulkUpdate = true; - this.markersViewStates.forEach(({ viewState }) => { - if (viewState.multiline !== value) { - viewState.multiline = value; + this.markersViewStates.forEach(({ viewModel }) => { + if (viewModel.multiline !== value) { + viewModel.multiline = value; changed = true; } }); this.bulkUpdate = false; if (changed) { - this._onDidChangeViewState.fire(undefined); + this._onDidChange.fire(undefined); } }