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

'use strict';

8
import 'vs/css!./media/suggest';
J
Johannes Rieken 已提交
9
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
10
import { createMatches } from 'vs/base/common/filters';
11
import * as strings from 'vs/base/common/strings';
M
Matt Bierner 已提交
12
import { Event, Emitter, chain } from 'vs/base/common/event';
13
import { onUnexpectedError } from 'vs/base/common/errors';
J
Joao Moreno 已提交
14
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
15
import { addClass, append, $, hide, removeClass, show, toggleClass, getDomNodePagePosition, hasClass } from 'vs/base/browser/dom';
J
Joao Moreno 已提交
16
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
J
Joao Moreno 已提交
17
import { IVirtualDelegate, IListEvent, IRenderer } from 'vs/base/browser/ui/list/list';
J
Joao Moreno 已提交
18 19
import { List } from 'vs/base/browser/ui/list/listWidget';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
J
Johannes Rieken 已提交
20 21
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
22
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
J
Joao Moreno 已提交
23
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
24 25
import { Context as SuggestContext } from './suggest';
import { ICompletionItem, CompletionModel } from './completionModel';
J
Johannes Rieken 已提交
26
import { alert } from 'vs/base/browser/ui/aria/aria';
J
Joao Moreno 已提交
27
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
28 29
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { IThemeService, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
30
import { registerColor, editorWidgetBackground, listFocusBackground, activeContrastBorder, listHighlightForeground, editorForeground, editorWidgetBorder, focusBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry';
31
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
32
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
33 34
import { IModeService } from 'vs/editor/common/services/modeService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
35 36
import { TimeoutTimer, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
E
Erich Gamma 已提交
37

38
const expandSuggestionDocsByDefault = false;
39
const maxSuggestionsToShow = 12;
J
Joao Moreno 已提交
40

E
Erich Gamma 已提交
41 42 43 44
interface ISuggestionTemplateData {
	root: HTMLElement;
	icon: HTMLElement;
	colorspan: HTMLElement;
A
Alex Dima 已提交
45
	highlightedLabel: HighlightedLabel;
46
	typeLabel: HTMLElement;
47
	readMore: HTMLElement;
48
	disposables: IDisposable[];
E
Erich Gamma 已提交
49 50
}

M
Martin Aeschlimann 已提交
51 52 53
/**
 * Suggest widget colors
 */
54
export const editorSuggestWidgetBackground = registerColor('editorSuggestWidget.background', { dark: editorWidgetBackground, light: editorWidgetBackground, hc: editorWidgetBackground }, nls.localize('editorSuggestWidgetBackground', 'Background color of the suggest widget.'));
55
export const editorSuggestWidgetBorder = registerColor('editorSuggestWidget.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hc: editorWidgetBorder }, nls.localize('editorSuggestWidgetBorder', 'Border color of the suggest widget.'));
56 57 58
export const editorSuggestWidgetForeground = registerColor('editorSuggestWidget.foreground', { dark: editorForeground, light: editorForeground, hc: editorForeground }, nls.localize('editorSuggestWidgetForeground', 'Foreground color of the suggest widget.'));
export const editorSuggestWidgetSelectedBackground = registerColor('editorSuggestWidget.selectedBackground', { dark: listFocusBackground, light: listFocusBackground, hc: listFocusBackground }, nls.localize('editorSuggestWidgetSelectedBackground', 'Background color of the selected entry in the suggest widget.'));
export const editorSuggestWidgetHighlightForeground = registerColor('editorSuggestWidget.highlightForeground', { dark: listHighlightForeground, light: listHighlightForeground, hc: listHighlightForeground }, nls.localize('editorSuggestWidgetHighlightForeground', 'Color of the match highlights in the suggest widget.'));
59

M
Martin Aeschlimann 已提交
60

61
const colorRegExp = /^(#([\da-f]{3}){1,2}|(rgb|hsl)a\(\s*(\d{1,3}%?\s*,\s*){3}(1|0?\.\d+)\)|(rgb|hsl)\(\s*\d{1,3}%?(\s*,\s*\d{1,3}%?){2}\s*\))$/i;
62 63 64
function matchesColor(text: string) {
	return text && text.match(colorRegExp) ? text : null;
}
65

66
function canExpandCompletionItem(item: ICompletionItem) {
R
Ramya Achutha Rao 已提交
67 68 69
	if (!item) {
		return false;
	}
70 71 72 73
	const suggestion = item.suggestion;
	if (suggestion.documentation) {
		return true;
	}
74
	return (suggestion.detail && suggestion.detail !== suggestion.label);
75 76
}

77
class Renderer implements IRenderer<ICompletionItem, ISuggestionTemplateData> {
E
Erich Gamma 已提交
78

J
Joao Moreno 已提交
79 80
	constructor(
		private widget: SuggestWidget,
81
		private editor: ICodeEditor,
82
		private triggerKeybindingLabel: string
J
Joao Moreno 已提交
83
	) {
84

J
Joao Moreno 已提交
85
	}
J
Joao Moreno 已提交
86

J
Joao Moreno 已提交
87 88 89
	get templateId(): string {
		return 'suggestion';
	}
E
Erich Gamma 已提交
90

J
Joao Moreno 已提交
91
	renderTemplate(container: HTMLElement): ISuggestionTemplateData {
J
Johannes Rieken 已提交
92
		const data = <ISuggestionTemplateData>Object.create(null);
93
		data.disposables = [];
E
Erich Gamma 已提交
94
		data.root = container;
J
Joao Moreno 已提交
95

J
Joao Moreno 已提交
96 97
		data.icon = append(container, $('.icon'));
		data.colorspan = append(data.icon, $('span.colorspan'));
E
Erich Gamma 已提交
98

J
Joao Moreno 已提交
99
		const text = append(container, $('.contents'));
J
Joao Moreno 已提交
100
		const main = append(text, $('.main'));
A
Alex Dima 已提交
101
		data.highlightedLabel = new HighlightedLabel(main);
102
		data.disposables.push(data.highlightedLabel);
103 104
		data.typeLabel = append(main, $('span.type-label'));

105 106
		data.readMore = append(main, $('span.readMore'));
		data.readMore.title = nls.localize('readMore', "Read More...{0}", this.triggerKeybindingLabel);
107

108
		const configureFont = () => {
J
Joao Moreno 已提交
109 110 111 112
			const configuration = this.editor.getConfiguration();
			const fontFamily = configuration.fontInfo.fontFamily;
			const fontSize = configuration.contribInfo.suggestFontSize || configuration.fontInfo.fontSize;
			const lineHeight = configuration.contribInfo.suggestLineHeight || configuration.fontInfo.lineHeight;
113
			const fontWeight = configuration.fontInfo.fontWeight;
J
Johannes Rieken 已提交
114 115
			const fontSizePx = `${fontSize}px`;
			const lineHeightPx = `${lineHeight}px`;
J
Joao Moreno 已提交
116 117

			data.root.style.fontSize = fontSizePx;
118
			data.root.style.fontWeight = fontWeight;
J
Joao Moreno 已提交
119 120 121 122
			main.style.fontFamily = fontFamily;
			main.style.lineHeight = lineHeightPx;
			data.icon.style.height = lineHeightPx;
			data.icon.style.width = lineHeightPx;
123 124
			data.readMore.style.height = lineHeightPx;
			data.readMore.style.width = lineHeightPx;
125 126 127 128
		};

		configureFont();

J
Joao Moreno 已提交
129
		chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
J
Joao Moreno 已提交
130
			.filter(e => e.fontInfo || e.contribInfo)
J
Joao Moreno 已提交
131
			.on(configureFont, null, data.disposables);
132

E
Erich Gamma 已提交
133 134 135
		return data;
	}

136
	renderElement(element: ICompletionItem, index: number, templateData: ISuggestionTemplateData): void {
J
Johannes Rieken 已提交
137
		const data = <ISuggestionTemplateData>templateData;
138
		const suggestion = (<ICompletionItem>element).suggestion;
E
Erich Gamma 已提交
139

140 141 142 143
		data.icon.className = 'icon ' + suggestion.type;
		data.colorspan.style.backgroundColor = '';

		if (suggestion.type === 'color') {
144
			let color = matchesColor(suggestion.label) || typeof suggestion.documentation === 'string' && matchesColor(suggestion.documentation);
145 146 147 148
			if (color) {
				data.icon.className = 'icon customcolor';
				data.colorspan.style.backgroundColor = color;
			}
E
Erich Gamma 已提交
149 150
		}

J
Johannes Rieken 已提交
151
		data.highlightedLabel.set(suggestion.label, createMatches(element.matches), '', true);
J
Johannes Rieken 已提交
152
		// data.highlightedLabel.set(`${suggestion.label} <${element.score}=score(${element.word}, ${suggestion.filterText || suggestion.label})>`, createMatches(element.matches));
153
		data.typeLabel.textContent = (suggestion.detail || '').replace(/\n.*$/m, '');
J
Joao Moreno 已提交
154

155
		if (canExpandCompletionItem(element)) {
156 157
			show(data.readMore);
			data.readMore.onmousedown = e => {
158 159 160
				e.stopPropagation();
				e.preventDefault();
			};
161
			data.readMore.onclick = e => {
162 163 164 165 166
				e.stopPropagation();
				e.preventDefault();
				this.widget.toggleDetails();
			};
		} else {
167 168 169
			hide(data.readMore);
			data.readMore.onmousedown = null;
			data.readMore.onclick = null;
170
		}
J
Joao Moreno 已提交
171
	}
J
Joao Moreno 已提交
172

J
Joao Moreno 已提交
173 174
	disposeElement(): void {
		// noop
E
Erich Gamma 已提交
175 176
	}

J
Joao Moreno 已提交
177
	disposeTemplate(templateData: ISuggestionTemplateData): void {
178
		templateData.disposables = dispose(templateData.disposables);
J
Joao Moreno 已提交
179 180
	}
}
E
Erich Gamma 已提交
181

A
Alex Dima 已提交
182
const enum State {
J
Joao Moreno 已提交
183 184 185
	Hidden,
	Loading,
	Empty,
J
Joao Moreno 已提交
186
	Open,
J
Joao Moreno 已提交
187 188 189 190 191 192 193
	Frozen,
	Details
}

class SuggestionDetails {

	private el: HTMLElement;
194
	private close: HTMLElement;
195
	private scrollbar: DomScrollableElement;
196
	private body: HTMLElement;
197
	private header: HTMLElement;
J
Joao Moreno 已提交
198 199
	private type: HTMLElement;
	private docs: HTMLElement;
200 201
	private ariaLabel: string;
	private disposables: IDisposable[];
202
	private renderDisposeable: IDisposable;
203
	private borderWidth: number = 1;
204 205 206 207

	constructor(
		container: HTMLElement,
		private widget: SuggestWidget,
208
		private editor: ICodeEditor,
209
		private markdownRenderer: MarkdownRenderer,
210
		private triggerKeybindingLabel: string
211 212
	) {
		this.disposables = [];
J
Joao Moreno 已提交
213 214

		this.el = append(container, $('.details'));
215 216
		this.disposables.push(toDisposable(() => container.removeChild(this.el)));

217
		this.body = $('.body');
218

219
		this.scrollbar = new DomScrollableElement(this.body, {});
220
		append(this.el, this.scrollbar.getDomNode());
221 222
		this.disposables.push(this.scrollbar);

223 224
		this.header = append(this.body, $('.header'));
		this.close = append(this.header, $('span.close'));
225
		this.close.title = nls.localize('readLess', "Read less...{0}", this.triggerKeybindingLabel);
226
		this.type = append(this.header, $('p.type'));
227

228
		this.docs = append(this.body, $('p.docs'));
229
		this.ariaLabel = null;
230 231 232

		this.configureFont();

J
Joao Moreno 已提交
233 234 235
		chain<IConfigurationChangedEvent>(this.editor.onDidChangeConfiguration.bind(this.editor))
			.filter(e => e.fontInfo)
			.on(this.configureFont, this, this.disposables);
236 237

		markdownRenderer.onDidRenderCodeBlock(() => this.scrollbar.scanDomNode(), this, this.disposables);
J
Joao Moreno 已提交
238 239
	}

J
Joao Moreno 已提交
240 241 242
	get element() {
		return this.el;
	}
J
Joao Moreno 已提交
243

244
	render(item: ICompletionItem): void {
245 246
		this.renderDisposeable = dispose(this.renderDisposeable);

247
		if (!item || !canExpandCompletionItem(item)) {
J
Joao Moreno 已提交
248 249
			this.type.textContent = '';
			this.docs.textContent = '';
250
			addClass(this.el, 'no-docs');
251
			this.ariaLabel = null;
J
Joao Moreno 已提交
252 253
			return;
		}
254
		removeClass(this.el, 'no-docs');
255
		if (typeof item.suggestion.documentation === 'string') {
256
			removeClass(this.docs, 'markdown-docs');
257 258
			this.docs.textContent = item.suggestion.documentation;
		} else {
259
			addClass(this.docs, 'markdown-docs');
M
Matt Bierner 已提交
260
			this.docs.innerHTML = '';
261 262 263
			const renderedContents = this.markdownRenderer.render(item.suggestion.documentation);
			this.renderDisposeable = renderedContents;
			this.docs.appendChild(renderedContents.element);
264
		}
265

R
Ramya Achutha Rao 已提交
266 267 268 269 270 271 272 273
		if (item.suggestion.detail) {
			this.type.innerText = item.suggestion.detail;
			show(this.type);
		} else {
			this.type.innerText = '';
			hide(this.type);
		}

274
		this.el.style.height = this.header.offsetHeight + this.docs.offsetHeight + (this.borderWidth * 2) + 'px';
275

276
		this.close.onmousedown = e => {
277 278 279
			e.preventDefault();
			e.stopPropagation();
		};
280
		this.close.onclick = e => {
281 282 283 284
			e.preventDefault();
			e.stopPropagation();
			this.widget.toggleDetails();
		};
285

J
Joao Moreno 已提交
286
		this.body.scrollTop = 0;
287
		this.scrollbar.scanDomNode();
288

289 290 291 292
		this.ariaLabel = strings.format(
			'{0}{1}',
			item.suggestion.detail || '',
			item.suggestion.documentation ? (typeof item.suggestion.documentation === 'string' ? item.suggestion.documentation : item.suggestion.documentation.value) : '');
293 294 295 296
	}

	getAriaLabel(): string {
		return this.ariaLabel;
J
Joao Moreno 已提交
297 298
	}

299 300 301 302 303 304 305 306
	scrollDown(much = 8): void {
		this.body.scrollTop += much;
	}

	scrollUp(much = 8): void {
		this.body.scrollTop -= much;
	}

307 308 309 310 311 312 313 314
	scrollTop(): void {
		this.body.scrollTop = 0;
	}

	scrollBottom(): void {
		this.body.scrollTop = this.body.scrollHeight;
	}

315 316 317 318 319 320 321 322
	pageDown(): void {
		this.scrollDown(80);
	}

	pageUp(): void {
		this.scrollUp(80);
	}

323 324 325 326
	setBorderWidth(width: number): void {
		this.borderWidth = width;
	}

327
	private configureFont() {
J
Joao Moreno 已提交
328 329 330
		const configuration = this.editor.getConfiguration();
		const fontFamily = configuration.fontInfo.fontFamily;
		const fontSize = configuration.contribInfo.suggestFontSize || configuration.fontInfo.fontSize;
331
		const lineHeight = configuration.contribInfo.suggestLineHeight || configuration.fontInfo.lineHeight;
332
		const fontWeight = configuration.fontInfo.fontWeight;
J
Johannes Rieken 已提交
333
		const fontSizePx = `${fontSize}px`;
334
		const lineHeightPx = `${lineHeight}px`;
J
Joao Moreno 已提交
335

J
Joao Moreno 已提交
336
		this.el.style.fontSize = fontSizePx;
337
		this.el.style.fontWeight = fontWeight;
J
Joao Moreno 已提交
338
		this.type.style.fontFamily = fontFamily;
339 340
		this.close.style.height = lineHeightPx;
		this.close.style.width = lineHeightPx;
341
	}
342

343 344
	dispose(): void {
		this.disposables = dispose(this.disposables);
345
		this.renderDisposeable = dispose(this.renderDisposeable);
J
Joao Moreno 已提交
346
	}
J
Joao Moreno 已提交
347 348
}

349 350 351 352 353 354
export interface ISelectedSuggestion {
	item: ICompletionItem;
	index: number;
	model: CompletionModel;
}

J
Joao Moreno 已提交
355
export class SuggestWidget implements IContentWidget, IVirtualDelegate<ICompletionItem>, IDisposable {
J
Joao Moreno 已提交
356

M
Matt Bierner 已提交
357
	private static readonly ID: string = 'editor.widget.suggestWidget';
E
Erich Gamma 已提交
358

J
Johannes Rieken 已提交
359 360
	static LOADING_MESSAGE: string = nls.localize('suggestWidget.loading', "Loading...");
	static NO_SUGGESTIONS_MESSAGE: string = nls.localize('suggestWidget.noSuggestions', "No suggestions.");
E
Erich Gamma 已提交
361

J
Joao Moreno 已提交
362
	// Editor.IContentWidget.allowEditorOverflow
363
	readonly allowEditorOverflow = true;
J
Joao Moreno 已提交
364

J
Joao Moreno 已提交
365
	private state: State;
E
Erich Gamma 已提交
366
	private isAuto: boolean;
367
	private loadingTimeout: number;
368
	private currentSuggestionDetails: CancelablePromise<void>;
369
	private focusedItem: ICompletionItem;
370
	private ignoreFocusEvents = false;
J
Joao Moreno 已提交
371
	private completionModel: CompletionModel;
J
Joao Moreno 已提交
372

E
Erich Gamma 已提交
373
	private element: HTMLElement;
J
Joao Moreno 已提交
374
	private messageElement: HTMLElement;
J
Joao Moreno 已提交
375
	private listElement: HTMLElement;
J
Joao Moreno 已提交
376
	private details: SuggestionDetails;
377
	private list: List<ICompletionItem>;
378
	private listHeight: number;
E
Erich Gamma 已提交
379

A
Alex Dima 已提交
380 381 382
	private suggestWidgetVisible: IContextKey<boolean>;
	private suggestWidgetMultipleSuggestions: IContextKey<boolean>;
	private suggestionSupportsAutoAccept: IContextKey<boolean>;
J
Joao Moreno 已提交
383

384 385
	private readonly editorBlurTimeout = new TimeoutTimer();
	private readonly showTimeout = new TimeoutTimer();
J
Joao Moreno 已提交
386
	private toDispose: IDisposable[];
J
Joao Moreno 已提交
387

388 389
	private onDidSelectEmitter = new Emitter<ISelectedSuggestion>();
	private onDidFocusEmitter = new Emitter<ISelectedSuggestion>();
390 391 392
	private onDidHideEmitter = new Emitter<this>();
	private onDidShowEmitter = new Emitter<this>();

393

394 395
	readonly onDidSelect: Event<ISelectedSuggestion> = this.onDidSelectEmitter.event;
	readonly onDidFocus: Event<ISelectedSuggestion> = this.onDidFocusEmitter.event;
396 397
	readonly onDidHide: Event<this> = this.onDidHideEmitter.event;
	readonly onDidShow: Event<this> = this.onDidShowEmitter.event;
398

399
	private readonly maxWidgetWidth = 660;
400
	private readonly listWidth = 330;
401
	private storageService: IStorageService;
402 403
	private detailsFocusBorderColor: string;
	private detailsBorderColor: string;
404

405 406 407
	private storageServiceAvailable: boolean = true;
	private expandSuggestionDocs: boolean = false;

408 409
	private firstFocusInCurrentList: boolean = false;

410
	constructor(
A
Alex Dima 已提交
411
		private editor: ICodeEditor,
J
Joao Moreno 已提交
412
		@ITelemetryService private telemetryService: ITelemetryService,
413
		@IContextKeyService contextKeyService: IContextKeyService,
414 415
		@IThemeService themeService: IThemeService,
		@IStorageService storageService: IStorageService,
416 417 418
		@IKeybindingService keybindingService: IKeybindingService,
		@IModeService modeService: IModeService,
		@IOpenerService openerService: IOpenerService
419
	) {
420 421
		const kb = keybindingService.lookupKeybinding('editor.action.triggerSuggest');
		const triggerKeybindingLabel = !kb ? '' : ` (${kb.getLabel()})`;
422
		const markdownRenderer = new MarkdownRenderer(editor, modeService, openerService);
423

E
Erich Gamma 已提交
424
		this.isAuto = false;
J
Joao Moreno 已提交
425
		this.focusedItem = null;
426
		this.storageService = storageService;
427 428 429 430 431 432

		if (this.expandDocsSettingFromStorage() === undefined) {
			this.storageService.store('expandSuggestionDocs', expandSuggestionDocsByDefault, StorageScope.GLOBAL);
			if (this.expandDocsSettingFromStorage() === undefined) {
				this.storageServiceAvailable = false;
			}
433
		}
E
Erich Gamma 已提交
434

435
		this.element = $('.editor-widget.suggest-widget');
A
Alex Dima 已提交
436
		if (!this.editor.getConfiguration().contribInfo.iconsInSuggestions) {
J
Joao Moreno 已提交
437
			addClass(this.element, 'no-icons');
E
Erich Gamma 已提交
438 439
		}

J
Joao Moreno 已提交
440
		this.messageElement = append(this.element, $('.message'));
J
Joao Moreno 已提交
441
		this.listElement = append(this.element, $('.tree'));
442
		this.details = new SuggestionDetails(this.element, this, this.editor, markdownRenderer, triggerKeybindingLabel);
J
Joao Moreno 已提交
443

444
		let renderer = new Renderer(this, this.editor, triggerKeybindingLabel);
J
Joao Moreno 已提交
445

446 447
		this.list = new List(this.listElement, this, [renderer], {
			useShadows: false,
J
Joao Moreno 已提交
448
			selectOnMouseDown: true,
J
Joao Moreno 已提交
449 450
			focusOnMouseDown: false,
			openController: { shouldOpen: () => false }
451
		});
J
Joao Moreno 已提交
452 453

		this.toDispose = [
454
			attachListStyler(this.list, themeService, {
455
				listInactiveFocusBackground: editorSuggestWidgetSelectedBackground,
456 457
				listInactiveFocusOutline: activeContrastBorder
			}),
M
Martin Aeschlimann 已提交
458
			themeService.onThemeChange(t => this.onThemeChange(t)),
459
			editor.onDidLayoutChange(() => this.onEditorLayoutChange()),
J
Joao Moreno 已提交
460
			this.list.onSelectionChange(e => this.onListSelection(e)),
J
Joao Moreno 已提交
461
			this.list.onFocusChange(e => this.onListFocus(e)),
462
			this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged())
J
Joao Moreno 已提交
463 464
		];

465 466 467
		this.suggestWidgetVisible = SuggestContext.Visible.bindTo(contextKeyService);
		this.suggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(contextKeyService);
		this.suggestionSupportsAutoAccept = SuggestContext.AcceptOnKey.bindTo(contextKeyService);
J
Joao Moreno 已提交
468

J
Joao Moreno 已提交
469 470
		this.editor.addContentWidget(this);
		this.setState(State.Hidden);
471

M
Martin Aeschlimann 已提交
472
		this.onThemeChange(themeService.getTheme());
J
Joao Moreno 已提交
473
	}
E
Erich Gamma 已提交
474

J
Joao Moreno 已提交
475 476 477 478
	private onCursorSelectionChanged(): void {
		if (this.state === State.Hidden) {
			return;
		}
E
Erich Gamma 已提交
479

J
Joao Moreno 已提交
480 481
		this.editor.layoutContentWidget(this);
	}
J
Joao Moreno 已提交
482

483 484 485 486 487 488
	private onEditorLayoutChange(): void {
		if ((this.state === State.Open || this.state === State.Details) && this.expandDocsSettingFromStorage()) {
			this.expandSideOrBelow();
		}
	}

J
Joao Moreno 已提交
489
	private onListSelection(e: IListEvent<ICompletionItem>): void {
J
Joao Moreno 已提交
490 491 492
		if (!e.elements.length) {
			return;
		}
E
Erich Gamma 已提交
493

J
Joao Moreno 已提交
494
		const item = e.elements[0];
495
		const index = e.indexes[0];
496
		item.resolve(CancellationToken.None).then(() => {
497
			this.onDidSelectEmitter.fire({ item, index, model: this.completionModel });
J
Johannes Rieken 已提交
498 499
			alert(nls.localize('suggestionAriaAccepted', "{0}, accepted", item.suggestion.label));
			this.editor.focus();
500
		});
J
Joao Moreno 已提交
501
	}
E
Erich Gamma 已提交
502

J
Johannes Rieken 已提交
503
	private _getSuggestionAriaAlertLabel(item: ICompletionItem): string {
504 505 506 507 508 509 510 511
		const isSnippet = item.suggestion.type === 'snippet';

		if (!canExpandCompletionItem(item)) {
			return isSnippet ? nls.localize('ariaCurrentSnippetSuggestion', "{0}, snippet suggestion", item.suggestion.label)
				: nls.localize('ariaCurrentSuggestion', "{0}, suggestion", item.suggestion.label);
		} else if (this.expandDocsSettingFromStorage()) {
			return isSnippet ? nls.localize('ariaCurrentSnippeSuggestionReadDetails', "{0}, snippet suggestion. Reading details. {1}", item.suggestion.label, this.details.getAriaLabel())
				: nls.localize('ariaCurrenttSuggestionReadDetails', "{0}, suggestion. Reading details. {1}", item.suggestion.label, this.details.getAriaLabel());
512
		} else {
513 514
			return isSnippet ? nls.localize('ariaCurrentSnippetSuggestionWithDetails', "{0}, snippet suggestion, has details", item.suggestion.label)
				: nls.localize('ariaCurrentSuggestionWithDetails', "{0}, suggestion, has details", item.suggestion.label);
515 516 517 518
		}
	}

	private _lastAriaAlertLabel: string;
J
Johannes Rieken 已提交
519
	private _ariaAlert(newAriaAlertLabel: string): void {
520 521 522 523 524 525 526 527 528
		if (this._lastAriaAlertLabel === newAriaAlertLabel) {
			return;
		}
		this._lastAriaAlertLabel = newAriaAlertLabel;
		if (this._lastAriaAlertLabel) {
			alert(this._lastAriaAlertLabel);
		}
	}

M
Martin Aeschlimann 已提交
529 530 531
	private onThemeChange(theme: ITheme) {
		let backgroundColor = theme.getColor(editorSuggestWidgetBackground);
		if (backgroundColor) {
532 533
			this.listElement.style.backgroundColor = backgroundColor.toString();
			this.details.element.style.backgroundColor = backgroundColor.toString();
R
Ramya Achutha Rao 已提交
534
			this.messageElement.style.backgroundColor = backgroundColor.toString();
M
Martin Aeschlimann 已提交
535 536 537
		}
		let borderColor = theme.getColor(editorSuggestWidgetBorder);
		if (borderColor) {
538 539 540
			this.listElement.style.borderColor = borderColor.toString();
			this.details.element.style.borderColor = borderColor.toString();
			this.messageElement.style.borderColor = borderColor.toString();
541
			this.detailsBorderColor = borderColor.toString();
M
Martin Aeschlimann 已提交
542
		}
543 544 545 546
		let focusBorderColor = theme.getColor(focusBorder);
		if (focusBorderColor) {
			this.detailsFocusBorderColor = focusBorderColor.toString();
		}
547
		this.details.setBorderWidth(theme.type === 'hc' ? 2 : 1);
M
Martin Aeschlimann 已提交
548 549
	}

J
Joao Moreno 已提交
550
	private onListFocus(e: IListEvent<ICompletionItem>): void {
551 552 553 554
		if (this.ignoreFocusEvents) {
			return;
		}

J
Joao Moreno 已提交
555
		if (!e.elements.length) {
J
Joao Moreno 已提交
556 557 558 559 560
			if (this.currentSuggestionDetails) {
				this.currentSuggestionDetails.cancel();
				this.currentSuggestionDetails = null;
				this.focusedItem = null;
			}
561

J
Joao Moreno 已提交
562
			this._ariaAlert(null);
J
Joao Moreno 已提交
563 564
			return;
		}
E
Erich Gamma 已提交
565

J
Joao Moreno 已提交
566
		const item = e.elements[0];
567

568
		this.firstFocusInCurrentList = !this.focusedItem;
J
Joao Moreno 已提交
569 570 571
		if (item === this.focusedItem) {
			return;
		}
E
Erich Gamma 已提交
572

J
Joao Moreno 已提交
573 574 575 576 577
		if (this.currentSuggestionDetails) {
			this.currentSuggestionDetails.cancel();
			this.currentSuggestionDetails = null;
		}

J
Joao Moreno 已提交
578
		const index = e.indexes[0];
E
Erich Gamma 已提交
579

580
		this.suggestionSupportsAutoAccept.set(!item.suggestion.noAutoAccept);
581

J
Joao Moreno 已提交
582
		this.focusedItem = item;
583

J
Joao Moreno 已提交
584
		this.list.reveal(index);
J
Joao Moreno 已提交
585

586 587 588
		this.currentSuggestionDetails = createCancelablePromise(token => item.resolve(token));

		this.currentSuggestionDetails.then(() => {
589 590 591 592
			if (this.list.length < index) {
				return;
			}

593 594 595 596 597 598 599 600 601 602 603
			// item can have extra information, so re-render
			this.ignoreFocusEvents = true;
			this.list.splice(index, 1, [item]);
			this.list.setFocus([index]);
			this.ignoreFocusEvents = false;

			if (this.expandDocsSettingFromStorage()) {
				this.showDetails();
			} else {
				removeClass(this.element, 'docs-side');
			}
604 605

			this._ariaAlert(this._getSuggestionAriaAlertLabel(item));
J
Joao Moreno 已提交
606 607 608 609 610
		}).catch(onUnexpectedError).then(() => {
			if (this.focusedItem === item) {
				this.currentSuggestionDetails = null;
			}
		});
611 612

		// emit an event
613
		this.onDidFocusEmitter.fire({ item, index, model: this.completionModel });
J
Joao Moreno 已提交
614
	}
J
Joao Moreno 已提交
615 616

	private setState(state: State): void {
J
Joao Moreno 已提交
617 618 619 620
		if (!this.element) {
			return;
		}

621
		const stateChanged = this.state !== state;
J
Joao Moreno 已提交
622 623
		this.state = state;

J
Joao Moreno 已提交
624 625
		toggleClass(this.element, 'frozen', state === State.Frozen);

J
Joao Moreno 已提交
626 627
		switch (state) {
			case State.Hidden:
628
				hide(this.messageElement, this.details.element, this.listElement);
J
Joao Moreno 已提交
629
				this.hide();
630
				this.listHeight = 0;
A
tslint  
Alex Dima 已提交
631
				if (stateChanged) {
632
					this.list.splice(0, this.list.length);
A
tslint  
Alex Dima 已提交
633
				}
634
				this.focusedItem = null;
635
				break;
J
Joao Moreno 已提交
636
			case State.Loading:
J
Joao Moreno 已提交
637
				this.messageElement.textContent = SuggestWidget.LOADING_MESSAGE;
J
Joao Moreno 已提交
638
				hide(this.listElement, this.details.element);
J
Joao Moreno 已提交
639
				show(this.messageElement);
R
Ramya Achutha Rao 已提交
640
				removeClass(this.element, 'docs-side');
641
				this.show();
642
				this.focusedItem = null;
J
Joao Moreno 已提交
643 644
				break;
			case State.Empty:
J
Joao Moreno 已提交
645
				this.messageElement.textContent = SuggestWidget.NO_SUGGESTIONS_MESSAGE;
J
Joao Moreno 已提交
646
				hide(this.listElement, this.details.element);
J
Joao Moreno 已提交
647
				show(this.messageElement);
R
Ramya Achutha Rao 已提交
648
				removeClass(this.element, 'docs-side');
649
				this.show();
650
				this.focusedItem = null;
J
Joao Moreno 已提交
651 652
				break;
			case State.Open:
653
				hide(this.messageElement);
654
				show(this.listElement);
655
				this.show();
J
Joao Moreno 已提交
656 657
				break;
			case State.Frozen:
658
				hide(this.messageElement);
J
Joao Moreno 已提交
659
				show(this.listElement);
660
				this.show();
J
Joao Moreno 已提交
661 662
				break;
			case State.Details:
663 664
				hide(this.messageElement);
				show(this.details.element, this.listElement);
665
				this.show();
666
				this._ariaAlert(this.details.getAriaLabel());
J
Joao Moreno 已提交
667 668
				break;
		}
669 670
	}

671
	showTriggered(auto: boolean, delay: number) {
J
Joao Moreno 已提交
672 673 674
		if (this.state !== State.Hidden) {
			return;
		}
E
Erich Gamma 已提交
675

676
		this.isAuto = !!auto;
J
Joao Moreno 已提交
677 678 679 680 681

		if (!this.isAuto) {
			this.loadingTimeout = setTimeout(() => {
				this.loadingTimeout = null;
				this.setState(State.Loading);
682
			}, delay);
J
Joao Moreno 已提交
683
		}
684
	}
E
Erich Gamma 已提交
685

686
	showSuggestions(completionModel: CompletionModel, selectionIndex: number, isFrozen: boolean, isAuto: boolean): void {
A
Alex Dima 已提交
687 688 689 690
		if (this.loadingTimeout) {
			clearTimeout(this.loadingTimeout);
			this.loadingTimeout = null;
		}
J
Joao Moreno 已提交
691

692 693 694
		if (this.completionModel !== completionModel) {
			this.completionModel = completionModel;
		}
J
Joao Moreno 已提交
695

R
Ramya Achutha Rao 已提交
696
		if (isFrozen && this.state !== State.Empty && this.state !== State.Hidden) {
697 698
			this.setState(State.Frozen);
			return;
699 700
		}

701 702
		let visibleCount = this.completionModel.items.length;

J
Joao Moreno 已提交
703
		const isEmpty = visibleCount === 0;
704
		this.suggestWidgetMultipleSuggestions.set(visibleCount > 1);
J
Joao Moreno 已提交
705 706

		if (isEmpty) {
707
			if (isAuto) {
J
Joao Moreno 已提交
708 709
				this.setState(State.Hidden);
			} else {
Y
Yuki Ueda 已提交
710
				this.setState(State.Empty);
J
Joao Moreno 已提交
711 712
			}

J
Joao Moreno 已提交
713
			this.completionModel = null;
J
Joao Moreno 已提交
714

J
Joao Moreno 已提交
715
		} else {
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730

			if (this.state !== State.Open) {
				const { stats } = this.completionModel;
				stats['wasAutomaticallyTriggered'] = !!isAuto;
				/* __GDPR__
					"suggestWidget" : {
						"wasAutomaticallyTriggered" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
						"${include}": [
							"${ICompletionStats}",
							"${EditorTelemetryData}"
						]
					}
				*/
				this.telemetryService.publicLog('suggestWidget', { ...stats, ...this.editor.getTelemetryData() });
			}
J
Joao Moreno 已提交
731

732 733
			this.list.splice(0, this.list.length, this.completionModel.items);

R
Ramya Achutha Rao 已提交
734 735 736 737 738
			if (isFrozen) {
				this.setState(State.Frozen);
			} else {
				this.setState(State.Open);
			}
R
Ramya Achutha Rao 已提交
739

J
Joao Moreno 已提交
740
			this.list.reveal(selectionIndex, selectionIndex);
J
Joao Moreno 已提交
741 742
			this.list.setFocus([selectionIndex]);

R
Ramya Achutha Rao 已提交
743 744 745 746
			// Reset focus border
			if (this.detailsBorderColor) {
				this.details.element.style.borderColor = this.detailsBorderColor;
			}
J
Joao Moreno 已提交
747
		}
748
	}
E
Erich Gamma 已提交
749

J
Joao Moreno 已提交
750
	selectNextPage(): boolean {
J
Joao Moreno 已提交
751 752 753 754 755 756 757 758 759 760 761 762
		switch (this.state) {
			case State.Hidden:
				return false;
			case State.Details:
				this.details.pageDown();
				return true;
			case State.Loading:
				return !this.isAuto;
			default:
				this.list.focusNextPage();
				return true;
		}
E
Erich Gamma 已提交
763 764
	}

J
Joao Moreno 已提交
765
	selectNext(): boolean {
J
Joao Moreno 已提交
766 767 768 769 770 771 772 773 774
		switch (this.state) {
			case State.Hidden:
				return false;
			case State.Loading:
				return !this.isAuto;
			default:
				this.list.focusNext(1, true);
				return true;
		}
E
Erich Gamma 已提交
775 776
	}

777 778 779 780 781
	selectLast(): boolean {
		switch (this.state) {
			case State.Hidden:
				return false;
			case State.Details:
782
				this.details.scrollBottom();
783 784 785 786 787 788 789 790 791
				return true;
			case State.Loading:
				return !this.isAuto;
			default:
				this.list.focusLast();
				return true;
		}
	}

J
Joao Moreno 已提交
792
	selectPreviousPage(): boolean {
J
Joao Moreno 已提交
793 794 795 796 797 798 799 800 801 802 803 804
		switch (this.state) {
			case State.Hidden:
				return false;
			case State.Details:
				this.details.pageUp();
				return true;
			case State.Loading:
				return !this.isAuto;
			default:
				this.list.focusPreviousPage();
				return true;
		}
E
Erich Gamma 已提交
805 806
	}

J
Joao Moreno 已提交
807
	selectPrevious(): boolean {
J
Joao Moreno 已提交
808 809 810 811 812 813 814
		switch (this.state) {
			case State.Hidden:
				return false;
			case State.Loading:
				return !this.isAuto;
			default:
				this.list.focusPrevious(1, true);
J
Joao Moreno 已提交
815
				return false;
J
Joao Moreno 已提交
816
		}
E
Erich Gamma 已提交
817 818
	}

819 820 821 822 823
	selectFirst(): boolean {
		switch (this.state) {
			case State.Hidden:
				return false;
			case State.Details:
824
				this.details.scrollTop();
825 826 827 828 829 830 831 832 833
				return true;
			case State.Loading:
				return !this.isAuto;
			default:
				this.list.focusFirst();
				return true;
		}
	}

834
	getFocusedItem(): ISelectedSuggestion {
835 836 837 838
		if (this.state !== State.Hidden
			&& this.state !== State.Empty
			&& this.state !== State.Loading) {

839 840 841 842 843
			return {
				item: this.list.getFocusedElements()[0],
				index: this.list.getFocus()[0],
				model: this.completionModel
			};
J
Joao Moreno 已提交
844
		}
845
		return undefined;
E
Erich Gamma 已提交
846 847
	}

848
	toggleDetailsFocus(): void {
J
Joao Moreno 已提交
849 850
		if (this.state === State.Details) {
			this.setState(State.Open);
851 852 853
			if (this.detailsBorderColor) {
				this.details.element.style.borderColor = this.detailsBorderColor;
			}
854
		} else if (this.state === State.Open && this.expandDocsSettingFromStorage()) {
855
			this.setState(State.Details);
856 857 858
			if (this.detailsFocusBorderColor) {
				this.details.element.style.borderColor = this.detailsFocusBorderColor;
			}
J
Joao Moreno 已提交
859
		}
K
kieferrm 已提交
860
		/* __GDPR__
K
kieferrm 已提交
861 862 863 864 865 866
			"suggestWidget:toggleDetailsFocus" : {
				"${include}": [
					"${EditorTelemetryData}"
				]
			}
		*/
867
		this.telemetryService.publicLog('suggestWidget:toggleDetailsFocus', this.editor.getTelemetryData());
868
	}
J
Joao Moreno 已提交
869

870
	toggleDetails(): void {
R
Ramya Achutha Rao 已提交
871 872 873
		if (!canExpandCompletionItem(this.list.getFocusedElements()[0])) {
			return;
		}
874

875 876
		if (this.expandDocsSettingFromStorage()) {
			this.updateExpandDocsSetting(false);
877
			hide(this.details.element);
878
			removeClass(this.element, 'docs-side');
879
			removeClass(this.element, 'docs-below');
880
			this.editor.layoutContentWidget(this);
K
kieferrm 已提交
881
			/* __GDPR__
K
kieferrm 已提交
882 883 884 885 886 887
				"suggestWidget:collapseDetails" : {
					"${include}": [
						"${EditorTelemetryData}"
					]
				}
			*/
888
			this.telemetryService.publicLog('suggestWidget:collapseDetails', this.editor.getTelemetryData());
889
		} else {
890
			if (this.state !== State.Open && this.state !== State.Details && this.state !== State.Frozen) {
891 892 893
				return;
			}

894
			this.updateExpandDocsSetting(true);
895
			this.showDetails();
896
			this._ariaAlert(this.details.getAriaLabel());
K
kieferrm 已提交
897
			/* __GDPR__
K
kieferrm 已提交
898 899 900 901 902 903
				"suggestWidget:expandDetails" : {
					"${include}": [
						"${EditorTelemetryData}"
					]
				}
			*/
904
			this.telemetryService.publicLog('suggestWidget:expandDetails', this.editor.getTelemetryData());
905
		}
906

907 908
	}

909
	showDetails(): void {
R
Ramya Achutha Rao 已提交
910
		this.expandSideOrBelow();
911 912

		show(this.details.element);
913
		this.details.render(this.list.getFocusedElements()[0]);
914
		this.details.element.style.maxHeight = this.maxWidgetHeight + 'px';
915

R
Ramya Achutha Rao 已提交
916 917
		// Reset margin-top that was set as Fix for #26416
		this.listElement.style.marginTop = '0px';
918 919 920 921 922

		// with docs showing up widget width/height may change, so reposition the widget
		this.editor.layoutContentWidget(this);

		this.adjustDocsPosition();
J
Joao Moreno 已提交
923

J
Joao Moreno 已提交
924
		this.editor.focus();
J
Joao Moreno 已提交
925 926
	}

927
	private show(): void {
928 929 930 931 932 933
		const newHeight = this.updateListHeight();
		if (newHeight !== this.listHeight) {
			this.editor.layoutContentWidget(this);
			this.listHeight = newHeight;
		}

J
Joao Moreno 已提交
934
		this.suggestWidgetVisible.set(true);
935

936
		this.showTimeout.cancelAndSet(() => {
J
Joao Moreno 已提交
937
			addClass(this.element, 'visible');
938
			this.onDidShowEmitter.fire(this);
939
		}, 100);
E
Erich Gamma 已提交
940 941
	}

942
	private hide(): void {
J
Joao Moreno 已提交
943
		this.suggestWidgetVisible.reset();
S
Sean Kelly 已提交
944
		this.suggestWidgetMultipleSuggestions.reset();
J
Joao Moreno 已提交
945
		removeClass(this.element, 'visible');
E
Erich Gamma 已提交
946 947
	}

948 949 950
	hideWidget(): void {
		clearTimeout(this.loadingTimeout);
		this.setState(State.Hidden);
951
		this.onDidHideEmitter.fire(this);
952 953
	}

J
Joao Moreno 已提交
954
	getPosition(): IContentWidgetPosition {
J
Joao Moreno 已提交
955 956
		if (this.state === State.Hidden) {
			return null;
E
Erich Gamma 已提交
957
		}
J
Joao Moreno 已提交
958 959

		return {
960
			position: this.editor.getPosition(),
A
Alex Dima 已提交
961
			preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE]
J
Joao Moreno 已提交
962
		};
E
Erich Gamma 已提交
963 964
	}

J
Joao Moreno 已提交
965
	getDomNode(): HTMLElement {
E
Erich Gamma 已提交
966 967 968
		return this.element;
	}

J
Joao Moreno 已提交
969
	getId(): string {
E
Erich Gamma 已提交
970 971 972
		return SuggestWidget.ID;
	}

973
	private updateListHeight(): number {
J
Joao Moreno 已提交
974
		let height = 0;
E
Erich Gamma 已提交
975

J
Joao Moreno 已提交
976
		if (this.state === State.Empty || this.state === State.Loading) {
977
			height = this.unfocusedHeight;
J
Joao Moreno 已提交
978
		} else {
979 980
			const suggestionCount = this.list.contentHeight / this.unfocusedHeight;
			height = Math.min(suggestionCount, maxSuggestionsToShow) * this.unfocusedHeight;
J
Joao Moreno 已提交
981
		}
J
Joao Moreno 已提交
982

983
		this.element.style.lineHeight = `${this.unfocusedHeight}px`;
984
		this.listElement.style.height = `${height}px`;
J
Joao Moreno 已提交
985
		this.list.layout(height);
986
		return height;
J
Joao Moreno 已提交
987 988
	}

989
	private adjustDocsPosition() {
R
Ramya Achutha Rao 已提交
990
		const lineHeight = this.editor.getConfiguration().fontInfo.lineHeight;
991 992 993 994
		const cursorCoords = this.editor.getScrolledVisiblePosition(this.editor.getPosition());
		const editorCoords = getDomNodePagePosition(this.editor.getDomNode());
		const cursorX = editorCoords.left + cursorCoords.left;
		const cursorY = editorCoords.top + cursorCoords.top + cursorCoords.height;
995 996 997
		const widgetCoords = getDomNodePagePosition(this.element);
		const widgetX = widgetCoords.left;
		const widgetY = widgetCoords.top;
998

999 1000 1001
		if (widgetX < cursorX - this.listWidth) {
			// Widget is too far to the left of cursor, swap list and docs
			addClass(this.element, 'list-right');
1002 1003
		} else {
			removeClass(this.element, 'list-right');
1004 1005
		}

R
Ramya Achutha Rao 已提交
1006 1007 1008
		// Compare top of the cursor (cursorY - lineheight) with widgetTop to determine if
		// margin-top needs to be applied on list to make it appear right above the cursor
		// Cannot compare cursorY directly as it may be a few decimals off due to zoooming
R
Ramya Achutha Rao 已提交
1009
		if (hasClass(this.element, 'docs-side')
R
Ramya Achutha Rao 已提交
1010
			&& cursorY - lineHeight > widgetY
R
Ramya Achutha Rao 已提交
1011 1012 1013 1014 1015
			&& this.details.element.offsetHeight > this.listElement.offsetHeight) {

			// Fix for #26416
			// Docs is bigger than list and widget is above cursor, apply margin-top so that list appears right above cursor
			this.listElement.style.marginTop = `${this.details.element.offsetHeight - this.listElement.offsetHeight}px`;
1016
		}
1017 1018
	}

1019
	private expandSideOrBelow() {
1020 1021 1022 1023 1024 1025
		if (!canExpandCompletionItem(this.focusedItem) && this.firstFocusInCurrentList) {
			removeClass(this.element, 'docs-side');
			removeClass(this.element, 'docs-below');
			return;
		}

1026 1027 1028 1029
		let matches = this.element.style.maxWidth.match(/(\d+)px/);
		if (!matches || Number(matches[1]) < this.maxWidgetWidth) {
			addClass(this.element, 'docs-below');
			removeClass(this.element, 'docs-side');
1030
		} else if (canExpandCompletionItem(this.focusedItem)) {
1031 1032 1033 1034 1035
			addClass(this.element, 'docs-side');
			removeClass(this.element, 'docs-below');
		}
	}

1036 1037
	// Heights

1038 1039
	private get maxWidgetHeight(): number {
		return this.unfocusedHeight * maxSuggestionsToShow;
1040 1041 1042
	}

	private get unfocusedHeight(): number {
J
Joao Moreno 已提交
1043 1044
		const configuration = this.editor.getConfiguration();
		return configuration.contribInfo.suggestLineHeight || configuration.fontInfo.lineHeight;
1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
	}

	// IDelegate

	getHeight(element: ICompletionItem): number {
		return this.unfocusedHeight;
	}

	getTemplateId(element: ICompletionItem): string {
		return 'suggestion';
	}

1057 1058 1059
	// Monaco Editor does not have a storage service
	private expandDocsSettingFromStorage(): boolean {
		if (this.storageServiceAvailable) {
1060
			return this.storageService.getBoolean('expandSuggestionDocs', StorageScope.GLOBAL);
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074
		} else {
			return this.expandSuggestionDocs;
		}
	}

	// Monaco Editor does not have a storage service
	private updateExpandDocsSetting(value: boolean) {
		if (this.storageServiceAvailable) {
			this.storageService.store('expandSuggestionDocs', value, StorageScope.GLOBAL);
		} else {
			this.expandSuggestionDocs = value;
		}
	}

J
Joao Moreno 已提交
1075
	dispose(): void {
J
Joao Moreno 已提交
1076 1077 1078
		this.state = null;
		this.suggestionSupportsAutoAccept = null;
		this.currentSuggestionDetails = null;
J
Joao Moreno 已提交
1079
		this.focusedItem = null;
J
Joao Moreno 已提交
1080 1081
		this.element = null;
		this.messageElement = null;
J
Joao Moreno 已提交
1082
		this.listElement = null;
J
Joao Moreno 已提交
1083 1084
		this.details.dispose();
		this.details = null;
J
Joao Moreno 已提交
1085 1086
		this.list.dispose();
		this.list = null;
J
Joao Moreno 已提交
1087
		this.toDispose = dispose(this.toDispose);
A
Alex Dima 已提交
1088 1089 1090 1091
		if (this.loadingTimeout) {
			clearTimeout(this.loadingTimeout);
			this.loadingTimeout = null;
		}
J
Joao Moreno 已提交
1092

1093 1094
		this.editorBlurTimeout.dispose();
		this.showTimeout.dispose();
E
Erich Gamma 已提交
1095
	}
J
Johannes Rieken 已提交
1096
}
1097 1098

registerThemingParticipant((theme, collector) => {
1099
	let matchHighlight = theme.getColor(editorSuggestWidgetHighlightForeground);
1100
	if (matchHighlight) {
J
Johannes Rieken 已提交
1101
		collector.addRule(`.monaco-editor .suggest-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { color: ${matchHighlight}; }`);
1102
	}
1103 1104
	let foreground = theme.getColor(editorSuggestWidgetForeground);
	if (foreground) {
1105
		collector.addRule(`.monaco-editor .suggest-widget { color: ${foreground}; }`);
1106
	}
M
Matt Bierner 已提交
1107 1108 1109 1110 1111

	const link = theme.getColor(textLinkForeground);
	if (link) {
		collector.addRule(`.monaco-editor .suggest-widget a { color: ${link}; }`);
	}
1112 1113 1114 1115 1116

	let codeBackground = theme.getColor(textCodeBlockBackground);
	if (codeBackground) {
		collector.addRule(`.monaco-editor .suggest-widget code { background-color: ${codeBackground}; }`);
	}
1117
});