textAreaHandler.ts 22.3 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.
 *--------------------------------------------------------------------------------------------*/

6
import 'vs/css!./textAreaHandler';
7
import * as nls from 'vs/nls';
A
Alex Dima 已提交
8
import * as browser from 'vs/base/browser/browser';
A
Alex Dima 已提交
9 10 11
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import * as platform from 'vs/base/common/platform';
12
import * as strings from 'vs/base/common/strings';
J
Johannes Rieken 已提交
13
import { Configuration } from 'vs/editor/browser/config/configuration';
14
import { CopyOptions, ICompositionData, IPasteData, ITextAreaInputHost, TextAreaInput, ClipboardDataToCopy } from 'vs/editor/browser/controller/textAreaInput';
A
Alex Dima 已提交
15
import { ISimpleModel, ITypeData, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/textAreaState';
16
import { ViewController } from 'vs/editor/browser/view/viewController';
A
Alex Dima 已提交
17
import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart';
18
import { LineNumbersOverlay } from 'vs/editor/browser/viewParts/lineNumbers/lineNumbers';
A
Alex Dima 已提交
19
import { Margin } from 'vs/editor/browser/viewParts/margin/margin';
20
import { RenderLineNumbersType, EditorOption, IComputedEditorOptions, EditorOptions } from 'vs/editor/common/config/editorOptions';
A
Alex Dima 已提交
21 22 23 24 25 26
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
import { WordCharacterClass, getMapForWordSeparators } from 'vs/editor/common/controller/wordCharacterClassifier';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { ScrollType } from 'vs/editor/common/editorCommon';
A
Alex Dima 已提交
27
import { EndOfLinePreference } from 'vs/editor/common/model';
28
import { RenderingContext, RestrictedRenderingContext, HorizontalPosition } from 'vs/editor/common/view/renderingContext';
A
Alex Dima 已提交
29 30
import { ViewContext } from 'vs/editor/common/view/viewContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
31
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
I
isidor 已提交
32
import { IEditorAriaOptions } from 'vs/editor/browser/editorBrowser';
33

34
export interface ITextAreaHandlerHelper {
35
	visibleRangeForPositionRelativeToEditor(lineNumber: number, column: number): HorizontalPosition | null;
36
}
37

38
class VisibleTextAreaData {
39
	_visibleTextAreaBrand: void;
40 41 42

	public readonly top: number;
	public readonly left: number;
43
	public readonly width: number;
44

45
	constructor(top: number, left: number, width: number) {
46 47
		this.top = top;
		this.left = left;
48 49 50
		this.width = width;
	}

51 52
	public setWidth(width: number): VisibleTextAreaData {
		return new VisibleTextAreaData(this.top, this.left, width);
53 54
	}
}
55

56 57 58
const canUseZeroSizeTextarea = (browser.isEdgeOrIE || browser.isFirefox);

export class TextAreaHandler extends ViewPart {
59

60
	private readonly _viewController: ViewController;
61
	private readonly _viewHelper: ITextAreaHandlerHelper;
A
Alex Dima 已提交
62 63 64
	private _scrollLeft: number;
	private _scrollTop: number;

65 66
	private _accessibilitySupport!: AccessibilitySupport;
	private _accessibilityPageSize!: number;
67 68
	private _contentLeft: number;
	private _contentWidth: number;
69
	private _contentHeight: number;
70 71
	private _fontInfo: BareFontInfo;
	private _lineHeight: number;
72
	private _emptySelectionClipboard: boolean;
73
	private _copyWithSyntaxHighlighting: boolean;
74 75 76 77

	/**
	 * Defined only when the text area is visible (composition case).
	 */
A
Alex Dima 已提交
78
	private _visibleTextArea: VisibleTextAreaData | null;
79
	private _selections: Selection[];
80
	private _modelSelections: Selection[];
A
Alex Dima 已提交
81

82 83 84 85 86 87
	/**
	 * The position at which the textarea was rendered.
	 * This is useful for hit-testing and determining the mouse position.
	 */
	private _lastRenderPosition: Position | null;

88 89
	public readonly textArea: FastDomNode<HTMLTextAreaElement>;
	public readonly textAreaCover: FastDomNode<HTMLElement>;
90
	private readonly _textAreaInput: TextAreaInput;
91

92
	constructor(context: ViewContext, viewController: ViewController, viewHelper: ITextAreaHandlerHelper) {
93
		super(context);
E
Erich Gamma 已提交
94

95 96
		this._viewController = viewController;
		this._viewHelper = viewHelper;
A
Alex Dima 已提交
97 98
		this._scrollLeft = 0;
		this._scrollTop = 0;
99

A
Alex Dima 已提交
100
		const options = this._context.configuration.options;
A
renames  
Alex Dima 已提交
101
		const layoutInfo = options.get(EditorOption.layoutInfo);
102

103
		this._setAccessibilityOptions(options);
A
Alex Dima 已提交
104 105
		this._contentLeft = layoutInfo.contentLeft;
		this._contentWidth = layoutInfo.contentWidth;
106
		this._contentHeight = layoutInfo.height;
107
		this._fontInfo = options.get(EditorOption.fontInfo);
A
Alex Dima 已提交
108
		this._lineHeight = options.get(EditorOption.lineHeight);
A
Alex Dima 已提交
109 110
		this._emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard);
		this._copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting);
111

112
		this._visibleTextArea = null;
113
		this._selections = [new Selection(1, 1, 1, 1)];
114
		this._modelSelections = [new Selection(1, 1, 1, 1)];
115
		this._lastRenderPosition = null;
A
Alex Dima 已提交
116

117 118 119 120 121 122 123
		// Text Area (The focus will always be in the textarea when the cursor is blinking)
		this.textArea = createFastDomNode(document.createElement('textarea'));
		PartFingerprints.write(this.textArea, PartFingerprint.TextArea);
		this.textArea.setClassName('inputarea');
		this.textArea.setAttribute('wrap', 'off');
		this.textArea.setAttribute('autocorrect', 'off');
		this.textArea.setAttribute('autocapitalize', 'off');
124
		this.textArea.setAttribute('autocomplete', 'off');
125
		this.textArea.setAttribute('spellcheck', 'false');
126
		this.textArea.setAttribute('aria-label', this._getAriaLabel(options));
127 128 129 130 131
		this.textArea.setAttribute('role', 'textbox');
		this.textArea.setAttribute('aria-multiline', 'true');
		this.textArea.setAttribute('aria-haspopup', 'false');
		this.textArea.setAttribute('aria-autocomplete', 'both');

132
		if (platform.isWeb && options.get(EditorOption.readOnly)) {
133 134 135
			this.textArea.setAttribute('readonly', 'true');
		}

136 137 138
		this.textAreaCover = createFastDomNode(document.createElement('div'));
		this.textAreaCover.setPosition('absolute');

139 140 141 142 143 144 145 146 147 148 149 150
		const simpleModel: ISimpleModel = {
			getLineCount: (): number => {
				return this._context.model.getLineCount();
			},
			getLineMaxColumn: (lineNumber: number): number => {
				return this._context.model.getLineMaxColumn(lineNumber);
			},
			getValueInRange: (range: Range, eol: EndOfLinePreference): string => {
				return this._context.model.getValueInRange(range, eol);
			}
		};

151
		const textAreaInputHost: ITextAreaInputHost = {
152
			getDataToCopy: (generateHTML: boolean): ClipboardDataToCopy => {
153
				const rawTextToCopy = this._context.model.getPlainTextToCopy(this._modelSelections, this._emptySelectionClipboard, platform.isWindows);
154 155
				const newLineCharacter = this._context.model.getEOL();

156
				const isFromEmptySelection = (this._emptySelectionClipboard && this._modelSelections.length === 1 && this._modelSelections[0].isEmpty());
157 158
				const multicursorText = (Array.isArray(rawTextToCopy) ? rawTextToCopy : null);
				const text = (Array.isArray(rawTextToCopy) ? rawTextToCopy.join(newLineCharacter) : rawTextToCopy);
A
Alex Dima 已提交
159

160
				let html: string | null | undefined = undefined;
161
				let mode: string | null = null;
162 163
				if (generateHTML) {
					if (CopyOptions.forceCopyWithSyntaxHighlighting || (this._copyWithSyntaxHighlighting && text.length < 65536)) {
164 165 166 167 168
						const richText = this._context.model.getRichTextToCopy(this._modelSelections, this._emptySelectionClipboard);
						if (richText) {
							html = richText.html;
							mode = richText.mode;
						}
169
					}
170
				}
171 172 173 174
				return {
					isFromEmptySelection,
					multicursorText,
					text,
175 176
					html,
					mode
177
				};
A
Alex Dima 已提交
178
			},
179 180 181 182 183 184 185 186

			getScreenReaderContent: (currentState: TextAreaState): TextAreaState => {

				if (browser.isIPad) {
					// Do not place anything in the textarea for the iPad
					return TextAreaState.EMPTY;
				}

187
				if (this._accessibilitySupport === AccessibilitySupport.Disabled) {
188
					// We know for a fact that a screen reader is not attached
189
					// On OSX, we write the character before the cursor to allow for "long-press" composition
190
					// Also on OSX, we write the word before the cursor to allow for the Accessibility Keyboard to give good hints
191 192 193 194
					if (platform.isMacintosh) {
						const selection = this._selections[0];
						if (selection.isEmpty()) {
							const position = selection.getStartPosition();
195 196 197 198 199 200 201 202

							let textBefore = this._getWordBeforePosition(position);
							if (textBefore.length === 0) {
								textBefore = this._getCharacterBeforePosition(position);
							}

							if (textBefore.length > 0) {
								return new TextAreaState(textBefore, textBefore.length, textBefore.length, position, position);
203 204 205
							}
						}
					}
206 207 208
					return TextAreaState.EMPTY;
				}

I
isidor 已提交
209
				return PagedScreenReaderStrategy.fromEditorSelection(currentState, simpleModel, this._selections[0], this._accessibilityPageSize, this._accessibilitySupport === AccessibilitySupport.Unknown);
210 211 212 213
			},

			deduceModelPosition: (viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position => {
				return this._context.model.deduceModelPositionRelativeToViewPosition(viewAnchorPosition, deltaOffset, lineFeedCnt);
A
Alex Dima 已提交
214 215
			}
		};
216

217
		this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, this.textArea));
