modesContentHover.ts 23.1 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

A
Alex Dima 已提交
6
import * as nls from 'vs/nls';
M
Michel Kaporin 已提交
7
import * as dom from 'vs/base/browser/dom';
A
Alex Dima 已提交
8 9
import { CancellationToken } from 'vs/base/common/cancellation';
import { Color, RGBA } from 'vs/base/common/color';
10
import { IMarkdownString, MarkdownString, isEmptyMarkdownString, markedStringsEquals } from 'vs/base/common/htmlContent';
11
import { Disposable, IDisposable, toDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle';
A
Alex Dima 已提交
12 13 14
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
A
Alex Dima 已提交
15
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
16
import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor } from 'vs/editor/common/modes';
A
Alex Dima 已提交
17 18
import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color';
import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector';
19 20
import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel';
import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/colorPickerWidget';
A
Alex Dima 已提交
21 22 23 24
import { getHover } from 'vs/editor/contrib/hover/getHover';
import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/hoverOperation';
import { ContentHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets';
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
A
Alex Dima 已提交
25
import { IThemeService } from 'vs/platform/theme/common/themeService';
M
Matt Bierner 已提交
26
import { coalesce, isNonEmptyArray, asArray } from 'vs/base/common/arrays';
S
Sandeep Somavarapu 已提交
27
import { IMarker, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
B
Benjamin Pasero 已提交
28
import { basename } from 'vs/base/common/resources';
29
import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService';
S
Sandeep Somavarapu 已提交
30 31
import { onUnexpectedError } from 'vs/base/common/errors';
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
S
Sandeep Somavarapu 已提交
32 33
import { MarkerController, NextMarkerAction } from 'vs/editor/contrib/gotoError/gotoError';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
34 35 36 37 38 39 40 41
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
import { applyCodeAction, QuickFixAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
import { Action } from 'vs/base/common/actions';
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
42
import { IModeService } from 'vs/editor/common/services/modeService';
M
Matt Bierner 已提交
43
import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
44

M
Michel Kaporin 已提交
45
const $ = dom.$;
E
Erich Gamma 已提交
46

47 48 49 50
class ColorHover {

	constructor(
		public readonly range: IRange,
J
Joao Moreno 已提交
51
		public readonly color: IColor,
52
		public readonly provider: DocumentColorProvider
53 54 55
	) { }
}

56 57 58 59 60 61 62 63 64
class MarkerHover {

	constructor(
		public readonly range: IRange,
		public readonly marker: IMarker,
	) { }
}

type HoverPart = MarkdownHover | ColorHover | MarkerHover;
E
Erich Gamma 已提交
65

66
class ModesContentComputer implements IHoverComputer<HoverPart[]> {
E
Erich Gamma 已提交
67

68
	private readonly _editor: ICodeEditor;
69
	private _result: HoverPart[];
M
Matt Bierner 已提交
70
	private _range?: Range;
E
Erich Gamma 已提交
71

72 73
	constructor(
		editor: ICodeEditor,
74
		private readonly _markerDecorationsService: IMarkerDecorationsService
75
	) {
E
Erich Gamma 已提交
76 77 78
		this._editor = editor;
	}

J
Joao Moreno 已提交
79
	setRange(range: Range): void {
E
Erich Gamma 已提交
80 81 82 83
		this._range = range;
		this._result = [];
	}

J
Joao Moreno 已提交
84
	clearResult(): void {
E
Erich Gamma 已提交
85 86 87
		this._result = [];
	}

88
	computeAsync(token: CancellationToken): Promise<HoverPart[]> {
A
Alex Dima 已提交
89 90 91 92
		if (!this._editor.hasModel() || !this._range) {
			return Promise.resolve([]);
		}

J
Joao Moreno 已提交
93
		const model = this._editor.getModel();
E
Erich Gamma 已提交
94

A
Alex Dima 已提交
95
		if (!HoverProviderRegistry.has(model)) {
A
Alex Dima 已提交
96
			return Promise.resolve([]);
E
Erich Gamma 已提交
97 98
		}

99
		return getHover(model, new Position(
100 101
			this._range.startLineNumber,
			this._range.startColumn
102
		), token);
E
Erich Gamma 已提交
103 104
	}

105
	computeSync(): HoverPart[] {
A
Alex Dima 已提交
106 107 108 109
		if (!this._editor.hasModel() || !this._range) {
			return [];
		}

110
		const model = this._editor.getModel();
J
Joao Moreno 已提交
111
		const lineNumber = this._range.startLineNumber;
E
Erich Gamma 已提交
112 113 114

		if (lineNumber > this._editor.getModel().getLineCount()) {
			// Illegal line number => no results
J
Joao Moreno 已提交
115
			return [];
E
Erich Gamma 已提交
116 117
		}

118
		const colorDetector = ColorDetector.get(this._editor);
119
		const maxColumn = model.getLineMaxColumn(lineNumber);
J
Joao Moreno 已提交
120
		const lineDecorations = this._editor.getLineDecorations(lineNumber);
121
		let didFindColor = false;
J
Joao Moreno 已提交
122

123 124
		const hoverRange = this._range;
		const result = lineDecorations.map((d): HoverPart | null => {
J
Joao Moreno 已提交
125 126 127
			const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1;
			const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn;

128
			if (startColumn > hoverRange.startColumn || hoverRange.endColumn > endColumn) {
J
Joao Moreno 已提交
129 130 131
				return null;
			}

132
			const range = new Range(hoverRange.startLineNumber, startColumn, hoverRange.startLineNumber, endColumn);
133 134 135 136 137
			const marker = this._markerDecorationsService.getMarker(model, d);
			if (marker) {
				return new MarkerHover(range, marker);
			}

138
			const colorData = colorDetector.getColorData(d.range.getStartPosition());
J
Joao Moreno 已提交
139

140
			if (!didFindColor && colorData) {
141
				didFindColor = true;
142

143 144
				const { color, range } = colorData.colorInfo;
				return new ColorHover(range, color, colorData.provider);
145
			} else {
146
				if (isEmptyMarkdownString(d.options.hoverMessage)) {
147
					return null;
E
Erich Gamma 已提交
148
				}
J
Joao Moreno 已提交
149

M
Matt Bierner 已提交
150
				const contents: IMarkdownString[] = d.options.hoverMessage ? asArray(d.options.hoverMessage) : [];
151 152
				return { contents, range };
			}
E
Erich Gamma 已提交
153
		});
J
Joao Moreno 已提交
154

155
		return coalesce(result);
E
Erich Gamma 已提交
156 157
	}

158
	onResult(result: HoverPart[], isFromSynchronousComputation: boolean): void {
E
Erich Gamma 已提交
159 160
		// Always put synchronous messages before asynchronous ones
		if (isFromSynchronousComputation) {
161
			this._result = result.concat(this._result.sort((a, b) => {
162
				if (a instanceof ColorHover) { // sort picker messages at to the top
163
					return -1;
164
				} else if (b instanceof ColorHover) {
165 166 167 168
					return 1;
				}
				return 0;
			}));
E
Erich Gamma 已提交
169 170 171 172 173
		} else {
			this._result = this._result.concat(result);
		}
	}

174
	getResult(): HoverPart[] {
E
Erich Gamma 已提交
175 176 177
		return this._result.slice(0);
	}

178
	getResultWithLoadingMessage(): HoverPart[] {
E
Erich Gamma 已提交
179 180 181
		return this._result.slice(0).concat([this._getLoadingMessage()]);
	}

182
	private _getLoadingMessage(): HoverPart {
E
Erich Gamma 已提交
183
		return {
M
Matt Bierner 已提交
184
			range: this._range,
185
			contents: [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))]
E
Erich Gamma 已提交
186 187 188 189
		};
	}
}

190 191 192 193
interface ActionSet extends IDisposable {
	readonly actions: Action[];
}

A
Alex Dima 已提交
194
export class ModesContentHoverWidget extends ContentHoverWidget {
E
Erich Gamma 已提交
195

196
	static readonly ID = 'editor.contrib.modesContentHoverWidget';
J
Joao Moreno 已提交
197

198
	private _messages: HoverPart[];
A
Alex Dima 已提交
199
	private _lastRange: Range | null;
200 201
	private readonly _computer: ModesContentComputer;
	private readonly _hoverOperation: HoverOperation<HoverPart[]>;
202
	private _highlightDecorations: string[];
E
Erich Gamma 已提交
203
	private _isChangingDecorations: boolean;
I
isidor 已提交
204
	private _shouldFocus: boolean;
A
Alex Dima 已提交
205
	private _colorPicker: ColorPickerWidget | null;
206

J
Joao Moreno 已提交
207
	private renderDisposable: IDisposable = Disposable.None;
E
Erich Gamma 已提交
208

A
Alex Dima 已提交
209 210
	constructor(
		editor: ICodeEditor,
211
		markerDecorationsService: IMarkerDecorationsService,
S
Sandeep Somavarapu 已提交
212
		private readonly _themeService: IThemeService,
S
Sandeep Somavarapu 已提交
213
		private readonly _keybindingService: IKeybindingService,
214 215 216
		private readonly _contextMenuService: IContextMenuService,
		private readonly _bulkEditService: IBulkEditService,
		private readonly _commandService: ICommandService,
217
		private readonly _modeService: IModeService,
S
Sandeep Somavarapu 已提交
218
		private readonly _openerService: IOpenerService | null = NullOpenerService,
A
Alex Dima 已提交
219
	) {
E
Erich Gamma 已提交
220 221
		super(ModesContentHoverWidget.ID, editor);

A
Alex Dima 已提交
222 223
		this._messages = [];
		this._lastRange = null;
224
		this._computer = new ModesContentComputer(this._editor, markerDecorationsService);
E
Erich Gamma 已提交
225 226
		this._highlightDecorations = [];
		this._isChangingDecorations = false;
J
Joao Moreno 已提交
227

A
Alex Dima 已提交
228
		this._hoverOperation = new HoverOperation(
E
Erich Gamma 已提交
229
			this._computer,
230
			result => this._withResult(result, true),
E
Erich Gamma 已提交
231
			null,
A
Alex Dima 已提交
232 233
			result => this._withResult(result, false),
			this._editor.getConfiguration().contribInfo.hover.delay
E
Erich Gamma 已提交
234
		);
M
Michel Kaporin 已提交
235

B
Benjamin Pasero 已提交
236
		this._register(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.FOCUS, () => {
M
Michel Kaporin 已提交
237 238 239 240
			if (this._colorPicker) {
				dom.addClass(this.getDomNode(), 'colorpicker-hover');
			}
		}));
B
Benjamin Pasero 已提交
241
		this._register(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.BLUR, () => {
M
Michel Kaporin 已提交
242 243
			dom.removeClass(this.getDomNode(), 'colorpicker-hover');
		}));
B
Benjamin Pasero 已提交
244
		this._register(editor.onDidChangeConfiguration((e) => {
245 246
			this._hoverOperation.setHoverTime(this._editor.getConfiguration().contribInfo.hover.delay);
		}));
E
Erich Gamma 已提交
247 248
	}

