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

A
Alex Dima 已提交
7
import * as nls from 'vs/nls';
M
Michel Kaporin 已提交
8
import * as dom from 'vs/base/browser/dom';
9
import { TPromise } from 'vs/base/common/winjs.base';
10
import { IRange, Range } from 'vs/editor/common/core/range';
11
import { Position } from 'vs/editor/common/core/position';
12
import { HoverProviderRegistry, Hover, IColor, DocumentColorProvider } from 'vs/editor/common/modes';
13
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
14
import { getHover } from 'vs/editor/contrib/hover/getHover';
15
import { HoverOperation, IHoverComputer, HoverStartMode } from './hoverOperation';
16
import { ContentHoverWidget } from './hoverWidgets';
17
import { IMarkdownString, MarkdownString, isEmptyMarkdownString, markedStringsEquals } from 'vs/base/common/htmlContent';
18
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
A
Alex Dima 已提交
19
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
20 21 22
import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel';
import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/colorPickerWidget';
import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector';
J
Joao Moreno 已提交
23
import { Color, RGBA } from 'vs/base/common/color';
24
import { IDisposable, empty as EmptyDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
25
import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color';
A
Alex Dima 已提交
26
import { IThemeService } from 'vs/platform/theme/common/themeService';
M
Michel Kaporin 已提交
27
const $ = dom.$;
E
Erich Gamma 已提交
28

29 30 31 32
class ColorHover {

	constructor(
		public readonly range: IRange,
J
Joao Moreno 已提交
33
		public readonly color: IColor,
34
		public readonly provider: DocumentColorProvider
35 36 37 38
	) { }
}

type HoverPart = Hover | ColorHover;
E
Erich Gamma 已提交
39

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

A
Alex Dima 已提交
42
	private _editor: ICodeEditor;
43
	private _result: HoverPart[];
44
	private _range: Range;
E
Erich Gamma 已提交
45

A
Alex Dima 已提交
46
	constructor(editor: ICodeEditor) {
E
Erich Gamma 已提交
47 48 49 50
		this._editor = editor;
		this._range = null;
	}

J
Joao Moreno 已提交
51
	setRange(range: Range): void {
E
Erich Gamma 已提交
52 53 54 55
		this._range = range;
		this._result = [];
	}

J
Joao Moreno 已提交
56
	clearResult(): void {
E
Erich Gamma 已提交
57 58 59
		this._result = [];
	}

60
	computeAsync(): TPromise<HoverPart[]> {
J
Joao Moreno 已提交
61
		const model = this._editor.getModel();
E
Erich Gamma 已提交
62

A
Alex Dima 已提交
63
		if (!HoverProviderRegistry.has(model)) {
E
Erich Gamma 已提交
64 65 66
			return TPromise.as(null);
		}

67
		return getHover(model, new Position(
68 69
			this._range.startLineNumber,
			this._range.startColumn
70
		));
E
Erich Gamma 已提交
71 72
	}

73
	computeSync(): HoverPart[] {
J
Joao Moreno 已提交
74
		const lineNumber = this._range.startLineNumber;
E
Erich Gamma 已提交
75 76 77

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

81
		const colorDetector = ColorDetector.get(this._editor);
J
Joao Moreno 已提交
82 83
		const maxColumn = this._editor.getModel().getLineMaxColumn(lineNumber);
		const lineDecorations = this._editor.getLineDecorations(lineNumber);
84
		let didFindColor = false;
J
Joao Moreno 已提交
85 86 87 88 89

		const result = lineDecorations.map(d => {
			const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1;
			const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn;

90
			if (startColumn > this._range.startColumn || this._range.endColumn > endColumn) {
J
Joao Moreno 已提交
91 92 93 94
				return null;
			}

			const range = new Range(this._range.startLineNumber, startColumn, this._range.startLineNumber, endColumn);
95
			const colorData = colorDetector.getColorData(d.range.getStartPosition());
J
Joao Moreno 已提交
96

97
			if (!didFindColor && colorData) {
98
				didFindColor = true;
99

100 101
				const { color, range } = colorData.colorInfo;
				return new ColorHover(range, color, colorData.provider);
102
			} else {
103
				if (isEmptyMarkdownString(d.options.hoverMessage)) {
104
					return null;
E
Erich Gamma 已提交
105
				}
J
Joao Moreno 已提交
106

107
				let contents: IMarkdownString[];
M
Michel Kaporin 已提交
108

109 110 111 112 113 114 115
				if (d.options.hoverMessage) {
					if (Array.isArray(d.options.hoverMessage)) {
						contents = [...d.options.hoverMessage];
					} else {
						contents = [d.options.hoverMessage];
					}
				}
J
Joao Moreno 已提交
116

117 118
				return { contents, range };
			}
E
Erich Gamma 已提交
119
		});
J
Joao Moreno 已提交
120 121

		return result.filter(d => !!d);
E
Erich Gamma 已提交
122 123
	}

124
	onResult(result: HoverPart[], isFromSynchronousComputation: boolean): void {
E
Erich Gamma 已提交
125 126
		// Always put synchronous messages before asynchronous ones
		if (isFromSynchronousComputation) {
127
			this._result = result.concat(this._result.sort((a, b) => {
128
				if (a instanceof ColorHover) { // sort picker messages at to the top
129
					return -1;
130
				} else if (b instanceof ColorHover) {
131 132 133 134
					return 1;
				}
				return 0;
			}));
E
Erich Gamma 已提交
135 136 137 138 139
		} else {
			this._result = this._result.concat(result);
		}
	}

140
	getResult(): HoverPart[] {
E
Erich Gamma 已提交
141 142 143
		return this._result.slice(0);
	}

144
	getResultWithLoadingMessage(): HoverPart[] {
E
Erich Gamma 已提交
145 146 147
		return this._result.slice(0).concat([this._getLoadingMessage()]);
	}

148
	private _getLoadingMessage(): HoverPart {
E
Erich Gamma 已提交
149 150
		return {
			range: this._range,
151
			contents: [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))]
E
Erich Gamma 已提交
152 153 154 155
		};
	}
}