218 219 220 221 222 223 224 225 226 227

		this._register(this._textAreaInput.onKeyDown((e: IKeyboardEvent) => {
			this._viewController.emitKeyDown(e);
		}));

		this._register(this._textAreaInput.onKeyUp((e: IKeyboardEvent) => {
			this._viewController.emitKeyUp(e);
		}));

		this._register(this._textAreaInput.onPaste((e: IPasteData) => {
A
Alex Dima 已提交
228
			let pasteOnNewLine = false;
229
			let multicursorText: string[] | null = null;
230
			let mode: string | null = null;
231 232 233
			if (e.metadata) {
				pasteOnNewLine = (this._emptySelectionClipboard && !!e.metadata.isFromEmptySelection);
				multicursorText = (typeof e.metadata.multicursorText !== 'undefined' ? e.metadata.multicursorText : null);
234
				mode = e.metadata.mode;
A
Alex Dima 已提交
235
			}
236
			this._viewController.paste('keyboard', e.text, pasteOnNewLine, multicursorText, mode);
237 238 239 240
		}));

		this._register(this._textAreaInput.onCut(() => {
			this._viewController.cut('keyboard');
A
Alex Dima 已提交
241
		}));
242 243

		this._register(this._textAreaInput.onType((e: ITypeData) => {
244
			if (e.replaceCharCnt) {
245
				this._viewController.replacePreviousChar('keyboard', e.text, e.replaceCharCnt);
246
			} else {
247
				this._viewController.type('keyboard', e.text);
248 249
			}
		}));
