gotoErrorWidget.ts 11.4 KB
Newer Older
1 2 3 4 5 6 7 8 9
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import 'vs/css!./gotoErrorWidget';
import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
10
import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers';
11 12 13 14 15 16 17 18 19 20 21 22
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
import { registerColor, oneOf } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { Color } from 'vs/base/common/color';
import { AccessibilitySupport } from 'vs/base/common/platform';
import { editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoForeground, editorInfoBorder } from 'vs/editor/common/view/editorColorRegistry';
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { ScrollType } from 'vs/editor/common/editorCommon';
J
Johannes Rieken 已提交
23
import { getBaseLabel, getPathLabel } from 'vs/base/common/labels';
24
import { isNonEmptyArray } from 'vs/base/common/arrays';
25
import { Event, Emitter } from 'vs/base/common/event';
26 27 28

class MessageWidget {

J
Johannes Rieken 已提交
29 30
	private _lines: number = 0;
	private _longestLineLength: number = 0;
31 32

	private readonly _editor: ICodeEditor;
J
Johannes Rieken 已提交
33 34
	private readonly _messageBlock: HTMLDivElement;
	private readonly _relatedBlock: HTMLDivElement;
35
	private readonly _scrollable: ScrollableElement;
36
	private readonly _relatedDiagnostics = new WeakMap<HTMLElement, IRelatedInformation>();
37 38
	private readonly _disposables: IDisposable[] = [];

39
	constructor(parent: HTMLElement, editor: ICodeEditor, onRelatedInformation: (related: IRelatedInformation) => void, ) {
40 41
		this._editor = editor;

42 43 44 45 46
		const domNode = document.createElement('div');
		domNode.className = 'descriptioncontainer';
		domNode.setAttribute('aria-live', 'assertive');
		domNode.setAttribute('role', 'alert');

J
Johannes Rieken 已提交
47
		this._messageBlock = document.createElement('div');
48
		dom.addClass(this._messageBlock, 'message');
49 50 51 52 53 54 55 56
		domNode.appendChild(this._messageBlock);

		this._relatedBlock = document.createElement('div');
		domNode.appendChild(this._relatedBlock);
		this._disposables.push(dom.addStandardDisposableListener(this._relatedBlock, 'click', event => {
			event.preventDefault();
			const related = this._relatedDiagnostics.get(event.target);
			if (related) {
57
				onRelatedInformation(related);
58 59
			}
		}));
60

61
		this._scrollable = new ScrollableElement(domNode, {
62
			horizontal: ScrollbarVisibility.Auto,
J
Johannes Rieken 已提交
63
			vertical: ScrollbarVisibility.Auto,
64
			useShadows: false,
J
Johannes Rieken 已提交
65 66
			horizontalScrollbarSize: 3,
			verticalScrollbarSize: 3
67 68 69
		});
		dom.addClass(this._scrollable.getDomNode(), 'block');
		parent.appendChild(this._scrollable.getDomNode());
J
Johannes Rieken 已提交
70 71 72 73
		this._disposables.push(this._scrollable.onScroll(e => {
			domNode.style.left = `-${e.scrollLeft}px`;
			domNode.style.top = `-${e.scrollTop}px`;
		}));
74 75 76 77 78 79 80
		this._disposables.push(this._scrollable);
	}

	dispose(): void {
		dispose(this._disposables);
	}

S
Sandeep Somavarapu 已提交
81
	update({ source, message, relatedInformation, code }: IMarker): void {
82

83 84 85 86 87 88 89 90 91 92 93 94 95
		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);
S
Sandeep Somavarapu 已提交
96 97 98
			if (line === '') {
				lastLineElement.style.height = lastLineElement.style.lineHeight;
			}
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
			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);
116 117 118
			}
		}

119
		dom.clearNode(this._relatedBlock);
