inputBox.ts 20.5 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.
 *--------------------------------------------------------------------------------------------*/

import 'vs/css!./inputBox';
A
Alex Dima 已提交
7

8
import * as nls from 'vs/nls';
A
Alex Dima 已提交
9
import * as dom from 'vs/base/browser/dom';
10
import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
11
import { renderFormattedText, renderText } from 'vs/base/browser/formattedTextRenderer';
12
import * as aria from 'vs/base/browser/ui/aria/aria';
J
Johannes Rieken 已提交
13 14 15
import { IAction } from 'vs/base/common/actions';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IContextViewProvider, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
M
Matt Bierner 已提交
16
import { Event, Emitter } from 'vs/base/common/event';
J
Johannes Rieken 已提交
17
import { Widget } from 'vs/base/browser/ui/widget';
B
Benjamin Pasero 已提交
18
import { Color } from 'vs/base/common/color';
19
import { mixin } from 'vs/base/common/objects';
20
import { HistoryNavigator } from 'vs/base/common/history';
21
import { IHistoryNavigationWidget } from 'vs/base/browser/history';
J
Joao Moreno 已提交
22 23 24
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { domEvent } from 'vs/base/browser/event';
A
Alex Dima 已提交
25

J
Joao Moreno 已提交
26
const $ = dom.$;
E
Erich Gamma 已提交
27

B
Benjamin Pasero 已提交
28
export interface IInputOptions extends IInputBoxStyles {
M
Matt Bierner 已提交
29 30 31 32 33
	readonly placeholder?: string;
	readonly ariaLabel?: string;
	readonly type?: string;
	readonly validationOptions?: IInputValidationOptions;
	readonly flexibleHeight?: boolean;
34
	readonly flexibleWidth?: boolean;
J
Joao Moreno 已提交
35
	readonly flexibleMaxHeight?: number;
36
	readonly actions?: ReadonlyArray<IAction>;
E
Erich Gamma 已提交
37 38
}

39
export interface IInputBoxStyles {
40 41 42 43 44 45 46 47 48 49 50 51
	readonly inputBackground?: Color;
	readonly inputForeground?: Color;
	readonly inputBorder?: Color;
	readonly inputValidationInfoBorder?: Color;
	readonly inputValidationInfoBackground?: Color;
	readonly inputValidationInfoForeground?: Color;
	readonly inputValidationWarningBorder?: Color;
	readonly inputValidationWarningBackground?: Color;
	readonly inputValidationWarningForeground?: Color;
	readonly inputValidationErrorBorder?: Color;
	readonly inputValidationErrorBackground?: Color;
	readonly inputValidationErrorForeground?: Color;
52 53
}

E
Erich Gamma 已提交
54
export interface IInputValidator {
M
Matt Bierner 已提交
55
	(value: string): IMessage | null;
E
Erich Gamma 已提交
56 57 58
}

export interface IMessage {
M
Matt Bierner 已提交
59 60 61
	readonly content: string;
	readonly formatContent?: boolean; // defaults to false
	readonly type?: MessageType;
E
Erich Gamma 已提交
62 63 64
}

export interface IInputValidationOptions {
M
Matt Bierner 已提交
65
	validation?: IInputValidator;
E
Erich Gamma 已提交
66 67
}

68
export const enum MessageType {
E
Erich Gamma 已提交
69 70 71 72 73 74 75 76 77 78
	INFO = 1,
	WARNING = 2,
	ERROR = 3
}

export interface IRange {
	start: number;
	end: number;
}

79 80
const defaultOpts = {
	inputBackground: Color.fromHex('#3C3C3C'),
81
	inputForeground: Color.fromHex('#CCCCCC'),
B
Benjamin Pasero 已提交
82 83 84 85 86 87
	inputValidationInfoBorder: Color.fromHex('#55AAFF'),
	inputValidationInfoBackground: Color.fromHex('#063B49'),
	inputValidationWarningBorder: Color.fromHex('#B89500'),
	inputValidationWarningBackground: Color.fromHex('#352A05'),
	inputValidationErrorBorder: Color.fromHex('#BE1100'),
	inputValidationErrorBackground: Color.fromHex('#5A1D1D')
88 89
};