250

251 252 253 254
		this._register(this._textAreaInput.onSelectionChangeRequest((modelSelection: Selection) => {
			this._viewController.setSelection('keyboard', modelSelection);
		}));

255
		this._register(this._textAreaInput.onCompositionStart(() => {
A
Alex Dima 已提交
256 257
			const lineNumber = this._selections[0].startLineNumber;
			const column = this._selections[0].startColumn;
258

259
			this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent(
260
				'keyboard',
A
Alex Dima 已提交
261
				new Range(lineNumber, column, lineNumber, column),
262
				viewEvents.VerticalRevealType.Simple,
263 264
				true,
				ScrollType.Immediate
A
Alex Dima 已提交
265
			));
266 267

			// Find range pixel position
268
			const visibleRange = this._viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column);
269 270

			if (visibleRange) {
271 272
				this._visibleTextArea = new VisibleTextAreaData(
					this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber),
273 274 275
					visibleRange.left,
					canUseZeroSizeTextarea ? 0 : 1
				);
276
				this._render();
277 278 279
			}

			// Show the textarea
280
			this.textArea.setClassName('inputarea ime-input');
281

282
			this._viewController.compositionStart('keyboard');
283
		}));
284

285
		this._register(this._textAreaInput.onCompositionUpdate((e: ICompositionData) => {
286 287 288
			if (browser.isEdgeOrIE) {
				// Due to isEdgeOrIE (where the textarea was not cleared initially)
				// we cannot assume the text consists only of the composited text
A
Alex Dima 已提交
289
				this._visibleTextArea = this._visibleTextArea!.setWidth(0);
290 291
			} else {
				// adjust width by its size
A
Alex Dima 已提交
292
				this._visibleTextArea = this._visibleTextArea!.setWidth(measureText(e.data, this._fontInfo));
293
			}
294
			this._render();
295 296
		}));

