gotoErrorWidget.ts 10.6 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 { isFalsyOrEmpty } 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 49 50 51 52 53 54 55
		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) {
56
				onRelatedInformation(related);
57 58
			}
		}));
59

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

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

80
	update({ source, message, relatedInformation }: IMarker): void {
81 82

		if (source) {
J
Johannes Rieken 已提交
83 84
			this._lines = 0;
			this._longestLineLength = 0;
85 86 87 88
			const indent = new Array(source.length + 3 + 1).join(' ');
			const lines = message.split(/\r\n|\r|\n/g);
			for (let i = 0; i < lines.length; i++) {
				let line = lines[i];
J
Johannes Rieken 已提交
89 90
				this._lines += 1;
				this._longestLineLength = Math.max(line.length, this._longestLineLength);
91 92 93 94 95 96 97
				if (i === 0) {
					message = `[${source}] ${line}`;
				} else {
					message += `\n${indent}${line}`;
				}
			}
		} else {
J
Johannes Rieken 已提交
98 99
			this._lines = 1;
			this._longestLineLength = message.length;
100 101
		}

102 103 104 105
		dom.clearNode(this._relatedBlock);

		if (!isFalsyOrEmpty(relatedInformation)) {
			this._relatedBlock.style.paddingTop = `${Math.floor(this._editor.getConfiguration().lineHeight * .66)}px`;
J
Johannes Rieken 已提交
106
			this._lines += 1;
107

108
			for (const related of relatedInformation) {
109 110 111 112 113

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

				let relatedResource = document.createElement('span');
				dom.addClass(relatedResource, 'filename');
114
				relatedResource.innerHTML = `${getBaseLabel(related.resource)}(${related.startLineNumber}, ${related.startColumn}): `;
115
				relatedResource.title = getPathLabel(related.resource, undefined);
116 117 118 119 120 121 122 123 124
				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 已提交
125
				this._lines += 1;
126
				this._relatedBlock.appendChild(container);
127 128 129
			}
		}

130 131
		this._messageBlock.innerText = message;
		this._editor.applyFontInfo(this._messageBlock);
J
Johannes Rieken 已提交
132 133 134 135
		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 });
136 137 138
	}

	layout(height: number, width: number): void {
J
Johannes Rieken 已提交
139 140 141 142 143 144
		this._scrollable.getDomNode().style.height = `${height}px`;
		this._scrollable.setScrollDimensions({ width, height });
	}

	getHeightInLines(): number {
		return Math.min(17, this._lines);
145 146 147 148 149 150 151 152 153 154
	}
}

export class MarkerNavigationWidget extends ZoneWidget {

	private _parentContainer: HTMLElement;
	private _container: HTMLElement;
	private _title: HTMLElement;
	private _message: MessageWidget;
	private _callOnDispose: IDisposable[] = [];
J
Johannes Rieken 已提交
155
	private _severity: MarkerSeverity;
156
	private _backgroundColor: Color;
157 158 159
	private _onDidSelectRelatedInformation = new Emitter<IRelatedInformation>();

	readonly onDidSelectRelatedInformation: Event<IRelatedInformation> = this._onDidSelectRelatedInformation.event;
160 161 162

	constructor(
		editor: ICodeEditor,
163
		private _themeService: IThemeService
164 165
	) {
		super(editor, { showArrow: true, showFrame: true, isAccessible: true });
J
Johannes Rieken 已提交
166
		this._severity = MarkerSeverity.Warning;
167 168 169 170 171 172 173 174 175 176 177
		this._backgroundColor = Color.white;

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

		this.create();
	}

	private _applyTheme(theme: ITheme) {
		this._backgroundColor = theme.getColor(editorMarkerNavigationBackground);
		let colorId = editorMarkerNavigationError;
J
Johannes Rieken 已提交
178
		if (this._severity === MarkerSeverity.Warning) {
179
			colorId = editorMarkerNavigationWarning;
J
Johannes Rieken 已提交
180
		} else if (this._severity === MarkerSeverity.Info) {
181 182
			colorId = editorMarkerNavigationInfo;
		}
M
Matt Bierner 已提交
183
		const frameColor = theme.getColor(colorId);
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
		this.style({
			arrowColor: frameColor,
			frameColor: frameColor
		}); // style() will trigger _applyStyles
	}

	protected _applyStyles(): void {
		if (this._parentContainer) {
			this._parentContainer.style.backgroundColor = this._backgroundColor.toString();
		}
		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);

219
		this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related));
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
		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);
		let position = range.containsPosition(this.editor.getPosition()) ? this.editor.getPosition() : range.getStartPosition();
		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);
263
		this._container.style.height = `${heightInPixel}px`;
264 265 266 267 268 269 270
	}

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

	private computeRequiredHeight() {
J
Johannes Rieken 已提交
271
		return 1 + this._message.getHeightInLines();
272 273 274 275 276 277 278 279 280 281 282 283 284
	}
}

// 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.'));