textAreaHandler.ts 20.6 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';
A
Alex Dima 已提交
7
import * as browser from 'vs/base/browser/browser';
A
Alex Dima 已提交
8 9 10
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import * as platform from 'vs/base/common/platform';
11
import * as strings from 'vs/base/common/strings';
J
Johannes Rieken 已提交
12
import { Configuration } from 'vs/editor/browser/config/configuration';
A
Alex Dima 已提交
13 14
import { CopyOptions, ICompositionData, IPasteData, ITextAreaInputHost, TextAreaInput } from 'vs/editor/browser/controller/textAreaInput';
import { ISimpleModel, ITypeData, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/textAreaState';
15
import { ViewController } from 'vs/editor/browser/view/viewController';
A
Alex Dima 已提交
16
import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart';
17
import { LineNumbersOverlay } from 'vs/editor/browser/viewParts/lineNumbers/lineNumbers';
A
Alex Dima 已提交
18
import { Margin } from 'vs/editor/browser/viewParts/margin/margin';
A
renames  
Alex Dima 已提交
19
import { RenderLineNumbersType, EditorOption } from 'vs/editor/common/config/editorOptions';
A
Alex Dima 已提交
20 21 22 23 24 25
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 已提交
26
import { EndOfLinePreference } from 'vs/editor/common/model';
A
Alex Dima 已提交
27 28 29
import { HorizontalRange, RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
30
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
31

32
export interface ITextAreaHandlerHelper {
A
Alex Dima 已提交
33
	visibleRangeForPositionRelativeToEditor(lineNumber: number, column: number): HorizontalRange | null;
34
}
35

36
class VisibleTextAreaData {
37
	_visibleTextAreaBrand: void;
38 39 40

	public readonly top: number;
	public readonly left: number;
41
	public readonly width: number;
42

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

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

54 55
const canUseZeroSizeTextarea = (browser.isEdgeOrIE || browser.isFirefox);

56 57 58
interface LocalClipboardMetadata {
	lastCopiedValue: string;
	isFromEmptySelection: boolean;
A
Alex Dima 已提交
59
	multicursorText: string[] | null;
60 61 62 63 64 65 66 67 68 69
}

/**
 * Every time we write to the clipboard, we record a bit of extra metadata here.
 * Every time we read from the cipboard, if the text matches our last written text,
 * we can fetch the previous metadata.
 */
class LocalClipboardMetadataManager {
	public static INSTANCE = new LocalClipboardMetadataManager();

A
Alex Dima 已提交
70
	private _lastState: LocalClipboardMetadata | null;
71 72 73 74 75

	constructor() {
		this._lastState = null;
	}

A
Alex Dima 已提交
76
	public set(state: LocalClipboardMetadata | null): void {
77 78 79
		this._lastState = state;
	}

A
Alex Dima 已提交
80
	public get(pastedText: string): LocalClipboardMetadata | null {
81 82 83 84 85 86 87 88 89
		if (this._lastState && this._lastState.lastCopiedValue === pastedText) {
			// match!
			return this._lastState;
		}
		this._lastState = null;
		return null;
	}
}

90
export class TextAreaHandler extends ViewPart {
91

92
	private readonly _viewController: ViewController;
93
	private readonly _viewHelper: ITextAreaHandlerHelper;
94
	private _accessibilitySupport: AccessibilitySupport;
95 96
	private _contentLeft: number;
	private _contentWidth: number;
97
	private _contentHeight: number;
98 99
	private _scrollLeft: number;
	private _scrollTop: number;
100 101
	private _fontInfo: BareFontInfo;
	private _lineHeight: number;
102
	private _emptySelectionClipboard: boolean;
103
	private _copyWithSyntaxHighlighting: boolean;
104 105 106 107

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

111 112
	public readonly textArea: FastDomNode<HTMLTextAreaElement>;
	public readonly textAreaCover: FastDomNode<HTMLElement>;
113
	private readonly _textAreaInput: TextAreaInput;
114

115
	constructor(context: ViewContext, viewController: ViewController, viewHelper: ITextAreaHandlerHelper) {
116
		super(context);
E
Erich Gamma 已提交
117

118 119
		this._viewController = viewController;
		this._viewHelper = viewHelper;
120

121
		const conf = this._context.configuration.editor;
A
Alex Dima 已提交
122
		const options = this._context.configuration.options;
A
renames  
Alex Dima 已提交
123
		const layoutInfo = options.get(EditorOption.layoutInfo);
124

A
renames  
Alex Dima 已提交
125
		this._accessibilitySupport = options.get(EditorOption.accessibilitySupport);
A
Alex Dima 已提交
126 127 128
		this._contentLeft = layoutInfo.contentLeft;
		this._contentWidth = layoutInfo.contentWidth;
		this._contentHeight = layoutInfo.contentHeight;
129 130
		this._scrollLeft = 0;
		this._scrollTop = 0;
131 132
		this._fontInfo = conf.fontInfo;
		this._lineHeight = conf.lineHeight;
133
		this._emptySelectionClipboard = conf.emptySelectionClipboard;
134
		this._copyWithSyntaxHighlighting = conf.copyWithSyntaxHighlighting;
135

136
		this._visibleTextArea = null;
137
		this._selections = [new Selection(1, 1, 1, 1)];
A
Alex Dima 已提交
138

139 140 141 142 143 144 145
		// 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');
146
		this.textArea.setAttribute('autocomplete', 'off');
147
		this.textArea.setAttribute('spellcheck', 'false');
A
renames  
Alex Dima 已提交
148
		this.textArea.setAttribute('aria-label', options.get(EditorOption.ariaLabel));
149 150 151 152 153 154 155 156
		this.textArea.setAttribute('role', 'textbox');
		this.textArea.setAttribute('aria-multiline', 'true');
		this.textArea.setAttribute('aria-haspopup', 'false');
		this.textArea.setAttribute('aria-autocomplete', 'both');

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

157 158 159 160 161 162 163 164 165 166 167 168
		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);
			}
		};

169
		const textAreaInputHost: ITextAreaInputHost = {
A
Alex Dima 已提交
170
			getPlainTextToCopy: (): string => {
171
				const rawWhatToCopy = this._context.model.getPlainTextToCopy(this._selections, this._emptySelectionClipboard, platform.isWindows);
172 173 174 175 176 177
				const newLineCharacter = this._context.model.getEOL();

				const isFromEmptySelection = (this._emptySelectionClipboard && this._selections.length === 1 && this._selections[0].isEmpty());
				const multicursorText = (Array.isArray(rawWhatToCopy) ? rawWhatToCopy : null);
				const whatToCopy = (Array.isArray(rawWhatToCopy) ? rawWhatToCopy.join(newLineCharacter) : rawWhatToCopy);

178
				let metadata: LocalClipboardMetadata | null = null;
179 180 181 182 183 184 185 186 187 188 189 190
				if (isFromEmptySelection || multicursorText) {
					// Only store the non-default metadata

					// When writing "LINE\r\n" to the clipboard and then pasting,
					// Firefox pastes "LINE\n", so let's work around this quirk
					const lastCopiedValue = (browser.isFirefox ? whatToCopy.replace(/\r\n/g, '\n') : whatToCopy);
					metadata = {
						lastCopiedValue: lastCopiedValue,
						isFromEmptySelection: (this._emptySelectionClipboard && this._selections.length === 1 && this._selections[0].isEmpty()),
						multicursorText: multicursorText
					};
				}
A
Alex Dima 已提交
191

192
				LocalClipboardMetadataManager.INSTANCE.set(metadata);
A
Alex Dima 已提交
193 194 195

				return whatToCopy;
			},
196

A
Alex Dima 已提交
197
			getHTMLToCopy: (): string | null => {
198
				if (!this._copyWithSyntaxHighlighting && !CopyOptions.forceCopyWithSyntaxHighlighting) {
199 200 201
					return null;
				}

202
				return this._context.model.getHTMLToCopy(this._selections, this._emptySelectionClipboard);
A
Alex Dima 已提交
203
			},
204 205 206 207 208 209 210 211

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

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

212
				if (this._accessibilitySupport === AccessibilitySupport.Disabled) {
213
					// We know for a fact that a screen reader is not attached
214
					// On OSX, we write the character before the cursor to allow for "long-press" composition
215
					// Also on OSX, we write the word before the cursor to allow for the Accessibility Keyboard to give good hints
216 217 218 219
					if (platform.isMacintosh) {
						const selection = this._selections[0];
						if (selection.isEmpty()) {
							const position = selection.getStartPosition();
220 221 222 223 224 225 226 227

							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);
228 229 230
							}
						}
					}
231 232 233
					return TextAreaState.EMPTY;
				}

234
				return PagedScreenReaderStrategy.fromEditorSelection(currentState, simpleModel, this._selections[0], this._accessibilitySupport === AccessibilitySupport.Unknown);
235 236 237 238
			},

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