297
		this._register(this._textAreaInput.onCompositionEnd(() => {
298

299 300
			this._visibleTextArea = null;
			this._render();
301

302
			this.textArea.setClassName('inputarea');
303
			this._viewController.compositionEnd('keyboard');
304
		}));
305

306 307 308 309 310 311 312
		this._register(this._textAreaInput.onFocus(() => {
			this._context.privateViewEventBus.emit(new viewEvents.ViewFocusChangedEvent(true));
		}));

		this._register(this._textAreaInput.onBlur(() => {
			this._context.privateViewEventBus.emit(new viewEvents.ViewFocusChangedEvent(false));
		}));
E
Erich Gamma 已提交
313 314
	}

315
	public dispose(): void {
A
Alex Dima 已提交
316
		super.dispose();
317 318
	}

319 320
	private _getWordBeforePosition(position: Position): string {
		const lineContent = this._context.model.getLineContent(position.lineNumber);
A
Alex Dima 已提交
321
		const wordSeparators = getMapForWordSeparators(this._context.configuration.options.get(EditorOption.wordSeparators));
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347

		let column = position.column;
		let distance = 0;
		while (column > 1) {
			const charCode = lineContent.charCodeAt(column - 2);
			const charClass = wordSeparators.get(charCode);
			if (charClass !== WordCharacterClass.Regular || distance > 50) {
				return lineContent.substring(column - 1, position.column - 1);
			}
			distance++;
			column--;
		}
		return lineContent.substring(0, position.column - 1);
	}

	private _getCharacterBeforePosition(position: Position): string {
		if (position.column > 1) {
			const lineContent = this._context.model.getLineContent(position.lineNumber);
			const charBefore = lineContent.charAt(position.column - 2);
			if (!strings.isHighSurrogate(charBefore.charCodeAt(0))) {
				return charBefore;
			}
		}
		return '';
	}

348 349 350 351 352 353 354 355
	private _getAriaLabel(options: IComputedEditorOptions): string {
		const accessibilitySupport = options.get(EditorOption.accessibilitySupport);
		if (accessibilitySupport === AccessibilitySupport.Disabled) {
			return nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press Alt+F1 for options.");
		}
		return options.get(EditorOption.ariaLabel);
	}

356 357 358 359
	private _setAccessibilityOptions(options: IComputedEditorOptions): void {
		this._accessibilitySupport = options.get(EditorOption.accessibilitySupport);
		const accessibilityPageSize = options.get(EditorOption.accessibilityPageSize);
		if (this._accessibilitySupport === AccessibilitySupport.Enabled && accessibilityPageSize === EditorOptions.accessibilityPageSize.defaultValue) {
I
isidor 已提交
360 361 362
			// If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 160 for a better experience
			// If we put more than 160 lines the nvda can not handle this https://github.com/microsoft/vscode/issues/89717
			this._accessibilityPageSize = 160;
363 364 365 366 367
		} else {
			this._accessibilityPageSize = accessibilityPageSize;
		}
	}

368 369
	// --- begin event handlers

A
Alex Dima 已提交
370
	public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
A
Alex Dima 已提交
371
		const options = this._context.configuration.options;
A
Alex Dima 已提交
372 373
		const layoutInfo = options.get(EditorOption.layoutInfo);

374
		this._setAccessibilityOptions(options);
A
Alex Dima 已提交
375 376
		this._contentLeft = layoutInfo.contentLeft;
		this._contentWidth = layoutInfo.contentWidth;
377
		this._contentHeight = layoutInfo.height;
378
		this._fontInfo = options.get(EditorOption.fontInfo);
A
Alex Dima 已提交
379
		this._lineHeight = options.get(EditorOption.lineHeight);