A
Alex Dima 已提交
90
export class InputBox extends Widget {
M
Matt Bierner 已提交
91
	private contextViewProvider?: IContextViewProvider;
92
	element: HTMLElement;
E
Erich Gamma 已提交
93
	private input: HTMLInputElement;
M
Matt Bierner 已提交
94
	private actionbar?: ActionBar;
E
Erich Gamma 已提交
95
	private options: IInputOptions;
M
Matt Bierner 已提交
96
	private message: IMessage | null;
E
Erich Gamma 已提交
97 98
	private placeholder: string;
	private ariaLabel: string;
M
Matt Bierner 已提交
99
	private validation?: IInputValidator;
J
Joao Moreno 已提交
100
	private state: 'idle' | 'open' | 'closed' = 'idle';
J
Joao Moreno 已提交
101 102 103 104 105 106

	private mirror: HTMLElement | undefined;
	private cachedHeight: number | undefined;
	private cachedContentHeight: number | undefined;
	private maxHeight: number = Number.POSITIVE_INFINITY;
	private scrollableElement: ScrollableElement | undefined;
M
Matt Bierner 已提交
107

108 109 110 111 112 113 114 115 116 117 118 119 120
	private inputBackground?: Color;
	private inputForeground?: Color;
	private inputBorder?: Color;

	private inputValidationInfoBorder?: Color;
	private inputValidationInfoBackground?: Color;
	private inputValidationInfoForeground?: Color;
	private inputValidationWarningBorder?: Color;
	private inputValidationWarningBackground?: Color;
	private inputValidationWarningForeground?: Color;
	private inputValidationErrorBorder?: Color;
	private inputValidationErrorBackground?: Color;
	private inputValidationErrorForeground?: Color;
121

A
Alex Dima 已提交
122
	private _onDidChange = this._register(new Emitter<string>());
123
	public readonly onDidChange: Event<string> = this._onDidChange.event;
A
Alex Dima 已提交
124 125