242
		this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, this.textArea));
243 244 245 246 247 248 249 250 251 252

		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) => {
253 254
			const metadata = LocalClipboardMetadataManager.INSTANCE.get(e.text);

A
Alex Dima 已提交
255
			let pasteOnNewLine = false;
256
			let multicursorText: string[] | null = null;
257 258
			if (metadata) {
				pasteOnNewLine = (this._emptySelectionClipboard && metadata.isFromEmptySelection);
259
				multicursorText = metadata.multicursorText;
A
Alex Dima 已提交
260
			}
261
			this._viewController.paste('keyboard', e.text, pasteOnNewLine, multicursorText);
262 263 264 265
		}));

		this._register(this._textAreaInput.onCut(() => {
			this._viewController.cut('keyboard');
A
Alex Dima 已提交
266
		}));
267 268

		this._register(this._textAreaInput.onType((e: ITypeData) => {
269
			if (e.replaceCharCnt) {
270
				this._viewController.replacePreviousChar('keyboard', e.text, e.replaceCharCnt);
271
			} else {
272
				this._viewController.type('keyboard', e.text);
273 274
			}
		}));
275

276 277 278 279
		this._register(this._textAreaInput.onSelectionChangeRequest((modelSelection: Selection) => {
			this._viewController.setSelection('keyboard', modelSelection);
		}));