A
Alex Dima 已提交
156
export class ModesContentHoverWidget extends ContentHoverWidget {
E
Erich Gamma 已提交
157

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

160
	private _messages: HoverPart[];
161
	private _lastRange: Range;
E
Erich Gamma 已提交
162
	private _computer: ModesContentComputer;
163
	private _hoverOperation: HoverOperation<HoverPart[]>;
164
	private _highlightDecorations: string[];
E
Erich Gamma 已提交
165
	private _isChangingDecorations: boolean;
166
	private _markdownRenderer: MarkdownRenderer;
I
isidor 已提交
167
	private _shouldFocus: boolean;
168
	private _colorPicker: ColorPickerWidget;
169 170

	private renderDisposable: IDisposable = EmptyDisposable;
J
Joao Moreno 已提交
171
	private toDispose: IDisposable[] = [];
E
Erich Gamma 已提交
172

A
Alex Dima 已提交
173 174
	constructor(
		editor: ICodeEditor,
M
Matt Bierner 已提交
175
		markdownRenderer: MarkdownRenderer,
A
Alex Dima 已提交
176 177
		private readonly _themeService: IThemeService
	) {
E
Erich Gamma 已提交
178 179 180 181 182
		super(ModesContentHoverWidget.ID, editor);

		this._computer = new ModesContentComputer(this._editor);
		this._highlightDecorations = [];
		this._isChangingDecorations = false;
J
Joao Moreno 已提交
183

M
Matt Bierner 已提交
184 185
		this._markdownRenderer = markdownRenderer;
		markdownRenderer.onDidRenderCodeBlock(this.onContentsChange, this, this.toDispose);
E
Erich Gamma 已提交
186

A
Alex Dima 已提交
187
		this._hoverOperation = new HoverOperation(
E
Erich Gamma 已提交
188
			this._computer,
189
			result => this._withResult(result, true),
E
Erich Gamma 已提交
190
			null,
191
			result => this._withResult(result, false)
E
Erich Gamma 已提交
192
		);
M
Michel Kaporin 已提交
193 194 195 196 197 198 199 200 201

		this.toDispose.push(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.FOCUS, () => {
			if (this._colorPicker) {
				dom.addClass(this.getDomNode(), 'colorpicker-hover');
			}
		}));
		this.toDispose.push(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.BLUR, () => {
			dom.removeClass(this.getDomNode(), 'colorpicker-hover');
		}));
E
Erich Gamma 已提交
202 203
	}