A
Alex Dima 已提交
380 381
		this._emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard);
		this._copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting);
382
		this.textArea.setAttribute('aria-label', this._getAriaLabel(options));
383

384 385 386 387 388 389
		if (platform.isWeb && e.hasChanged(EditorOption.readOnly)) {
			if (options.get(EditorOption.readOnly)) {
				this.textArea.setAttribute('readonly', 'true');
			} else {
				this.textArea.removeAttribute('readonly');
			}
390 391
		}

A
renames  
Alex Dima 已提交
392
		if (e.hasChanged(EditorOption.accessibilitySupport)) {
393 394
			this._textAreaInput.writeScreenReaderContent('strategy changed');
		}
395

396
		return true;
397
	}
398 399
	public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
		this._selections = e.selections.slice(0);
400
		this._modelSelections = e.modelSelections.slice(0);
401
		this._textAreaInput.writeScreenReaderContent('selection changed');
402
		return true;
403
	}
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
	public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
		// true for inline decorations that can end up relayouting text
		return true;
	}
	public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
		return true;
	}
	public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
		return true;
	}
	public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
		return true;
	}
	public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
		return true;
	}
420
	public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
421 422
		this._scrollLeft = e.scrollLeft;
		this._scrollTop = e.scrollTop;
423 424 425
		return true;
	}
	public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
426
		return true;
427 428
	}

429 430
	// --- end event handlers

431 432
	// --- begin view API

A
Alex Dima 已提交
433 434 435 436 437 438 439 440
	public isFocused(): boolean {
		return this._textAreaInput.isFocused();
	}

	public focusTextArea(): void {
		this._textAreaInput.focusTextArea();
	}

441 442 443 444
	public refreshFocusState() {
		this._textAreaInput.refreshFocusState();
	}

445 446 447 448
	public getLastRenderData(): Position | null {
		return this._lastRenderPosition;
	}

I
isidor 已提交
449
	public setAriaOptions(options: IEditorAriaOptions): void {
I
isidor 已提交
450
		if (options.activeDescendant) {
I
isidor 已提交
451 452
			this.textArea.setAttribute('aria-haspopup', 'true');
			this.textArea.setAttribute('aria-autocomplete', 'list');
I
isidor 已提交
453
			this.textArea.setAttribute('aria-activedescendant', options.activeDescendant);
I
isidor 已提交
454 455 456 457 458 459 460
		} else {
			this.textArea.setAttribute('aria-haspopup', 'false');
			this.textArea.setAttribute('aria-autocomplete', 'both');
			this.textArea.removeAttribute('aria-activedescendant');
		}
	}

461
	// --- end view API
462

463
	private _primaryCursorPosition: Position = new Position(1, 1);
464
	private _primaryCursorVisibleRange: HorizontalPosition | null = null;
465

466
	public prepareRender(ctx: RenderingContext): void {
467 468
		this._primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn);
		this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(this._primaryCursorPosition);
469 470 471
	}

	public render(ctx: RestrictedRenderingContext): void {
472
		this._textAreaInput.writeScreenReaderContent('render');
473 474 475 476 477 478
		this._render();
	}

	private _render(): void {
		if (this._visibleTextArea) {
			// The text area is visible for composition reasons
479
			this._renderInsideEditor(
480
				null,
481 482 483
				this._visibleTextArea.top - this._scrollTop,
				this._contentLeft + this._visibleTextArea.left - this._scrollLeft,
				this._visibleTextArea.width,
484
				this._lineHeight
485 486 487
			);
			return;
		}
488

489 490 491 492 493
		if (!this._primaryCursorVisibleRange) {
			// The primary cursor is outside the viewport => place textarea to the top left
			this._renderAtTopLeft();
			return;
		}
494

495 496 497 498 499 500 501
		const left = this._contentLeft + this._primaryCursorVisibleRange.left - this._scrollLeft;
		if (left < this._contentLeft || left > this._contentLeft + this._contentWidth) {
			// cursor is outside the viewport
			this._renderAtTopLeft();
			return;
		}

502
		const top = this._context.viewLayout.getVerticalOffsetForLineNumber(this._selections[0].positionLineNumber) - this._scrollTop;
503 504 505 506 507 508 509
		if (top < 0 || top > this._contentHeight) {
			// cursor is outside the viewport
			this._renderAtTopLeft();
			return;
		}

		// The primary cursor is in the viewport (at least vertically) => place textarea on the cursor
510 511 512 513 514

		if (platform.isMacintosh) {
			// For the popup emoji input, we will make the text area as high as the line height
			// We will also make the fontSize and lineHeight the correct dimensions to help with the placement of these pickers
			this._renderInsideEditor(
515
				this._primaryCursorPosition,
516
				top, left,
517
				canUseZeroSizeTextarea ? 0 : 1, this._lineHeight
518 519 520 521
			);
			return;
		}

522
		this._renderInsideEditor(
523
			this._primaryCursorPosition,
524
			top, left,
525
			canUseZeroSizeTextarea ? 0 : 1, canUseZeroSizeTextarea ? 0 : 1
526
		);
527 528
	}