J
Joao Moreno 已提交
249
	dispose(): void {
250
		this.renderDisposable.dispose();
J
Joao Moreno 已提交
251
		this.renderDisposable = Disposable.None;
252 253 254 255
		this._hoverOperation.cancel();
		super.dispose();
	}

J
Joao Moreno 已提交
256
	onModelDecorationsChanged(): void {
E
Erich Gamma 已提交
257 258 259
		if (this._isChangingDecorations) {
			return;
		}
J
Joao Moreno 已提交
260
		if (this.isVisible) {
E
Erich Gamma 已提交
261 262 263 264
			// The decorations have changed and the hover is visible,
			// we need to recompute the displayed text
			this._hoverOperation.cancel();
			this._computer.clearResult();
265 266

			if (!this._colorPicker) { // TODO@Michel ensure that displayed text for other decorations is computed even if color picker is in place
267
				this._hoverOperation.start(HoverStartMode.Delayed);
268
			}
E
Erich Gamma 已提交
269 270 271
		}
	}

272
	startShowingAt(range: Range, mode: HoverStartMode, focus: boolean): void {
J
Joao Moreno 已提交
273 274 275
		if (this._lastRange && this._lastRange.equalsRange(range)) {
			// We have to show the widget at the exact same range as before, so no work is needed
			return;
E
Erich Gamma 已提交
276 277 278 279
		}

		this._hoverOperation.cancel();

J
Joao Moreno 已提交
280
		if (this.isVisible) {
E
Erich Gamma 已提交
281 282 283
			// The range might have changed, but the hover is visible
			// Instead of hiding it completely, filter out messages that are still in the new range and
			// kick off a new computation
284
			if (!this._showAtPosition || this._showAtPosition.lineNumber !== range.startLineNumber) {
E
Erich Gamma 已提交
285 286
				this.hide();
			} else {
A
Alex Dima 已提交
287 288 289 290
				let filteredMessages: HoverPart[] = [];
				for (let i = 0, len = this._messages.length; i < len; i++) {
					const msg = this._messages[i];
					const rng = msg.range;
291
					if (rng && rng.startColumn <= range.startColumn && rng.endColumn >= range.endColumn) {
E
Erich Gamma 已提交
292 293 294 295
						filteredMessages.push(msg);
					}
				}
				if (filteredMessages.length > 0) {
296 297 298
					if (hoverContentsEquals(filteredMessages, this._messages)) {
						return;
					}
E
Erich Gamma 已提交
299 300 301 302 303 304 305 306 307
					this._renderMessages(range, filteredMessages);
				} else {
					this.hide();
				}
			}
		}

		this._lastRange = range;
		this._computer.setRange(range);
I
isidor 已提交
308
		this._shouldFocus = focus;
309
		this._hoverOperation.start(mode);
E
Erich Gamma 已提交
310 311
	}