280
		this._register(this._textAreaInput.onCompositionStart(() => {
A
Alex Dima 已提交
281 282
			const lineNumber = this._selections[0].startLineNumber;
			const column = this._selections[0].startColumn;
283

284
			this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent(
A
Alex Dima 已提交
285
				new Range(lineNumber, column, lineNumber, column),
286
				viewEvents.VerticalRevealType.Simple,
287 288
				true,
				ScrollType.Immediate
A
Alex Dima 已提交
289
			));
290 291

			// Find range pixel position
292
			const visibleRange = this._viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column);
293 294

			if (visibleRange) {
295 296
				this._visibleTextArea = new VisibleTextAreaData(
					this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber),
297 298 299
					visibleRange.left,
					canUseZeroSizeTextarea ? 0 : 1
				);
300
				this._render();
301 302 303
			}

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

306
			this._viewController.compositionStart('keyboard');
307
		}));
308

309
		this._register(this._textAreaInput.onCompositionUpdate((e: ICompositionData) => {
310 311 312
			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 已提交
313
				this._visibleTextArea = this._visibleTextArea!.setWidth(0);
314 315
			} else {
				// adjust width by its size
A
Alex Dima 已提交
316
				this._visibleTextArea = this._visibleTextArea!.setWidth(measureText(e.data, this._fontInfo));
317
			}
318
			this._render();
319 320
		}));

321
		this._register(this._textAreaInput.onCompositionEnd(() => {
322

323 324
			this._visibleTextArea = null;
			this._render();
325

326
			this.textArea.setClassName('inputarea');
327
			this._viewController.compositionEnd('keyboard');
328
		}));
329

330 331 332 333 334 335 336
		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 已提交
337 338
	}

339
	public dispose(): void {
A
Alex Dima 已提交
340
		super.dispose();
341 342
	}

343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
	private _getWordBeforePosition(position: Position): string {
		const lineContent = this._context.model.getLineContent(position.lineNumber);
		const wordSeparators = getMapForWordSeparators(this._context.configuration.editor.wordSeparators);

		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 '';
	}

372 373
	// --- begin event handlers

A
Alex Dima 已提交
374
	public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
375
		const conf = this._context.configuration.editor;
A
Alex Dima 已提交
376
		const options = this._context.configuration.options;
377

378
		if (e.fontInfo) {
379
			this._fontInfo = conf.fontInfo;
380
		}
A
Alex Dima 已提交
381
		if (e.viewInfo) {
A
renames  
Alex Dima 已提交
382
			this.textArea.setAttribute('aria-label', options.get(EditorOption.ariaLabel));
383
		}
A
renames  
Alex Dima 已提交
384 385
		if (e.hasChanged(EditorOption.layoutInfo)) {
			const layoutInfo = options.get(EditorOption.layoutInfo);
A
Alex Dima 已提交
386 387 388
			this._contentLeft = layoutInfo.contentLeft;
			this._contentWidth = layoutInfo.contentWidth;
			this._contentHeight = layoutInfo.contentHeight;
A
Alex Dima 已提交
389
		}
390
		if (e.lineHeight) {
391
			this._lineHeight = conf.lineHeight;
392
		}
A
renames  
Alex Dima 已提交
393 394
		if (e.hasChanged(EditorOption.accessibilitySupport)) {
			this._accessibilitySupport = options.get(EditorOption.accessibilitySupport);
395 396
			this._textAreaInput.writeScreenReaderContent('strategy changed');
		}