	private _onDidHeightChange = this._register(new Emitter<number>());
126
	public readonly onDidHeightChange: Event<number> = this._onDidHeightChange.event;
A
Alex Dima 已提交
127

M
Matt Bierner 已提交
128
	constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options?: IInputOptions) {
E
Erich Gamma 已提交
129 130 131 132
		super();

		this.contextViewProvider = contextViewProvider;
		this.options = options || Object.create(null);
133
		mixin(this.options, defaultOpts, false);
E
Erich Gamma 已提交
134 135 136
		this.message = null;
		this.placeholder = this.options.placeholder || '';
		this.ariaLabel = this.options.ariaLabel || '';
137

138 139
		this.inputBackground = this.options.inputBackground;
		this.inputForeground = this.options.inputForeground;
B
Benjamin Pasero 已提交
140
		this.inputBorder = this.options.inputBorder;
E
Erich Gamma 已提交
141

B
Benjamin Pasero 已提交
142 143
		this.inputValidationInfoBorder = this.options.inputValidationInfoBorder;
		this.inputValidationInfoBackground = this.options.inputValidationInfoBackground;
144
		this.inputValidationInfoForeground = this.options.inputValidationInfoForeground;
B
Benjamin Pasero 已提交
145 146
		this.inputValidationWarningBorder = this.options.inputValidationWarningBorder;
		this.inputValidationWarningBackground = this.options.inputValidationWarningBackground;
147
		this.inputValidationWarningForeground = this.options.inputValidationWarningForeground;
B
Benjamin Pasero 已提交
148 149
		this.inputValidationErrorBorder = this.options.inputValidationErrorBorder;
		this.inputValidationErrorBackground = this.options.inputValidationErrorBackground;
150
		this.inputValidationErrorForeground = this.options.inputValidationErrorForeground;
151

E
Erich Gamma 已提交
152 153 154 155 156 157
		if (this.options.validationOptions) {
			this.validation = this.options.validationOptions.validation;
		}

		this.element = dom.append(container, $('.monaco-inputbox.idle'));

A
Alex Dima 已提交
158
		let tagName = this.options.flexibleHeight ? 'textarea' : 'input';
E
Erich Gamma 已提交
159

A
Alex Dima 已提交
160
		let wrapper = dom.append(this.element, $('.wrapper'));
J
Joao Moreno 已提交
161
		this.input = dom.append(wrapper, $(tagName + '.input.empty'));
E
Erich Gamma 已提交
162 163 164
		this.input.setAttribute('autocorrect', 'off');
		this.input.setAttribute('autocapitalize', 'off');
		this.input.setAttribute('spellcheck', 'false');
165

166 167
		this.onfocus(this.input, () => this.element.classList.add('synthetic-focus'));
		this.onblur(this.input, () => this.element.classList.remove('synthetic-focus'));
E
Erich Gamma 已提交
168 169

		if (this.options.flexibleHeight) {
J
Joao Moreno 已提交
170 171
			this.maxHeight = typeof this.options.flexibleMaxHeight === 'number' ? this.options.flexibleMaxHeight : Number.POSITIVE_INFINITY;

E
Erich Gamma 已提交
172
			this.mirror = dom.append(wrapper, $('div.mirror'));
173
			this.mirror.innerText = '\u00a0';
J
Joao Moreno 已提交
174 175

			this.scrollableElement = new ScrollableElement(this.element, { vertical: ScrollbarVisibility.Auto });
176 177 178 179

			if (this.options.flexibleWidth) {
				this.input.setAttribute('wrap', 'off');
				this.mirror.style.whiteSpace = 'pre';
P
Peng Lyu 已提交
180
				this.mirror.style.wordWrap = 'initial';
181 182
			}

J
Joao Moreno 已提交
183 184 185 186 187 188 189 190
			dom.append(container, this.scrollableElement.getDomNode());
			this._register(this.scrollableElement);

			// from ScrollableElement to DOM
			this._register(this.scrollableElement.onScroll(e => this.input.scrollTop = e.scrollTop));

			const onSelectionChange = Event.filter(domEvent(document, 'selectionchange'), () => {
				const selection = document.getSelection();
B
Benjamin Pasero 已提交
191
				return selection?.anchorNode === wrapper;
J
Joao Moreno 已提交
192 193 194 195 196
			});

			// from DOM to ScrollableElement
			this._register(onSelectionChange(this.updateScrollDimensions, this));
			this._register(this.onDidHeightChange(this.updateScrollDimensions, this));
E
Erich Gamma 已提交
197 198 199 200 201 202 203 204 205 206
		} else {
			this.input.type = this.options.type || 'text';
			this.input.setAttribute('wrap', 'off');
		}

		if (this.ariaLabel) {
			this.input.setAttribute('aria-label', this.ariaLabel);
		}

		if (this.placeholder) {
207
			this.setPlaceHolder(this.placeholder);
E
Erich Gamma 已提交
208 209
		}

A
Alex Dima 已提交
210 211 212
		this.oninput(this.input, () => this.onValueChange());
		this.onblur(this.input, () => this.onBlur());
		this.onfocus(this.input, () => this.onFocus());
E
Erich Gamma 已提交
213

214 215
		this.ignoreGesture(this.input);

J
Joao Moreno 已提交
216
		setTimeout(() => this.updateMirror(), 0);
E
Erich Gamma 已提交
217 218 219

		// Support actions
		if (this.options.actions) {
A
Alex Dima 已提交
220
			this.actionbar = this._register(new ActionBar(this.element));
E
Erich Gamma 已提交
221 222
			this.actionbar.push(this.options.actions, { icon: true, label: false });
		}
B
Benjamin Pasero 已提交
223

224
		this.applyStyles();
E
Erich Gamma 已提交
225 226 227 228 229 230 231 232 233 234
	}

	private onBlur(): void {
		this._hideMessage();
	}

	private onFocus(): void {
		this._showMessage();
	}

235
	public setPlaceHolder(placeHolder: string): void {
J
Joao Moreno 已提交
236
		this.placeholder = placeHolder;
J
Joao Moreno 已提交
237 238
		this.input.setAttribute('placeholder', placeHolder);
		this.input.title = placeHolder;
E
Erich Gamma 已提交
239 240
	}

241 242 243
	public setAriaLabel(label: string): void {
		this.ariaLabel = label;

J
Joao Moreno 已提交
244 245 246 247
		if (label) {
			this.input.setAttribute('aria-label', this.ariaLabel);
		} else {
			this.input.removeAttribute('aria-label');
248 249 250
		}
	}

251 252 253 254
	public getAriaLabel(): string {
		return this.ariaLabel;
	}

I
isidor 已提交
255
	public get mirrorElement(): HTMLElement | undefined {
256 257 258
		return this.mirror;
	}

E
Erich Gamma 已提交
259 260 261 262
	public get inputElement(): HTMLInputElement {
		return this.input;
	}