120
		if (isNonEmptyArray(relatedInformation)) {
S
Sandeep Somavarapu 已提交
121 122
			const relatedInformationNode = this._relatedBlock.appendChild(document.createElement('div'));
			relatedInformationNode.style.paddingTop = `${Math.floor(this._editor.getConfiguration().lineHeight * 0.66)}px`;
J
Johannes Rieken 已提交
123
			this._lines += 1;
124

125
			for (const related of relatedInformation) {
126 127 128 129 130

				let container = document.createElement('div');

				let relatedResource = document.createElement('span');
				dom.addClass(relatedResource, 'filename');
131
				relatedResource.innerHTML = `${getBaseLabel(related.resource)}(${related.startLineNumber}, ${related.startColumn}): `;
132
				relatedResource.title = getPathLabel(related.resource, undefined);
133 134 135 136 137 138 139 140 141
				this._relatedDiagnostics.set(relatedResource, related);

				let relatedMessage = document.createElement('span');
				relatedMessage.innerText = related.message;
				this._editor.applyFontInfo(relatedMessage);

				container.appendChild(relatedResource);
				container.appendChild(relatedMessage);

J
Johannes Rieken 已提交
142
				this._lines += 1;
S
Sandeep Somavarapu 已提交
143
				relatedInformationNode.appendChild(container);
144 145 146
			}
		}

J
Johannes Rieken 已提交
147 148 149 150
		const fontInfo = this._editor.getConfiguration().fontInfo;
		const scrollWidth = Math.ceil(fontInfo.typicalFullwidthCharacterWidth * this._longestLineLength * 0.75);
		const scrollHeight = fontInfo.lineHeight * this._lines;
		this._scrollable.setScrollDimensions({ scrollWidth, scrollHeight });
151 152 153
	}

	layout(height: number, width: number): void {
J
Johannes Rieken 已提交
154 155 156 157 158 159
		this._scrollable.getDomNode().style.height = `${height}px`;
		this._scrollable.setScrollDimensions({ width, height });
	}

	getHeightInLines(): number {
		return Math.min(17, this._lines);
160 161 162 163 164 165 166 167 168 169
	}
}

export class MarkerNavigationWidget extends ZoneWidget {

	private _parentContainer: HTMLElement;
	private _container: HTMLElement;
	private _title: HTMLElement;
	private _message: MessageWidget;
	private _callOnDispose: IDisposable[] = [];
J
Johannes Rieken 已提交
170
	private _severity: MarkerSeverity;
171
	private _backgroundColor?: Color;
172 173 174
	private _onDidSelectRelatedInformation = new Emitter<IRelatedInformation>();

	readonly onDidSelectRelatedInformation: Event<IRelatedInformation> = this._onDidSelectRelatedInformation.event;
175 176 177

	constructor(
		editor: ICodeEditor,
178
		private _themeService: IThemeService
179 180
	) {
		super(editor, { showArrow: true, showFrame: true, isAccessible: true });
J
Johannes Rieken 已提交
181
		this._severity = MarkerSeverity.Warning;
182 183 184 185 186 187 188 189 190
		this._backgroundColor = Color.white;

		this._applyTheme(_themeService.getTheme());
		this._callOnDispose.push(_themeService.onThemeChange(this._applyTheme.bind(this)));

		this.create();
	}

	private _applyTheme(theme: ITheme) {
J
Johannes Rieken 已提交
191
		this._backgroundColor = theme.getColor(editorMarkerNavigationBackground);
192
		let colorId = editorMarkerNavigationError;
J
Johannes Rieken 已提交
193
		if (this._severity === MarkerSeverity.Warning) {
194
			colorId = editorMarkerNavigationWarning;
J
Johannes Rieken 已提交
195
		} else if (this._severity === MarkerSeverity.Info) {
196 197
			colorId = editorMarkerNavigationInfo;
		}
M
Matt Bierner 已提交
198
		const frameColor = theme.getColor(colorId);
199 200 201 202 203 204 205 206
		this.style({
			arrowColor: frameColor,
			frameColor: frameColor
		}); // style() will trigger _applyStyles
	}