397 398 399
		if (e.emptySelectionClipboard) {
			this._emptySelectionClipboard = conf.emptySelectionClipboard;
		}
400 401
		if (e.copyWithSyntaxHighlighting) {
			this._copyWithSyntaxHighlighting = conf.copyWithSyntaxHighlighting;
402
		}
403

404
		return true;
405
	}
406 407
	public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
		this._selections = e.selections.slice(0);
408
		this._textAreaInput.writeScreenReaderContent('selection changed');
409
		return true;
410
	}
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
	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;
	}
427
	public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
428 429
		this._scrollLeft = e.scrollLeft;
		this._scrollTop = e.scrollTop;
430 431 432
		return true;
	}
	public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
433
		return true;
434 435
	}

436 437
	// --- end event handlers

438 439
	// --- begin view API

A
Alex Dima 已提交
440 441 442 443 444 445 446 447
	public isFocused(): boolean {
		return this._textAreaInput.isFocused();
	}

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

448
	// --- end view API
449

450
	private _primaryCursorVisibleRange: HorizontalRange | null = null;
451

452
	public prepareRender(ctx: RenderingContext): void {
453 454
		const primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn);
		this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(primaryCursorPosition);
455 456 457
	}

	public render(ctx: RestrictedRenderingContext): void {
458
		this._textAreaInput.writeScreenReaderContent('render');
459 460 461 462 463 464
		this._render();
	}

	private _render(): void {
		if (this._visibleTextArea) {
			// The text area is visible for composition reasons
465 466 467 468
			this._renderInsideEditor(
				this._visibleTextArea.top - this._scrollTop,
				this._contentLeft + this._visibleTextArea.left - this._scrollLeft,
				this._visibleTextArea.width,
469 470
				this._lineHeight,
				true
471 472 473
			);
			return;
		}
474

475 476 477 478 479
		if (!this._primaryCursorVisibleRange) {
			// The primary cursor is outside the viewport => place textarea to the top left
			this._renderAtTopLeft();
			return;
		}
480

481 482 483 484 485 486 487
		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;
		}

488
		const top = this._context.viewLayout.getVerticalOffsetForLineNumber(this._selections[0].positionLineNumber) - this._scrollTop;
489 490 491 492 493 494 495
		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
496 497 498 499 500
		this._renderInsideEditor(
			top, left,
			canUseZeroSizeTextarea ? 0 : 1, canUseZeroSizeTextarea ? 0 : 1,
			false
		);
501 502
	}

503
	private _renderInsideEditor(top: number, left: number, width: number, height: number, useEditorFont: boolean): void {
504 505 506
		const ta = this.textArea;
		const tac = this.textAreaCover;

507 508 509 510
		if (useEditorFont) {
			Configuration.applyFontInfo(ta, this._fontInfo);
		} else {
			ta.setFontSize(1);
511
			ta.setLineHeight(this._fontInfo.lineHeight);
512 513
		}

514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
		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 {
		const ta = this.textArea;
		const tac = this.textAreaCover;

529
		Configuration.applyFontInfo(ta, this._fontInfo);
530 531 532 533 534 535 536 537 538 539 540 541
		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;
		}
542

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

546 547 548 549 550
		ta.setWidth(1);
		ta.setHeight(1);
		tac.setWidth(1);
		tac.setHeight(1);

A
Alex Dima 已提交
551 552
		const options = this._context.configuration.options;

553
		if (this._context.configuration.editor.viewInfo.glyphMargin) {
A
Alex Dima 已提交
554
			tac.setClassName('monaco-editor-background textAreaCover ' + Margin.OUTER_CLASS_NAME);
555
		} else {
A
renames  
Alex Dima 已提交
556
			const renderLineNumbers = options.get(EditorOption.renderLineNumbers);
A
Alex Dima 已提交
557
			if (renderLineNumbers.renderType !== RenderLineNumbersType.Off) {
558
				tac.setClassName('monaco-editor-background textAreaCover ' + LineNumbersOverlay.CLASS_NAME);
559
			} else {
560
				tac.setClassName('monaco-editor-background textAreaCover');
561 562 563
			}
		}
	}
564
}
A
Alex Dima 已提交
565 566 567 568

function measureText(text: string, fontInfo: BareFontInfo): number {
	// adjust width by its size
	const canvasElem = <HTMLCanvasElement>document.createElement('canvas');
A
Alex Dima 已提交
569
	const context = canvasElem.getContext('2d')!;
A
Alex Dima 已提交
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
	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}`;
}