263
	public get value(): string {
E
Erich Gamma 已提交
264 265 266
		return this.input.value;
	}

267
	public set value(newValue: string) {
E
Erich Gamma 已提交
268 269 270 271 272 273 274
		if (this.input.value !== newValue) {
			this.input.value = newValue;
			this.onValueChange();
		}
	}

	public get height(): number {
J
Joao Moreno 已提交
275
		return typeof this.cachedHeight === 'number' ? this.cachedHeight : dom.getTotalHeight(this.element);
E
Erich Gamma 已提交
276 277 278 279 280 281 282 283 284 285 286
	}

	public focus(): void {
		this.input.focus();
	}

	public blur(): void {
		this.input.blur();
	}

	public hasFocus(): boolean {
287
		return document.activeElement === this.input;
E
Erich Gamma 已提交
288 289
	}

290
	public select(range: IRange | null = null): void {
E
Erich Gamma 已提交
291 292 293 294 295 296 297
		this.input.select();

		if (range) {
			this.input.setSelectionRange(range.start, range.end);
		}
	}

298 299 300 301
	public isSelectionAtEnd(): boolean {
		return this.input.selectionEnd === this.input.value.length && this.input.selectionStart === this.input.selectionEnd;
	}

E
Erich Gamma 已提交
302 303 304 305 306
	public enable(): void {
		this.input.removeAttribute('disabled');
	}

	public disable(): void {
307
		this.blur();
E
Erich Gamma 已提交
308 309 310 311
		this.input.disabled = true;
		this._hideMessage();
	}

312
	public setEnabled(enabled: boolean): void {
313 314 315 316 317 318 319
		if (enabled) {
			this.enable();
		} else {
			this.disable();
		}
	}

320
	public get width(): number {
E
Erich Gamma 已提交
321 322 323
		return dom.getTotalWidth(this.input);
	}

324
	public set width(width: number) {
325 326 327 328 329 330 331 332 333 334 335 336 337
		if (this.options.flexibleHeight && this.options.flexibleWidth) {
			// textarea with horizontal scrolling
			let horizontalPadding = 0;
			if (this.mirror) {
				const paddingLeft = parseFloat(this.mirror.style.paddingLeft || '') || 0;
				const paddingRight = parseFloat(this.mirror.style.paddingRight || '') || 0;
				horizontalPadding = paddingLeft + paddingRight;
			}
			this.input.style.width = (width - horizontalPadding) + 'px';
		} else {
			this.input.style.width = width + 'px';
		}

338 339 340
		if (this.mirror) {
			this.mirror.style.width = width + 'px';
		}
E
Erich Gamma 已提交
341 342
	}

343 344 345 346 347 348 349 350 351 352 353 354
	public set paddingRight(paddingRight: number) {
		if (this.options.flexibleHeight && this.options.flexibleWidth) {
			this.input.style.width = `calc(100% - ${paddingRight}px)`;
		} else {
			this.input.style.paddingRight = paddingRight + 'px';
		}

		if (this.mirror) {
			this.mirror.style.paddingRight = paddingRight + 'px';
		}
	}

J
Joao Moreno 已提交
355
	private updateScrollDimensions(): void {
356
		if (typeof this.cachedContentHeight !== 'number' || typeof this.cachedHeight !== 'number' || !this.scrollableElement) {
J
Joao Moreno 已提交
357 358 359 360 361 362 363
			return;
		}

		const scrollHeight = this.cachedContentHeight;
		const height = this.cachedHeight;
		const scrollTop = this.input.scrollTop;

364 365
		this.scrollableElement.setScrollDimensions({ scrollHeight, height });
		this.scrollableElement.setScrollPosition({ scrollTop });
366 367
	}