J
Joao Moreno 已提交
312
	hide(): void {
E
Erich Gamma 已提交
313 314 315 316 317 318
		this._lastRange = null;
		this._hoverOperation.cancel();
		super.hide();
		this._isChangingDecorations = true;
		this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, []);
		this._isChangingDecorations = false;
319
		this.renderDisposable.dispose();
J
Joao Moreno 已提交
320
		this.renderDisposable = Disposable.None;
321
		this._colorPicker = null;
E
Erich Gamma 已提交
322 323
	}

324 325 326 327 328 329 330
	isColorPickerVisible(): boolean {
		if (this._colorPicker) {
			return true;
		}
		return false;
	}

331
	private _withResult(result: HoverPart[], complete: boolean): void {
E
Erich Gamma 已提交
332 333
		this._messages = result;

A
Alex Dima 已提交
334
		if (this._lastRange && this._messages.length > 0) {
E
Erich Gamma 已提交
335
			this._renderMessages(this._lastRange, this._messages);
336
		} else if (complete) {
E
Erich Gamma 已提交
337 338 339 340
			this.hide();
		}
	}

341
	private _renderMessages(renderRange: Range, messages: HoverPart[]): void {
342 343
		this.renderDisposable.dispose();
		this._colorPicker = null;
E
Erich Gamma 已提交
344 345

		// update column from which to show
A
Alex Dima 已提交
346
		let renderColumn = Number.MAX_VALUE;
A
Alex Dima 已提交
347
		let highlightRange: Range | null = messages[0].range ? Range.lift(messages[0].range) : null;
A
Alex Dima 已提交
348 349
		let fragment = document.createDocumentFragment();
		let isEmptyHoverContent = true;
E
Erich Gamma 已提交
350

351
		let containColorPicker = false;
352
		let markdownDisposeables: IDisposable[] = [];
353
		const markerMessages: MarkerHover[] = [];
E
Erich Gamma 已提交
354 355 356 357 358 359
		messages.forEach((msg) => {
			if (!msg.range) {
				return;
			}

			renderColumn = Math.min(renderColumn, msg.range.startColumn);
A
Alex Dima 已提交
360
			highlightRange = highlightRange ? Range.plusRange(highlightRange, msg.range) : Range.lift(msg.range);
E
Erich Gamma 已提交
361

362
			if (msg instanceof ColorHover) {
363 364
				containColorPicker = true;

J
Joao Moreno 已提交
365
				const { red, green, blue, alpha } = msg.color;
R
rebornix 已提交
366
				const rgba = new RGBA(red * 255, green * 255, blue * 255, alpha);
J
Joao Moreno 已提交
367
				const color = new Color(rgba);
368

369 370 371 372
				if (!this._editor.hasModel()) {
					return;
				}

373 374
				const editorModel = this._editor.getModel();
				let range = new Range(msg.range.startLineNumber, msg.range.startColumn, msg.range.endLineNumber, msg.range.endColumn);
375
				let colorInfo = { range: msg.range, color: msg.color };
376

377 378
				// create blank olor picker model and widget first to ensure it's positioned correctly.
				const model = new ColorPickerModel(color, [], 0);
A
Alex Dima 已提交
379
				const widget = new ColorPickerWidget(fragment, model, this._editor.getConfiguration().pixelRatio, this._themeService);
380

381
				getColorPresentations(editorModel, colorInfo, msg.provider, CancellationToken.None).then(colorPresentations => {
A
Alex Dima 已提交
382 383 384 385 386
					model.colorPresentations = colorPresentations || [];
					if (!this._editor.hasModel()) {
						// gone...
						return;
					}
387 388 389 390
					const originalText = this._editor.getModel().getValueInRange(msg.range);
					model.guessColorPresentation(color, originalText);

					const updateEditorModel = () => {
M
Matt Bierner 已提交
391 392
						let textEdits: IIdentifiedSingleEditOperation[];
						let newRange: Range;
393
						if (model.presentation.textEdit) {
M
Matt Bierner 已提交
394
							textEdits = [model.presentation.textEdit as IIdentifiedSingleEditOperation];
395 396 397 398 399 400 401 402 403 404 405 406
							newRange = new Range(
								model.presentation.textEdit.range.startLineNumber,
								model.presentation.textEdit.range.startColumn,
								model.presentation.textEdit.range.endLineNumber,
								model.presentation.textEdit.range.endColumn
							);
							newRange = newRange.setEndPosition(newRange.endLineNumber, newRange.startColumn + model.presentation.textEdit.text.length);
						} else {
							textEdits = [{ identifier: null, range, text: model.presentation.label, forceMoveMarkers: false }];
							newRange = range.setEndPosition(range.endLineNumber, range.startColumn + model.presentation.label.length);
						}

407
						this._editor.pushUndoStop();
A
Alex Dima 已提交
408
						this._editor.executeEdits('colorpicker', textEdits);
409 410

						if (model.presentation.additionalTextEdits) {
M
Matt Bierner 已提交
411
							textEdits = [...model.presentation.additionalTextEdits as IIdentifiedSingleEditOperation[]];
A
Alex Dima 已提交
412
							this._editor.executeEdits('colorpicker', textEdits);
413
							this.hide();
E
Erich Gamma 已提交
414
						}
415
						this._editor.pushUndoStop();
416 417 418 419
						range = newRange;
					};

					const updateColorPresentations = (color: Color) => {
420
						return getColorPresentations(editorModel, {
421 422 423 424 425 426 427
							range: range,
							color: {
								red: color.rgba.r / 255,
								green: color.rgba.g / 255,
								blue: color.rgba.b / 255,
								alpha: color.rgba.a
							}
428
						}, msg.provider, CancellationToken.None).then((colorPresentations) => {
A
Alex Dima 已提交
429
							model.colorPresentations = colorPresentations || [];
430 431 432 433 434
						});
					};

					const colorListener = model.onColorFlushed((color: Color) => {
						updateColorPresentations(color).then(updateEditorModel);
J
Joao Moreno 已提交
435
					});
436
					const colorChangeListener = model.onDidChangeColor(updateColorPresentations);
437

438
					this._colorPicker = widget;
A
Alex Dima 已提交
439
					this.showAt(range.getStartPosition(), range, this._shouldFocus);
440 441
					this.updateContents(fragment);
					this._colorPicker.layout();
442

443
					this.renderDisposable = combinedDisposable(colorListener, colorChangeListener, widget, ...markdownDisposeables);
J
Joao Moreno 已提交
444
				});
445 446
			} else {
				if (msg instanceof MarkerHover) {
447
					markerMessages.push(msg);
448 449 450 451 452
					isEmptyHoverContent = false;
				} else {
					msg.contents
						.filter(contents => !isEmptyMarkdownString(contents))
						.forEach(contents => {
453 454 455
							const markdownHoverElement = $('div.hover-row.markdown-hover');
							const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents'));
							const renderer = new MarkdownRenderer(this._editor, this._modeService, this._openerService);
S
Sandeep Somavarapu 已提交
456 457 458 459
							markdownDisposeables.push(renderer.onDidRenderCodeBlock(() => {
								hoverContentsElement.className = 'hover-contents code-hover-contents';
								this.onContentsChange();
							}));
460 461 462 463
							const renderedContents = renderer.render(contents);
							hoverContentsElement.appendChild(renderedContents.element);
							fragment.appendChild(markdownHoverElement);
							markdownDisposeables.push(renderedContents);
464 465 466
							isEmptyHoverContent = false;
						});
				}
J
Joao Moreno 已提交
467
			}
E
Erich Gamma 已提交
468 469
		});

S
Sandeep Somavarapu 已提交
470 471
		if (markerMessages.length) {
			markerMessages.forEach(msg => fragment.appendChild(this.renderMarkerHover(msg)));
S
Sandeep Somavarapu 已提交
472 473
			const markerHoverForStatusbar = markerMessages.length === 1 ? markerMessages[0] : markerMessages.sort((a, b) => MarkerSeverity.compare(a.marker.severity, b.marker.severity))[0];
			fragment.appendChild(this.renderMarkerStatusbar(markerHoverForStatusbar));
S
Sandeep Somavarapu 已提交
474
		}
475

E
Erich Gamma 已提交
476 477
		// show

478
		if (!containColorPicker && !isEmptyHoverContent) {
A
Alex Dima 已提交
479
			this.showAt(new Position(renderRange.startLineNumber, renderColumn), highlightRange, this._shouldFocus);
480
			this.updateContents(fragment);
M
Michel Kaporin 已提交
481
		}
J
Joao Moreno 已提交
482

E
Erich Gamma 已提交
483
		this._isChangingDecorations = true;
A
Alex Dima 已提交
484
		this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, highlightRange ? [{
E
Erich Gamma 已提交
485
			range: highlightRange,
486
			options: ModesContentHoverWidget._DECORATION_OPTIONS
A
Alex Dima 已提交
487
		}] : []);