J
Joao Moreno 已提交
204
	dispose(): void {
205 206
		this.renderDisposable.dispose();
		this.renderDisposable = EmptyDisposable;
207
		this._hoverOperation.cancel();
208
		this.toDispose = dispose(this.toDispose);
209 210 211
		super.dispose();
	}

J
Joao Moreno 已提交
212
	onModelDecorationsChanged(): void {
E
Erich Gamma 已提交
213 214 215
		if (this._isChangingDecorations) {
			return;
		}
J
Joao Moreno 已提交
216
		if (this.isVisible) {
E
Erich Gamma 已提交
217 218 219 220
			// The decorations have changed and the hover is visible,
			// we need to recompute the displayed text
			this._hoverOperation.cancel();
			this._computer.clearResult();
221 222

			if (!this._colorPicker) { // TODO@Michel ensure that displayed text for other decorations is computed even if color picker is in place
223
				this._hoverOperation.start(HoverStartMode.Delayed);
224
			}
E
Erich Gamma 已提交
225 226 227
		}
	}

228
	startShowingAt(range: Range, mode: HoverStartMode, focus: boolean): void {
J
Joao Moreno 已提交
229 230 231
		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 已提交
232 233 234 235
		}

		this._hoverOperation.cancel();

J
Joao Moreno 已提交
236
		if (this.isVisible) {
E
Erich Gamma 已提交
237 238 239 240 241 242
			// 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
			if (this._showAtPosition.lineNumber !== range.startLineNumber) {
				this.hide();
			} else {
A
Alex Dima 已提交
243 244 245 246
				let filteredMessages: HoverPart[] = [];
				for (let i = 0, len = this._messages.length; i < len; i++) {
					const msg = this._messages[i];
					const rng = msg.range;
E
Erich Gamma 已提交
247 248 249 250 251
					if (rng.startColumn <= range.startColumn && rng.endColumn >= range.endColumn) {
						filteredMessages.push(msg);
					}
				}
				if (filteredMessages.length > 0) {
252 253 254
					if (hoverContentsEquals(filteredMessages, this._messages)) {
						return;
					}
E
Erich Gamma 已提交
255 256 257 258 259 260 261 262 263
					this._renderMessages(range, filteredMessages);
				} else {
					this.hide();
				}
			}
		}

		this._lastRange = range;
		this._computer.setRange(range);
I
isidor 已提交
264
		this._shouldFocus = focus;
265
		this._hoverOperation.start(mode);
E
Erich Gamma 已提交
266 267
	}

J
Joao Moreno 已提交
268
	hide(): void {
E
Erich Gamma 已提交
269 270 271 272 273 274
		this._lastRange = null;
		this._hoverOperation.cancel();
		super.hide();
		this._isChangingDecorations = true;
		this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, []);
		this._isChangingDecorations = false;
275 276
		this.renderDisposable.dispose();
		this.renderDisposable = EmptyDisposable;
277
		this._colorPicker = null;
E
Erich Gamma 已提交
278 279
	}

280 281 282 283 284 285 286
	isColorPickerVisible(): boolean {
		if (this._colorPicker) {
			return true;
		}
		return false;
	}

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

A
Alex Dima 已提交
290
		if (this._lastRange && this._messages.length > 0) {
E
Erich Gamma 已提交
291
			this._renderMessages(this._lastRange, this._messages);
292
		} else if (complete) {
E
Erich Gamma 已提交
293 294 295 296
			this.hide();
		}
	}