368
	public showMessage(message: IMessage, force?: boolean): void {
E
Erich Gamma 已提交
369 370
		this.message = message;

371 372 373 374 375
		this.element.classList.remove('idle');
		this.element.classList.remove('info');
		this.element.classList.remove('warning');
		this.element.classList.remove('error');
		this.element.classList.add(this.classForType(message.type));
E
Erich Gamma 已提交
376

377
		const styles = this.stylesForType(this.message.type);
378
		this.element.style.border = styles.border ? `1px solid ${styles.border}` : '';
379

E
Erich Gamma 已提交
380 381 382 383 384 385 386 387
		if (this.hasFocus() || force) {
			this._showMessage();
		}
	}

	public hideMessage(): void {
		this.message = null;

388 389 390 391
		this.element.classList.remove('info');
		this.element.classList.remove('warning');
		this.element.classList.remove('error');
		this.element.classList.add('idle');
E
Erich Gamma 已提交
392 393

		this._hideMessage();
394
		this.applyStyles();
E
Erich Gamma 已提交
395 396 397 398 399 400 401
	}

	public isInputValid(): boolean {
		return !!this.validation && !this.validation(this.value);
	}

	public validate(): boolean {
402
		let errorMsg: IMessage | null = null;
E
Erich Gamma 已提交
403 404

		if (this.validation) {
T
Till Salinger 已提交
405
			errorMsg = this.validation(this.value);
E
Erich Gamma 已提交
406

T
Till Salinger 已提交
407 408 409 410 411
			if (errorMsg) {
				this.inputElement.setAttribute('aria-invalid', 'true');
				this.showMessage(errorMsg);
			}
			else if (this.inputElement.hasAttribute('aria-invalid')) {
412
				this.inputElement.removeAttribute('aria-invalid');
E
Erich Gamma 已提交
413 414 415 416
				this.hideMessage();
			}
		}

T
Till Salinger 已提交
417
		return !errorMsg;
E
Erich Gamma 已提交
418 419
	}

420
	public stylesForType(type: MessageType | undefined): { border: Color | undefined; background: Color | undefined; foreground: Color | undefined } {
421
		switch (type) {
422 423 424
			case MessageType.INFO: return { border: this.inputValidationInfoBorder, background: this.inputValidationInfoBackground, foreground: this.inputValidationInfoForeground };
			case MessageType.WARNING: return { border: this.inputValidationWarningBorder, background: this.inputValidationWarningBackground, foreground: this.inputValidationWarningForeground };
			default: return { border: this.inputValidationErrorBorder, background: this.inputValidationErrorBackground, foreground: this.inputValidationErrorForeground };
425 426 427
		}
	}

M
Matt Bierner 已提交
428
	private classForType(type: MessageType | undefined): string {
E
Erich Gamma 已提交
429 430 431 432 433 434 435 436 437 438 439 440
		switch (type) {
			case MessageType.INFO: return 'info';
			case MessageType.WARNING: return 'warning';
			default: return 'error';
		}
	}

	private _showMessage(): void {
		if (!this.contextViewProvider || !this.message) {
			return;
		}

A
Alex Dima 已提交
441 442
		let div: HTMLElement;
		let layout = () => div.style.width = dom.getTotalWidth(this.element) + 'px';
E
Erich Gamma 已提交
443 444 445

		this.contextViewProvider.showContextView({
			getAnchor: () => this.element,
A
Alex Dima 已提交
446
			anchorAlignment: AnchorAlignment.RIGHT,
E
Erich Gamma 已提交
447
			render: (container: HTMLElement) => {
M
Matt Bierner 已提交
448 449 450 451
				if (!this.message) {
					return null;
				}

E
Erich Gamma 已提交
452 453 454
				div = dom.append(container, $('.monaco-inputbox-container'));
				layout();

455
				const renderOptions: MarkdownRenderOptions = {
456
					inline: true,
457
					className: 'monaco-inputbox-message'
E
Erich Gamma 已提交
458 459
				};

460
				const spanElement = (this.message.formatContent
461
					? renderFormattedText(this.message.content, renderOptions)
462
					: renderText(this.message.content, renderOptions));
463
				spanElement.classList.add(this.classForType(this.message.type));
464 465

				const styles = this.stylesForType(this.message.type);
466
				spanElement.style.backgroundColor = styles.background ? styles.background.toString() : '';
M
Matt Bierner 已提交
467
				spanElement.style.color = styles.foreground ? styles.foreground.toString() : '';
468
				spanElement.style.border = styles.border ? `1px solid ${styles.border}` : '';
469

E
Erich Gamma 已提交
470
				dom.append(div, spanElement);
471

E
Erich Gamma 已提交
472 473
				return null;
			},
S
SteVen Batten 已提交
474
			onHide: () => {
S
SteVen Batten 已提交
475
				this.state = 'closed';
S
SteVen Batten 已提交
476
			},
E
Erich Gamma 已提交
477 478
			layout: layout
		});
S
SteVen Batten 已提交
479

480 481 482 483 484 485 486 487 488 489 490 491
		// ARIA Support
		let alertText: string;
		if (this.message.type === MessageType.ERROR) {
			alertText = nls.localize('alertErrorMessage', "Error: {0}", this.message.content);
		} else if (this.message.type === MessageType.WARNING) {
			alertText = nls.localize('alertWarningMessage', "Warning: {0}", this.message.content);
		} else {
			alertText = nls.localize('alertInfoMessage', "Info: {0}", this.message.content);
		}

		aria.alert(alertText);

S
SteVen Batten 已提交
492
		this.state = 'open';
E
Erich Gamma 已提交
493 494 495
	}

	private _hideMessage(): void {
S
SteVen Batten 已提交
496
		if (!this.contextViewProvider) {
E
Erich Gamma 已提交
497 498 499
			return;
		}

S
SteVen Batten 已提交
500 501 502
		if (this.state === 'open') {
			this.contextViewProvider.hideContextView();
		}
E
Erich Gamma 已提交
503

S
SteVen Batten 已提交
504
		this.state = 'idle';
E
Erich Gamma 已提交
505 506 507
	}

	private onValueChange(): void {
A
Alex Dima 已提交
508
		this._onDidChange.fire(this.value);
E
Erich Gamma 已提交
509 510

		this.validate();
J
Joao Moreno 已提交
511
		this.updateMirror();
512
		this.input.classList.toggle('empty', !this.value);
J
Joao Moreno 已提交
513

M
Matt Bierner 已提交
514
		if (this.state === 'open' && this.contextViewProvider) {
J
Joao Moreno 已提交
515 516
			this.contextViewProvider.layout();
		}
E
Erich Gamma 已提交
517 518
	}