529 530
	private _renderInsideEditor(renderedPosition: Position | null, top: number, left: number, width: number, height: number): void {
		this._lastRenderPosition = renderedPosition;
531 532 533
		const ta = this.textArea;
		const tac = this.textAreaCover;

534
		Configuration.applyFontInfo(ta, this._fontInfo);
535

536 537 538 539 540 541 542 543 544 545 546 547
		ta.setTop(top);
		ta.setLeft(left);
		ta.setWidth(width);
		ta.setHeight(height);

		tac.setTop(0);
		tac.setLeft(0);
		tac.setWidth(0);
		tac.setHeight(0);
	}

	private _renderAtTopLeft(): void {
548
		this._lastRenderPosition = null;
549 550 551
		const ta = this.textArea;
		const tac = this.textAreaCover;

552
		Configuration.applyFontInfo(ta, this._fontInfo);
553 554 555 556 557 558 559 560 561 562 563 564
		ta.setTop(0);
		ta.setLeft(0);
		tac.setTop(0);
		tac.setLeft(0);

		if (canUseZeroSizeTextarea) {
			ta.setWidth(0);
			ta.setHeight(0);
			tac.setWidth(0);
			tac.setHeight(0);
			return;
		}
565

566
		// (in WebKit the textarea is 1px by 1px because it cannot handle input to a 0x0 textarea)
P
Paul.K.Zhang 已提交
567
		// specifically, when doing Korean IME, setting the textarea to 0x0 breaks IME badly.
568

569 570 571 572 573
		ta.setWidth(1);
		ta.setHeight(1);
		tac.setWidth(1);
		tac.setHeight(1);

A
Alex Dima 已提交
574 575
		const options = this._context.configuration.options;

A
Alex Dima 已提交
576
		if (options.get(EditorOption.glyphMargin)) {
A
Alex Dima 已提交
577
			tac.setClassName('monaco-editor-background textAreaCover ' + Margin.OUTER_CLASS_NAME);
578
		} else {
A
Alex Dima 已提交
579
			if (options.get(EditorOption.lineNumbers).renderType !== RenderLineNumbersType.Off) {
580
				tac.setClassName('monaco-editor-background textAreaCover ' + LineNumbersOverlay.CLASS_NAME);
581
			} else {
582
				tac.setClassName('monaco-editor-background textAreaCover');
583 584 585
			}
		}
	}
586
}
A
Alex Dima 已提交
587 588 589 590

function measureText(text: string, fontInfo: BareFontInfo): number {
	// adjust width by its size
	const canvasElem = <HTMLCanvasElement>document.createElement('canvas');
A
Alex Dima 已提交
591
	const context = canvasElem.getContext('2d')!;
A
Alex Dima 已提交
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
	context.font = createFontString(fontInfo);
	const metrics = context.measureText(text);

	if (browser.isFirefox) {
		return metrics.width + 2; // +2 for Japanese...
	} else {
		return metrics.width;
	}
}

function createFontString(bareFontInfo: BareFontInfo): string {
	return doCreateFontString('normal', bareFontInfo.fontWeight, bareFontInfo.fontSize, bareFontInfo.lineHeight, bareFontInfo.fontFamily);
}

function doCreateFontString(fontStyle: string, fontWeight: string, fontSize: number, lineHeight: number, fontFamily: string): string {
	// The full font syntax is:
	// style | variant | weight | stretch | size/line-height | fontFamily
	// (https://developer.mozilla.org/en-US/docs/Web/CSS/font)
	// But it appears Edge and IE11 cannot properly parse `stretch`.
	return `${fontStyle} normal ${fontWeight} ${fontSize}px / ${lineHeight}px ${fontFamily}`;
}