E
Erich Gamma 已提交
488 489
		this._isChangingDecorations = false;
	}
490

491
	private renderMarkerHover(markerHover: MarkerHover): HTMLElement {
492 493
		const hoverElement = $('div.hover-row');
		const markerElement = dom.append(hoverElement, $('div.marker.hover-contents'));
494 495
		const { source, message, code, relatedInformation } = markerHover.marker;

S
Sandeep Somavarapu 已提交
496
		this._editor.applyFontInfo(markerElement);
497
		const messageElement = dom.append(markerElement, $('span'));
498
		messageElement.style.whiteSpace = 'pre-wrap';
S
Sandeep Somavarapu 已提交
499
		messageElement.innerText = message;
500 501

		if (source || code) {
502
			const detailsElement = dom.append(markerElement, $('span'));
503 504
			detailsElement.style.opacity = '0.6';
			detailsElement.style.paddingLeft = '6px';
S
Sandeep Somavarapu 已提交
505
			detailsElement.innerText = source && code ? `${source}(${code})` : source ? source : `(${code})`;
506 507 508 509
		}

		if (isNonEmptyArray(relatedInformation)) {
			for (const { message, resource, startLineNumber, startColumn } of relatedInformation) {
510 511 512
				const relatedInfoContainer = dom.append(markerElement, $('div'));
				relatedInfoContainer.style.marginTop = '8px';
				const a = dom.append(relatedInfoContainer, $('a'));
S
Sandeep Somavarapu 已提交
513
				a.innerText = `${basename(resource)}(${startLineNumber}, ${startColumn}): `;
S
Sandeep Somavarapu 已提交
514 515 516 517
				a.style.cursor = 'pointer';
				a.onclick = e => {
					e.stopPropagation();
					e.preventDefault();
S
Sandeep Somavarapu 已提交
518 519 520
					if (this._openerService) {
						this._openerService.open(resource.with({ fragment: `${startLineNumber},${startColumn}` })).catch(onUnexpectedError);
					}
S
Sandeep Somavarapu 已提交
521
				};
522
				const messageElement = dom.append<HTMLAnchorElement>(relatedInfoContainer, $('span'));
S
Sandeep Somavarapu 已提交
523 524
				messageElement.innerText = message;
				this._editor.applyFontInfo(messageElement);
525 526
			}
		}
527

S
Sandeep Somavarapu 已提交
528 529 530 531 532
		return hoverElement;
	}

	private renderMarkerStatusbar(markerHover: MarkerHover): HTMLElement {
		const hoverElement = $('div.hover-row.status-bar');
533
		const disposables = new DisposableStore();
534
		const actionsElement = dom.append(hoverElement, $('div.actions'));
535
		disposables.add(this.renderAction(actionsElement, {
536 537 538 539
			label: nls.localize('quick fixes', "Quick Fix..."),
			commandId: QuickFixAction.Id,
			run: async (target) => {
				const codeActionsPromise = this.getCodeActions(markerHover.marker);
540
				disposables.add(toDisposable(() => codeActionsPromise.cancel()));
541
				const actions = await codeActionsPromise;
542
				disposables.add(actions);
543 544 545
				const elementPosition = dom.getDomNodePagePosition(target);
				this._contextMenuService.showContextMenu({
					getAnchor: () => ({ x: elementPosition.left + 6, y: elementPosition.top + elementPosition.height + 6 }),
546
					getActions: () => actions.actions
547 548 549
				});
			}
		}));
S
Sandeep Somavarapu 已提交
550
		if (markerHover.marker.severity === MarkerSeverity.Error || markerHover.marker.severity === MarkerSeverity.Warning || markerHover.marker.severity === MarkerSeverity.Info) {
551
			disposables.add(this.renderAction(actionsElement, {
S
Sandeep Somavarapu 已提交
552 553 554 555 556 557 558 559 560
				label: nls.localize('peek problem', "Peek Problem"),
				commandId: NextMarkerAction.ID,
				run: () => {
					this.hide();
					MarkerController.get(this._editor).show(markerHover.marker);
					this._editor.focus();
				}
			}));
		}
561
		this.renderDisposable = disposables;
562 563 564
		return hoverElement;
	}