J
Joao Moreno 已提交
519 520 521 522 523
	private updateMirror(): void {
		if (!this.mirror) {
			return;
		}

J
Joao Moreno 已提交
524
		const value = this.value;
J
Joao Moreno 已提交
525 526 527 528 529 530 531
		const lastCharCode = value.charCodeAt(value.length - 1);
		const suffix = lastCharCode === 10 ? ' ' : '';
		const mirrorTextContent = value + suffix;

		if (mirrorTextContent) {
			this.mirror.textContent = value + suffix;
		} else {
532
			this.mirror.innerText = '\u00a0';
J
Joao Moreno 已提交
533 534
		}

J
Joao Moreno 已提交
535 536 537
		this.layout();
	}

538
	public style(styles: IInputBoxStyles): void {
539 540
		this.inputBackground = styles.inputBackground;
		this.inputForeground = styles.inputForeground;
B
Benjamin Pasero 已提交
541
		this.inputBorder = styles.inputBorder;
B
Benjamin Pasero 已提交
542

B
Benjamin Pasero 已提交
543
		this.inputValidationInfoBackground = styles.inputValidationInfoBackground;
544
		this.inputValidationInfoForeground = styles.inputValidationInfoForeground;
B
Benjamin Pasero 已提交
545 546
		this.inputValidationInfoBorder = styles.inputValidationInfoBorder;
		this.inputValidationWarningBackground = styles.inputValidationWarningBackground;
547
		this.inputValidationWarningForeground = styles.inputValidationWarningForeground;
B
Benjamin Pasero 已提交
548 549
		this.inputValidationWarningBorder = styles.inputValidationWarningBorder;
		this.inputValidationErrorBackground = styles.inputValidationErrorBackground;
550
		this.inputValidationErrorForeground = styles.inputValidationErrorForeground;
B
Benjamin Pasero 已提交
551
		this.inputValidationErrorBorder = styles.inputValidationErrorBorder;
552

553
		this.applyStyles();
B
Benjamin Pasero 已提交
554 555
	}

556
	protected applyStyles(): void {
557 558 559
		const background = this.inputBackground ? this.inputBackground.toString() : '';
		const foreground = this.inputForeground ? this.inputForeground.toString() : '';
		const border = this.inputBorder ? this.inputBorder.toString() : '';
J
Joao Moreno 已提交
560 561 562

		this.element.style.backgroundColor = background;
		this.element.style.color = foreground;
563
		this.input.style.backgroundColor = 'inherit';
J
Joao Moreno 已提交
564 565
		this.input.style.color = foreground;

566 567
		this.element.style.borderWidth = border ? '1px' : '';
		this.element.style.borderStyle = border ? 'solid' : '';
J
Joao Moreno 已提交
568
		this.element.style.borderColor = border;
B
Benjamin Pasero 已提交
569 570
	}