297
	private _renderMessages(renderRange: Range, messages: HoverPart[]): void {
298 299
		this.renderDisposable.dispose();
		this._colorPicker = null;
E
Erich Gamma 已提交
300 301

		// update column from which to show
A
Alex Dima 已提交
302 303 304 305
		let renderColumn = Number.MAX_VALUE;
		let highlightRange = messages[0].range;
		let fragment = document.createDocumentFragment();
		let isEmptyHoverContent = true;
E
Erich Gamma 已提交
306

307
		let containColorPicker = false;
308
		let markdownDisposeable: IDisposable;
E
Erich Gamma 已提交
309 310 311 312 313 314 315 316
		messages.forEach((msg) => {
			if (!msg.range) {
				return;
			}

			renderColumn = Math.min(renderColumn, msg.range.startColumn);
			highlightRange = Range.plusRange(highlightRange, msg.range);

317
			if (!(msg instanceof ColorHover)) {
J
Joao Moreno 已提交
318
				msg.contents
319
					.filter(contents => !isEmptyMarkdownString(contents))
J
Joao Moreno 已提交
320
					.forEach(contents => {
321
						const renderedContents = this._markdownRenderer.render(contents);
322 323
						markdownDisposeable = renderedContents;
						fragment.appendChild($('div.hover-row', null, renderedContents.element));
324
						isEmptyHoverContent = false;
J
Joao Moreno 已提交
325
					});
J
Joao Moreno 已提交
326
			} else {
327 328
				containColorPicker = true;

J
Joao Moreno 已提交
329
				const { red, green, blue, alpha } = msg.color;
R
rebornix 已提交
330
				const rgba = new RGBA(red * 255, green * 255, blue * 255, alpha);
J
Joao Moreno 已提交
331
				const color = new Color(rgba);
332

333 334
				const editorModel = this._editor.getModel();
				let range = new Range(msg.range.startLineNumber, msg.range.startColumn, msg.range.endLineNumber, msg.range.endColumn);
335
				let colorInfo = { range: msg.range, color: msg.color };
336

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

341
				getColorPresentations(editorModel, colorInfo, msg.provider).then(colorPresentations => {
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
					model.colorPresentations = colorPresentations;
					const originalText = this._editor.getModel().getValueInRange(msg.range);
					model.guessColorPresentation(color, originalText);

					const updateEditorModel = () => {
						let textEdits;
						let newRange;
						if (model.presentation.textEdit) {
							textEdits = [model.presentation.textEdit];
							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);
						}

						editorModel.pushEditOperations([], textEdits, () => []);

						if (model.presentation.additionalTextEdits) {
							textEdits = [...model.presentation.additionalTextEdits];
							editorModel.pushEditOperations([], textEdits, () => []);
							this.hide();
E
Erich Gamma 已提交
369
						}
370
						this._editor.pushUndoStop();
371 372 373 374
						range = newRange;
					};

					const updateColorPresentations = (color: Color) => {
375
						return getColorPresentations(editorModel, {
376 377 378 379 380 381 382 383 384 385 386 387 388 389
							range: range,
							color: {
								red: color.rgba.r / 255,
								green: color.rgba.g / 255,
								blue: color.rgba.b / 255,
								alpha: color.rgba.a
							}
						}, msg.provider).then((colorPresentations) => {
							model.colorPresentations = colorPresentations;
						});
					};

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

393 394 395 396
					this._colorPicker = widget;
					this.showAt(new Position(renderRange.startLineNumber, renderColumn), this._shouldFocus);
					this.updateContents(fragment);
					this._colorPicker.layout();
397

398
					this.renderDisposable = combinedDisposable([colorListener, colorChangeListener, widget, markdownDisposeable]);
J
Joao Moreno 已提交
399
				});
J
Joao Moreno 已提交
400
			}
E
Erich Gamma 已提交
401 402 403 404
		});

		// show

405
		if (!containColorPicker && !isEmptyHoverContent) {
406 407
			this.showAt(new Position(renderRange.startLineNumber, renderColumn), this._shouldFocus);
			this.updateContents(fragment);
M
Michel Kaporin 已提交
408
		}
J
Joao Moreno 已提交
409

E
Erich Gamma 已提交
410 411 412
		this._isChangingDecorations = true;
		this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, [{
			range: highlightRange,
413
			options: ModesContentHoverWidget._DECORATION_OPTIONS
E
Erich Gamma 已提交
414 415 416
		}]);
		this._isChangingDecorations = false;
	}
417

418
	private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
419 420 421
		className: 'hoverHighlight'
	});
}
422

423
function hoverContentsEquals(first: HoverPart[], second: HoverPart[]): boolean {
424 425 426 427
	if ((!first && second) || (first && !second) || first.length !== second.length) {
		return false;
	}
	for (let i = 0; i < first.length; i++) {
428 429 430 431 432 433 434 435 436
		const firstElement = first[i];
		const secondElement = second[i];
		if (firstElement instanceof ColorHover) {
			return false;
		}
		if (secondElement instanceof ColorHover) {
			return false;
		}
		if (!markedStringsEquals(firstElement.contents, secondElement.contents)) {
437 438 439 440 441
			return false;
		}
	}
	return true;
}