diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.css b/src/vs/editor/contrib/gotoError/gotoErrorWidget.css index f5f1f38325d..2ecdf81af6f 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.css +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.css @@ -35,6 +35,11 @@ .monaco-editor .marker-widget .descriptioncontainer .message { display: flex; + flex-direction: column; +} + +.monaco-editor .marker-widget .descriptioncontainer .message .details { + padding-left: 6px; } .monaco-editor .marker-widget .descriptioncontainer .message .source, diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index cba65d3fa1d..a21d90fb258 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -80,20 +80,40 @@ class MessageWidget { update({ source, message, relatedInformation, code }: IMarker): void { - if (source) { - const lines = message.split(/\r\n|\r|\n/g); - this._lines = lines.length; - this._longestLineLength = 0; - for (const line of lines) { - this._longestLineLength = Math.max(line.length, this._longestLineLength); + const lines = message.split(/\r\n|\r|\n/g); + this._lines = lines.length; + this._longestLineLength = 0; + for (const line of lines) { + this._longestLineLength = Math.max(line.length, this._longestLineLength); + } + + dom.clearNode(this._messageBlock); + let lastLineElement = this._messageBlock; + for (const line of lines) { + lastLineElement = document.createElement('div'); + lastLineElement.innerText = line; + this._editor.applyFontInfo(lastLineElement); + this._messageBlock.appendChild(lastLineElement); + } + if (source || code) { + const detailsElement = document.createElement('span'); + dom.addClass(detailsElement, 'details'); + lastLineElement.appendChild(detailsElement); + if (source) { + const sourceElement = document.createElement('span'); + sourceElement.innerText = source; + dom.addClass(sourceElement, 'source'); + detailsElement.appendChild(sourceElement); + } + if (code) { + const codeElement = document.createElement('span'); + codeElement.innerText = `(${code})`; + dom.addClass(codeElement, 'code'); + detailsElement.appendChild(codeElement); } - } else { - this._lines = 1; - this._longestLineLength = message.length; } dom.clearNode(this._relatedBlock); - if (isNonEmptyArray(relatedInformation)) { this._relatedBlock.style.paddingTop = `${Math.floor(this._editor.getConfiguration().lineHeight * .66)}px`; this._lines += 1; @@ -120,26 +140,6 @@ class MessageWidget { } } - dom.clearNode(this._messageBlock); - if (source) { - const sourceElement = document.createElement('div'); - sourceElement.innerText = `[${source}] `; - dom.addClass(sourceElement, 'source'); - this._editor.applyFontInfo(sourceElement); - this._messageBlock.appendChild(sourceElement); - } - const messageElement = document.createElement('div'); - messageElement.innerText = message; - this._editor.applyFontInfo(messageElement); - this._messageBlock.appendChild(messageElement); - if (code) { - const codeElement = document.createElement('div'); - codeElement.innerText = ` [${code}]`; - dom.addClass(codeElement, 'code'); - this._editor.applyFontInfo(codeElement); - this._messageBlock.appendChild(codeElement); - } - const fontInfo = this._editor.getConfiguration().fontInfo; const scrollWidth = Math.ceil(fontInfo.typicalFullwidthCharacterWidth * this._longestLineLength * 0.75); const scrollHeight = fontInfo.lineHeight * this._lines; diff --git a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts index 5fba0d276cb..7b4b86eea42 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersModel.ts @@ -80,6 +80,14 @@ export class Marker { readonly relatedInformation: RelatedInformation[] = [] ) { } + private _lines: string[]; + get lines(): string[] { + if (!this._lines) { + this._lines = this.marker.message.split(/\r\n|\r|\n/g); + } + return this._lines; + } + toString(): string { return JSON.stringify({ ...this.marker, diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts index 86fc82a7afd..643c233ffc0 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts @@ -15,7 +15,7 @@ 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, dispose } from 'vs/base/common/lifecycle'; +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 { ILabelService } from 'vs/platform/label/common/label'; @@ -26,6 +26,7 @@ import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/marke import { IMatch } from 'vs/base/common/filters'; import { Event } from 'vs/base/common/event'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { isUndefinedOrNull } from 'vs/base/common/types'; export type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -36,12 +37,7 @@ interface IResourceMarkersTemplateData { } interface IMarkerTemplateData { - icon: HTMLElement; - actionBar: ActionBar; - source: HighlightedLabel; - description: HighlightedLabel; - lnCol: HTMLElement; - code: HighlightedLabel; + markerWidget: MarkerWidget; } interface IRelatedInformationTemplateData { @@ -78,7 +74,10 @@ const enum TemplateId { export class VirtualDelegate implements IListVirtualDelegate { - getHeight(): number { + getHeight(element: TreeElement): number { + if (element instanceof Marker) { + return element.lines.length * 22; + } return 22; } @@ -110,7 +109,7 @@ interface ResourceMarkersFilterData { interface MarkerFilterData { type: FilterDataType.Marker; - messageMatches: IMatch[]; + lineMatches: IMatch[][]; sourceMatches: IMatch[]; codeMatches: IMatch[]; } @@ -218,44 +217,91 @@ export class MarkerRenderer implements ITreeRenderer, _: 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); - - templateData.source.set(marker.source, sourceMatches); - dom.toggleClass(templateData.source.element, 'marker-source', !!marker.source); - - templateData.actionBar.clear(); - const quickFixAction = this.instantiationService.createInstance(QuickFixAction, node.element); - templateData.actionBar.push([quickFixAction], { icon: true, label: false }); - - 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, codeMatches); - - templateData.lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(marker.startLineNumber, marker.startColumn); + templateData.markerWidget.render(node.element, node.filterData); } disposeTemplate(templateData: IMarkerTemplateData): void { - templateData.description.dispose(); - templateData.source.dispose(); - templateData.actionBar.dispose(); + templateData.markerWidget.dispose(); + } + +} + +class MarkerWidget extends Disposable { + + private readonly actionBar: ActionBar; + private readonly icon: HTMLElement; + private readonly messageContainer: HTMLElement; + private disposables: IDisposable[] = []; + + constructor( + parent: HTMLElement, actionItemProvider: IActionItemProvider, + private instantiationService: IInstantiationService + ) { + super(); + const actionsContainer = dom.append(parent, dom.$('.actions')); + this.actionBar = this._register(new ActionBar(actionsContainer, { actionItemProvider })); + this.icon = dom.append(parent, dom.$('.icon')); + this.messageContainer = dom.append(parent, dom.$('.marker-message')); + this._register(toDisposable(() => this.disposables = dispose(this.disposables))); + } + + render(element: Marker, filterData: MarkerFilterData): void { + const { marker, lines } = element; + if (this.disposables.length) { + this.disposables = dispose(this.disposables); + } + dom.clearNode(this.messageContainer); + + this.icon.className = 'icon ' + MarkerWidget.iconClassNameFor(marker); + + this.actionBar.clear(); + const quickFixAction = this.instantiationService.createInstance(QuickFixAction, element); + this.actionBar.push([quickFixAction], { icon: true, label: false }); + this.onDidQuickFixesActionEnable(quickFixAction.enabled); + quickFixAction.onDidChange(({ enabled }) => { + if (!isUndefinedOrNull(enabled)) { + this.onDidQuickFixesActionEnable(enabled); + } + }, this, this.disposables); + + const lineMatches = filterData && filterData.lineMatches || []; + let lastLineElement = this.messageContainer; + for (let index = 0; index < lines.length; index++) { + lastLineElement = dom.append(this.messageContainer, dom.$('.marker-message-line')); + const highlightedLabel = new HighlightedLabel(lastLineElement, false); + highlightedLabel.set(lines[index], lineMatches[index]); + this.disposables.push(highlightedLabel); + } + + this.renderDetails(marker, filterData, lastLineElement); + } + + private onDidQuickFixesActionEnable(enabled: boolean): void { + dom.toggleClass(this.icon, 'quickFix', enabled); + } + + private renderDetails(marker: IMarker, filterData: MarkerFilterData, parent: HTMLElement): void { + dom.addClass(parent, 'details-container'); + const sourceMatches = filterData && filterData.sourceMatches || []; + const codeMatches = filterData && filterData.codeMatches || []; + + const source = new HighlightedLabel(dom.append(parent, dom.$('')), false); + source.set(marker.source, sourceMatches); + dom.toggleClass(source.element, 'marker-source', !!marker.source); + + const code = new HighlightedLabel(dom.append(parent, dom.$('')), false); + code.set(marker.code, codeMatches); + dom.toggleClass(code.element, 'marker-code', !!marker.code); + + const lnCol = dom.append(parent, dom.$('span.marker-line')); + lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(marker.startLineNumber, marker.startColumn); + + this.disposables.push(...[source, code]); } private static iconClassNameFor(element: IMarker): string { @@ -369,12 +415,15 @@ export class Filter implements ITreeFilter { return true; } - const messageMatches = FilterOptions._messageFilter(this.options.textFilter, marker.marker.message); + const lineMatches: IMatch[][] = []; + for (const line of marker.lines) { + lineMatches.push(FilterOptions._messageFilter(this.options.textFilter, line) || []); + } 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 || [] } }; + if (sourceMatches || codeMatches || lineMatches.some(lineMatch => lineMatch.length > 0)) { + return { visibility: true, data: { type: FilterDataType.Marker, lineMatches, sourceMatches: sourceMatches || [], codeMatches: codeMatches || [] } }; } return parentVisibility; 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 fbadd661122..68fc194c940 100644 --- a/src/vs/workbench/parts/markers/electron-browser/media/markers.css +++ b/src/vs/workbench/parts/markers/electron-browser/media/markers.css @@ -93,6 +93,7 @@ .markers-panel .markers-panel-container .tree-container .monaco-tl-contents { display: flex; line-height: 22px; + margin-right: 20px; } .hc-black .markers-panel .markers-panel-container .tree-container .monaco-tl-contents { @@ -108,25 +109,29 @@ margin-left: 10px; } -.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 .monaco-tl-contents .marker-message { + display: flex; + flex-direction: column; + white-space: pre; + flex: 1; } -.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 .monaco-tl-contents .marker-message .details-container { + display: flex; } -.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: '['; + content: '('; } -.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: ']'; + content: ')'; +} + +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-message, +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container .marker-source, +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container .marker-line { + margin-left: 6px; } .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-source, @@ -143,7 +148,6 @@ .markers-panel .monaco-tl-contents > .icon { height: 22px; - margin-right: 6px; flex: 0 0 16px; } @@ -180,17 +184,19 @@ background: url('lightbulb-dark.svg') center/80% no-repeat; } -.markers-panel .monaco-tl-contents > .actions { - flex: 0 0 16px; -} - -.markers-panel .monaco-tl-contents > .actions .monaco-action-bar { +.markers-panel .monaco-tl-contents .actions .monaco-action-bar { display: none; } -.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 { +.markers-panel .monaco-list-row:hover .monaco-tl-contents > .icon.quickFix, +.markers-panel .monaco-list-row.selected .monaco-tl-contents > .icon.quickFix, +.markers-panel .monaco-list-row.focused .monaco-tl-contents > .icon.quickFix { + display: none; +} + +.markers-panel .monaco-list-row:hover .monaco-tl-contents .actions .monaco-action-bar, +.markers-panel .monaco-list-row.selected .monaco-tl-contents .actions .monaco-action-bar, +.markers-panel .monaco-list-row.focused .monaco-tl-contents .actions .monaco-action-bar { display: block; } diff --git a/src/vs/workbench/parts/markers/electron-browser/messages.ts b/src/vs/workbench/parts/markers/electron-browser/messages.ts index 93cf9007d64..07b0ed7de14 100644 --- a/src/vs/workbench/parts/markers/electron-browser/messages.ts +++ b/src/vs/workbench/parts/markers/electron-browser/messages.ts @@ -42,7 +42,7 @@ export default class Messages { public static MARKERS_PANEL_SINGLE_UNKNOWN_LABEL: string = nls.localize('markers.panel.single.unknown.label', "1 Unknown"); public static readonly MARKERS_PANEL_MULTIPLE_UNKNOWNS_LABEL = (noOfUnknowns: number): string => { return nls.localize('markers.panel.multiple.unknowns.label', "{0} Unknowns", '' + noOfUnknowns); }; - public static readonly MARKERS_PANEL_AT_LINE_COL_NUMBER = (ln: number, col: number): string => { return nls.localize('markers.panel.at.ln.col.number', "({0}, {1})", '' + ln, '' + col); }; + public static readonly MARKERS_PANEL_AT_LINE_COL_NUMBER = (ln: number, col: number): string => { return nls.localize('markers.panel.at.ln.col.number', "[{0}, {1}]", '' + ln, '' + col); }; 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 => {