571
	public layout(): void {
E
Erich Gamma 已提交
572 573 574 575
		if (!this.mirror) {
			return;
		}

J
Joao Moreno 已提交
576 577
		const previousHeight = this.cachedContentHeight;
		this.cachedContentHeight = dom.getTotalHeight(this.mirror);
E
Erich Gamma 已提交
578

J
Joao Moreno 已提交
579 580
		if (previousHeight !== this.cachedContentHeight) {
			this.cachedHeight = Math.min(this.cachedContentHeight, this.maxHeight);
581
			this.input.style.height = this.cachedHeight + 'px';
J
Joao Moreno 已提交
582
			this._onDidHeightChange.fire(this.cachedContentHeight);
583
		}
E
Erich Gamma 已提交
584 585
	}

586 587 588 589 590 591 592 593 594 595 596 597 598
	public insertAtCursor(text: string): void {
		const inputElement = this.inputElement;
		const start = inputElement.selectionStart;
		const end = inputElement.selectionEnd;
		const content = inputElement.value;

		if (start !== null && end !== null) {
			this.value = content.substr(0, start) + text + content.substr(end);
			inputElement.setSelectionRange(start + 1, start + 1);
			this.layout();
		}
	}

E
Erich Gamma 已提交
599 600 601 602
	public dispose(): void {
		this._hideMessage();

		this.message = null;
J
Joao Moreno 已提交
603 604 605 606

		if (this.actionbar) {
			this.actionbar.dispose();
		}
E
Erich Gamma 已提交
607 608 609

		super.dispose();
	}
610 611 612 613 614 615
}

export interface IHistoryInputOptions extends IInputOptions {
	history: string[];
}

616
export class HistoryInputBox extends InputBox implements IHistoryNavigationWidget {
617 618 619

	private readonly history: HistoryNavigator<string>;

M
Matt Bierner 已提交
620
	constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options: IHistoryInputOptions) {
621 622 623 624
		super(container, contextViewProvider, options);
		this.history = new HistoryNavigator<string>(options.history, 100);
	}

S
Sandeep Somavarapu 已提交
625 626 627
	public addToHistory(): void {
		if (this.value && this.value !== this.getCurrentValue()) {
			this.history.add(this.value);
628 629 630 631 632 633 634
		}
	}

	public getHistory(): string[] {
		return this.history.getHistory();
	}

635
	public showNextValue(): void {
S
Sandeep Somavarapu 已提交
636 637 638 639
		if (!this.history.has(this.value)) {
			this.addToHistory();
		}

S
Sandeep Somavarapu 已提交
640 641 642 643 644
		let next = this.getNextValue();
		if (next) {
			next = next === this.value ? this.getNextValue() : next;
		}

645 646
		if (next) {
			this.value = next;
647
			aria.status(this.value);
648 649 650
		}
	}

651
	public showPreviousValue(): void {
S
Sandeep Somavarapu 已提交
652 653
		if (!this.history.has(this.value)) {
			this.addToHistory();
654
		}
S
Sandeep Somavarapu 已提交
655 656 657 658 659 660

		let previous = this.getPreviousValue();
		if (previous) {
			previous = previous === this.value ? this.getPreviousValue() : previous;
		}

661 662
		if (previous) {
			this.value = previous;
663
			aria.status(this.value);
664 665 666 667 668 669
		}
	}

	public clearHistory(): void {
		this.history.clear();
	}
S
Sandeep Somavarapu 已提交
670

M
Matt Bierner 已提交
671
	private getCurrentValue(): string | null {
S
Sandeep Somavarapu 已提交
672 673 674 675 676 677 678 679
		let currentValue = this.history.current();
		if (!currentValue) {
			currentValue = this.history.last();
			this.history.next();
		}
		return currentValue;
	}

M
Matt Bierner 已提交
680
	private getPreviousValue(): string | null {
S
Sandeep Somavarapu 已提交
681 682 683
		return this.history.previous() || this.history.first();
	}

M
Matt Bierner 已提交
684
	private getNextValue(): string | null {
S
Sandeep Somavarapu 已提交
685 686
		return this.history.next() || this.history.last();
	}
687
}