	protected _applyStyles(): void {
		if (this._parentContainer) {
M
Matt Bierner 已提交
207
			this._parentContainer.style.backgroundColor = this._backgroundColor ? this._backgroundColor.toString() : '';
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
		}
		super._applyStyles();
	}

	dispose(): void {
		this._callOnDispose = dispose(this._callOnDispose);
		super.dispose();
	}

	focus(): void {
		this._parentContainer.focus();
	}

	protected _fillContainer(container: HTMLElement): void {
		this._parentContainer = container;
		dom.addClass(container, 'marker-widget');
		this._parentContainer.tabIndex = 0;
		this._parentContainer.setAttribute('role', 'tooltip');

		this._container = document.createElement('div');
		container.appendChild(this._container);

		this._title = document.createElement('div');
		this._title.className = 'block title';
		this._container.appendChild(this._title);

234
		this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related));
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
		this._disposables.push(this._message);
	}

	show(where: Position, heightInLines: number): void {
		throw new Error('call showAtMarker');
	}

	showAtMarker(marker: IMarker, markerIdx: number, markerCount: number): void {
		// update:
		// * title
		// * message
		this._container.classList.remove('stale');
		this._title.innerHTML = nls.localize('title.wo_source', "({0}/{1})", markerIdx, markerCount);
		this._message.update(marker);

		// update frame color (only applied on 'show')
		this._severity = marker.severity;
		this._applyTheme(this._themeService.getTheme());

		// show
		let range = Range.lift(marker);
M
Matt Bierner 已提交
256 257
		const editorPosition = this.editor.getPosition();
		let position = editorPosition && range.containsPosition(editorPosition) ? editorPosition : range.getStartPosition();
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
		super.show(position, this.computeRequiredHeight());

		this.editor.revealPositionInCenter(position, ScrollType.Smooth);

		if (this.editor.getConfiguration().accessibilitySupport !== AccessibilitySupport.Disabled) {
			this.focus();
		}
	}

	updateMarker(marker: IMarker): void {
		this._container.classList.remove('stale');
		this._message.update(marker);
	}

	showStale() {
		this._container.classList.add('stale');
		this._relayout();
	}

	protected _doLayout(heightInPixel: number, widthInPixel: number): void {
		this._message.layout(heightInPixel, widthInPixel);
279
		this._container.style.height = `${heightInPixel}px`;
280 281 282 283 284 285 286
	}

	protected _relayout(): void {
		super._relayout(this.computeRequiredHeight());
	}

	private computeRequiredHeight() {
J
Johannes Rieken 已提交
287
		return 1 + this._message.getHeightInLines();
288 289 290 291 292 293 294 295 296 297 298 299 300
	}
}

// theming

let errorDefault = oneOf(editorErrorForeground, editorErrorBorder);
let warningDefault = oneOf(editorWarningForeground, editorWarningBorder);
let infoDefault = oneOf(editorInfoForeground, editorInfoBorder);

export const editorMarkerNavigationError = registerColor('editorMarkerNavigationError.background', { dark: errorDefault, light: errorDefault, hc: errorDefault }, nls.localize('editorMarkerNavigationError', 'Editor marker navigation widget error color.'));
export const editorMarkerNavigationWarning = registerColor('editorMarkerNavigationWarning.background', { dark: warningDefault, light: warningDefault, hc: warningDefault }, nls.localize('editorMarkerNavigationWarning', 'Editor marker navigation widget warning color.'));
export const editorMarkerNavigationInfo = registerColor('editorMarkerNavigationInfo.background', { dark: infoDefault, light: infoDefault, hc: infoDefault }, nls.localize('editorMarkerNavigationInfo', 'Editor marker navigation widget info color.'));
export const editorMarkerNavigationBackground = registerColor('editorMarkerNavigation.background', { dark: '#2D2D30', light: Color.white, hc: '#0C141F' }, nls.localize('editorMarkerNavigationBackground', 'Editor marker navigation widget background.'));