#1927 Render multiline diagnostics in problems view

This commit is contained in:
Sandeep Somavarapu 2018-12-17 17:33:34 +01:00
parent 27fd58c206
commit f9db8d784f
6 changed files with 162 additions and 94 deletions

View file

@ -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,

View file

@ -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;

View file

@ -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,

View file

@ -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<TreeElement> {
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<Marker, MarkerFilterData, I
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 });
data.icon = dom.append(container, dom.$('.icon'));
data.source = new HighlightedLabel(dom.append(container, dom.$('')), false);
data.description = new HighlightedLabel(dom.append(container, dom.$('.marker-description')), false);
data.code = new HighlightedLabel(dom.append(container, dom.$('')), false);
data.lnCol = dom.append(container, dom.$('span.marker-line'));
data.markerWidget = new MarkerWidget(container, this.actionItemProvider, this.instantiationService);
return data;
}
renderElement(node: ITreeNode<Marker, MarkerFilterData>, _: 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<TreeElement, FilterData> {
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;

View file

@ -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;
}

View file

@ -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 => {