565
	private getCodeActions(marker: IMarker): CancelablePromise<ActionSet> {
566 567
		return createCancelablePromise(async cancellationToken => {
			const codeActions = await getCodeActions(this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken);
M
Matt Bierner 已提交
568
			if (codeActions.actions.length) {
569 570 571 572 573 574 575 576 577 578 579
				const actions: Action[] = [];
				for (const codeAction of codeActions.actions) {
					actions.push(new Action(
						codeAction.command ? codeAction.command.id : codeAction.title,
						codeAction.title,
						undefined,
						true,
						() => applyCodeAction(codeAction, this._bulkEditService, this._commandService)));
				}
				return {
					actions: actions,
580
					dispose: () => codeActions.dispose()
581
				};
582
			}
583 584 585 586 587 588 589

			return {
				actions: [
					new Action('', nls.localize('editor.action.quickFix.noneMessage', "No code actions available"))
				],
				dispose() { }
			};
590 591 592 593
		});
	}

	private renderAction(parent: HTMLElement, actionOptions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }): IDisposable {
594 595 596 597 598 599 600
		const actionContainer = dom.append(parent, $('div.action-container'));
		const action = dom.append(actionContainer, $('a.action'));
		if (actionOptions.iconClass) {
			dom.append(action, $(`span.icon.${actionOptions.iconClass}`));
		}
		const label = dom.append(action, $('span'));
		label.textContent = actionOptions.label;
601 602
		const keybinding = this._keybindingService.lookupKeybinding(actionOptions.commandId);
		if (keybinding) {
S
Sandeep Somavarapu 已提交
603
			label.title = `${actionOptions.label} (${keybinding.getLabel()})`;
604
		}
605
		return dom.addDisposableListener(actionContainer, dom.EventType.CLICK, e => {
606 607
			e.stopPropagation();
			e.preventDefault();
608
			actionOptions.run(actionContainer);
609 610 611
		});
	}

612
	private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
613 614 615
		className: 'hoverHighlight'
	});
}
616

617
function hoverContentsEquals(first: HoverPart[], second: HoverPart[]): boolean {
618 619 620 621
	if ((!first && second) || (first && !second) || first.length !== second.length) {
		return false;
	}
	for (let i = 0; i < first.length; i++) {
622 623
		const firstElement = first[i];
		const secondElement = second[i];
624 625 626 627
		if (firstElement instanceof MarkerHover && secondElement instanceof MarkerHover) {
			return IMarkerData.makeKey(firstElement.marker) === IMarkerData.makeKey(secondElement.marker);
		}
		if (firstElement instanceof ColorHover || secondElement instanceof ColorHover) {
628 629
			return false;
		}
630
		if (firstElement instanceof MarkerHover || secondElement instanceof MarkerHover) {
631 632 633
			return false;
		}
		if (!markedStringsEquals(firstElement.contents, secondElement.contents)) {
634 635 636 637 638
			return false;
		}
	}
	return true;
}