diff --git a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts b/src/vs/editor/contrib/codeAction/codeActionTrigger.ts index 4f3c53b5dc9c409b078e9cf5f96cb1a30489916b..2e704921dd6ea4d6af31680e81cf7526dd36cf0b 100644 --- a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts +++ b/src/vs/editor/contrib/codeAction/codeActionTrigger.ts @@ -9,6 +9,7 @@ export class CodeActionKind { private static readonly sep = '.'; public static readonly Empty = new CodeActionKind(''); + public static readonly QuickFix = new CodeActionKind('quickfix'); public static readonly Refactor = new CodeActionKind('refactor'); public static readonly Source = new CodeActionKind('source'); public static readonly SourceOrganizeImports = new CodeActionKind('source.organizeImports'); diff --git a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts index f6a6c53d384b6c2e9639d92e29ef7d51644e210e..85e1d53e2ffa9e31d74c68f581059bd59a71b874 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts @@ -7,7 +7,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 } from 'vs/platform/markers/common/markers'; +import { IMarker, MarkerSeverity, IRelatedInformation, IMarkerData } 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'; @@ -18,7 +18,7 @@ import * as strings from 'vs/base/common/strings'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { CodeAction } from 'vs/editor/common/modes'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionTrigger } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; function compareUris(a: URI, b: URI) { if (a.toString() < b.toString()) { @@ -38,6 +38,7 @@ export class ResourceMarkers extends NodeWithId { private _name: string = null; private _path: string = null; + private _allFixesPromise: Promise; markers: Marker[] = []; isExcluded: boolean = false; @@ -46,7 +47,8 @@ export class ResourceMarkers extends NodeWithId { uriMatches: IMatch[] = []; constructor( - readonly uri: URI + readonly uri: URI, + private textModelService: ITextModelService ) { super(uri.toString()); } @@ -65,6 +67,37 @@ 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._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 modelReference = await this.textModelService.createModelReference(this.uri); + if (modelReference) { + const model = modelReference.object.textEditorModel; + const codeActions = await getCodeActions(model, range ? range : model.getFullModelRange(), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }); + modelReference.dispose(); + return codeActions; + } + return []; + } + static compare(a: ResourceMarkers, b: ResourceMarkers): number { let [firstMarkerOfA] = a.markers; let [firstMarkerOfB] = b.markers; @@ -88,8 +121,7 @@ export class Marker extends NodeWithId { constructor( id: string, - readonly raw: IMarker, - private textModelService: ITextModelService + readonly raw: IMarker ) { super(id); } @@ -102,17 +134,6 @@ export class Marker extends NodeWithId { return this.raw; } - public async getCodeActions(trigger: CodeActionTrigger): Promise { - const modelReference = await this.textModelService.createModelReference(this.resource); - if (modelReference) { - const model = modelReference.object.textEditorModel; - const codeActions = await getCodeActions(model, new Range(this.range.startLineNumber, this.range.startColumn, this.range.endLineNumber, this.range.endColumn), trigger); - modelReference.dispose(); - return codeActions; - } - return []; - } - public toString(): string { return JSON.stringify({ ...this.raw, @@ -289,11 +310,11 @@ export class MarkersModel { private createResource(uri: URI, rawMarkers: IMarker[]): ResourceMarkers { const markers: Marker[] = []; - const resource = new ResourceMarkers(uri); + const resource = new ResourceMarkers(uri, this.textModelService); this.updateResource(resource); rawMarkers.forEach((rawMarker, index) => { - const marker = new Marker(uri.toString() + index, rawMarker, this.textModelService); + const marker = new Marker(uri.toString() + index, rawMarker); if (rawMarker.relatedInformation) { const groupedByResource = groupBy(rawMarker.relatedInformation, MarkersModel._compareMarkersByUri); groupedByResource.sort((a, b) => compareUris(a[0].resource, b[0].resource)); diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts index 06df8759fb2b75d2ca29d0c2ab5c915a007fcb8a..8fe527107df55f4baeffa242d5ed767db3c0aace 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts @@ -7,7 +7,7 @@ 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 { MarkersModel, Marker, ResourceMarkers } 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, Action } from 'vs/base/common/actions'; @@ -19,6 +19,7 @@ import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { localize } from 'vs/nls'; export class Controller extends WorkbenchTreeController { @@ -79,10 +80,14 @@ export class Controller extends WorkbenchTreeController { private async _getMenuActions(tree: WorkbenchTree, element: any): Promise { const result: IAction[] = []; - const quickFixActions = await this._getQuickFixActions(element); - if (quickFixActions.length) { - result.push(...quickFixActions); + if (element instanceof Marker) { + const quickFixActions = await this._getQuickFixActions(tree, element); + if (quickFixActions.length) { + result.push(...quickFixActions); + } else { + result.push(new Action('problems.no.fixes', localize('no fixes available', "No fixes available"), void 0, false)); + } result.push(new Separator()); } @@ -100,9 +105,10 @@ export class Controller extends WorkbenchTreeController { return result; } - private async _getQuickFixActions(element: any): Promise { - if (element instanceof Marker) { - const codeActions = await element.getCodeActions({ type: 'manual' }); + private async _getQuickFixActions(tree: WorkbenchTree, element: Marker): Promise { + const parent = tree.getNavigator(element).parent(); + if (parent instanceof ResourceMarkers) { + const codeActions = await parent.getFixes(element); return codeActions.map(codeAction => new Action( codeAction.command ? codeAction.command.id : codeAction.title, codeAction.title, @@ -112,9 +118,8 @@ export class Controller extends WorkbenchTreeController { return this.openFileAtMarker(element) .then(() => applyCodeAction(codeAction, this.bulkEditService, this.commandService)); })); - } - return Promise.resolve([]); + return []; } public openFileAtMarker(element: Marker): TPromise { diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index b4eb6819d23563f20b9df6abac976791ad5bf7bc..0436d143f3b3c0d5a1c3904dc5efe0bf57f579be 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -31,6 +31,7 @@ interface IResourceMarkersTemplateData { interface IMarkerTemplateData { icon: HTMLElement; + lightbulb: HTMLElement; source: HighlightedLabel; description: HighlightedLabel; lnCol: HTMLElement; @@ -179,6 +180,7 @@ export class Renderer implements IRenderer { private renderMarkerTemplate(container: HTMLElement): IMarkerTemplateData { const data: IMarkerTemplateData = Object.create(null); data.icon = dom.append(container, dom.$('.marker-icon')); + data.lightbulb = dom.append(container, dom.$('.icon.lightbulb')); data.source = new HighlightedLabel(dom.append(container, dom.$(''))); data.description = new HighlightedLabel(dom.append(container, dom.$('.marker-description'))); data.lnCol = dom.append(container, dom.$('span.marker-line')); @@ -208,7 +210,9 @@ export class Renderer implements IRenderer { private renderMarkerElement(tree: ITree, element: Marker, templateData: IMarkerTemplateData) { let marker = element.raw; + templateData.icon.className = 'icon ' + Renderer.iconClassNameFor(marker); + dom.removeClass(templateData.lightbulb, 'quick-fixes'); templateData.source.set(marker.source, element.sourceMatches); dom.toggleClass(templateData.source.element, 'marker-source', !!marker.source); @@ -217,6 +221,11 @@ export class Renderer implements IRenderer { templateData.description.element.title = marker.message; templateData.lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(marker.startLineNumber, marker.startColumn); + + const parent = tree.getNavigator(element).parent(); + if (parent instanceof ResourceMarkers) { + parent.hasFixes(element).then(hasFixes => dom.toggleClass(templateData.lightbulb, 'quick-fixes', hasFixes)); + } } private renderRelatedInfoElement(tree: ITree, element: RelatedInformation, templateData: IRelatedInformationTemplateData) { diff --git a/src/vs/workbench/parts/markers/electron-browser/media/lightbulb-dark.svg b/src/vs/workbench/parts/markers/electron-browser/media/lightbulb-dark.svg new file mode 100644 index 0000000000000000000000000000000000000000..520f78f3e5554a55b3d8e304c7ceec315001ce84 --- /dev/null +++ b/src/vs/workbench/parts/markers/electron-browser/media/lightbulb-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/parts/markers/electron-browser/media/lightbulb.svg b/src/vs/workbench/parts/markers/electron-browser/media/lightbulb.svg new file mode 100644 index 0000000000000000000000000000000000000000..b359604661616fd01365a5c72800cf9184763c9f --- /dev/null +++ b/src/vs/workbench/parts/markers/electron-browser/media/lightbulb.svg @@ -0,0 +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 0d0649cfabcc4fad1e74e0c80d35ac81f0d3f437..b351563aa6c115c69fffa8106f8e692434ed32c8 100644 --- a/src/vs/workbench/parts/markers/electron-browser/media/markers.css +++ b/src/vs/workbench/parts/markers/electron-browser/media/markers.css @@ -164,4 +164,19 @@ .vs-dark .markers-panel .icon.info { background: url('status-info-inverse.svg') center center no-repeat; +} + +.markers-panel .icon.lightbulb { + background: url('lightbulb.svg') center/40% no-repeat; + position: absolute; + width: 24px; + height: 30px; +} + +.vs-dark .markers-panel .icon.lightbulb { + background: url('lightbulb-dark.svg') center/40% no-repeat; +} + +.markers-panel .icon.lightbulb:not(.quick-fixes) { + display: none; } \ No newline at end of file