textAreaHandler.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import './textAreaHandler.css';
  6. import * as nls from '../../../nls.js';
  7. import * as browser from '../../../base/browser/browser.js';
  8. import { createFastDomNode } from '../../../base/browser/fastDomNode.js';
  9. import * as platform from '../../../base/common/platform.js';
  10. import * as strings from '../../../base/common/strings.js';
  11. import { Configuration } from '../config/configuration.js';
  12. import { CopyOptions, TextAreaInput, TextAreaWrapper } from './textAreaInput.js';
  13. import { PagedScreenReaderStrategy, TextAreaState, _debugComposition } from './textAreaState.js';
  14. import { PartFingerprints, ViewPart } from '../view/viewPart.js';
  15. import { LineNumbersOverlay } from '../viewParts/lineNumbers/lineNumbers.js';
  16. import { Margin } from '../viewParts/margin/margin.js';
  17. import { EditorOptions } from '../../common/config/editorOptions.js';
  18. import { getMapForWordSeparators } from '../../common/controller/wordCharacterClassifier.js';
  19. import { Position } from '../../common/core/position.js';
  20. import { Range } from '../../common/core/range.js';
  21. import { Selection } from '../../common/core/selection.js';
  22. import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from '../../../base/browser/ui/mouseCursor/mouseCursor.js';
  23. class VisibleTextAreaData {
  24. constructor(top, left, width) {
  25. this._visibleTextAreaBrand = undefined;
  26. this.top = top;
  27. this.left = left;
  28. this.width = width;
  29. }
  30. setWidth(width) {
  31. return new VisibleTextAreaData(this.top, this.left, width);
  32. }
  33. }
  34. const canUseZeroSizeTextarea = (browser.isFirefox);
  35. export class TextAreaHandler extends ViewPart {
  36. constructor(context, viewController, viewHelper) {
  37. super(context);
  38. // --- end view API
  39. this._primaryCursorPosition = new Position(1, 1);
  40. this._primaryCursorVisibleRange = null;
  41. this._viewController = viewController;
  42. this._viewHelper = viewHelper;
  43. this._scrollLeft = 0;
  44. this._scrollTop = 0;
  45. const options = this._context.configuration.options;
  46. const layoutInfo = options.get(130 /* layoutInfo */);
  47. this._setAccessibilityOptions(options);
  48. this._contentLeft = layoutInfo.contentLeft;
  49. this._contentWidth = layoutInfo.contentWidth;
  50. this._contentHeight = layoutInfo.height;
  51. this._fontInfo = options.get(43 /* fontInfo */);
  52. this._lineHeight = options.get(58 /* lineHeight */);
  53. this._emptySelectionClipboard = options.get(32 /* emptySelectionClipboard */);
  54. this._copyWithSyntaxHighlighting = options.get(21 /* copyWithSyntaxHighlighting */);
  55. this._visibleTextArea = null;
  56. this._selections = [new Selection(1, 1, 1, 1)];
  57. this._modelSelections = [new Selection(1, 1, 1, 1)];
  58. this._lastRenderPosition = null;
  59. // Text Area (The focus will always be in the textarea when the cursor is blinking)
  60. this.textArea = createFastDomNode(document.createElement('textarea'));
  61. PartFingerprints.write(this.textArea, 6 /* TextArea */);
  62. this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
  63. this.textArea.setAttribute('wrap', 'off');
  64. this.textArea.setAttribute('autocorrect', 'off');
  65. this.textArea.setAttribute('autocapitalize', 'off');
  66. this.textArea.setAttribute('autocomplete', 'off');
  67. this.textArea.setAttribute('spellcheck', 'false');
  68. this.textArea.setAttribute('aria-label', this._getAriaLabel(options));
  69. this.textArea.setAttribute('tabindex', String(options.get(111 /* tabIndex */)));
  70. this.textArea.setAttribute('role', 'textbox');
  71. this.textArea.setAttribute('aria-roledescription', nls.localize('editor', "editor"));
  72. this.textArea.setAttribute('aria-multiline', 'true');
  73. this.textArea.setAttribute('aria-haspopup', 'false');
  74. this.textArea.setAttribute('aria-autocomplete', 'both');
  75. if (options.get(30 /* domReadOnly */) && options.get(80 /* readOnly */)) {
  76. this.textArea.setAttribute('readonly', 'true');
  77. }
  78. this.textAreaCover = createFastDomNode(document.createElement('div'));
  79. this.textAreaCover.setPosition('absolute');
  80. const simpleModel = {
  81. getLineCount: () => {
  82. return this._context.model.getLineCount();
  83. },
  84. getLineMaxColumn: (lineNumber) => {
  85. return this._context.model.getLineMaxColumn(lineNumber);
  86. },
  87. getValueInRange: (range, eol) => {
  88. return this._context.model.getValueInRange(range, eol);
  89. }
  90. };
  91. const textAreaInputHost = {
  92. getDataToCopy: (generateHTML) => {
  93. const rawTextToCopy = this._context.model.getPlainTextToCopy(this._modelSelections, this._emptySelectionClipboard, platform.isWindows);
  94. const newLineCharacter = this._context.model.getEOL();
  95. const isFromEmptySelection = (this._emptySelectionClipboard && this._modelSelections.length === 1 && this._modelSelections[0].isEmpty());
  96. const multicursorText = (Array.isArray(rawTextToCopy) ? rawTextToCopy : null);
  97. const text = (Array.isArray(rawTextToCopy) ? rawTextToCopy.join(newLineCharacter) : rawTextToCopy);
  98. let html = undefined;
  99. let mode = null;
  100. if (generateHTML) {
  101. if (CopyOptions.forceCopyWithSyntaxHighlighting || (this._copyWithSyntaxHighlighting && text.length < 65536)) {
  102. const richText = this._context.model.getRichTextToCopy(this._modelSelections, this._emptySelectionClipboard);
  103. if (richText) {
  104. html = richText.html;
  105. mode = richText.mode;
  106. }
  107. }
  108. }
  109. return {
  110. isFromEmptySelection,
  111. multicursorText,
  112. text,
  113. html,
  114. mode
  115. };
  116. },
  117. getScreenReaderContent: (currentState) => {
  118. if (this._accessibilitySupport === 1 /* Disabled */) {
  119. // We know for a fact that a screen reader is not attached
  120. // On OSX, we write the character before the cursor to allow for "long-press" composition
  121. // Also on OSX, we write the word before the cursor to allow for the Accessibility Keyboard to give good hints
  122. if (platform.isMacintosh) {
  123. const selection = this._selections[0];
  124. if (selection.isEmpty()) {
  125. const position = selection.getStartPosition();
  126. let textBefore = this._getWordBeforePosition(position);
  127. if (textBefore.length === 0) {
  128. textBefore = this._getCharacterBeforePosition(position);
  129. }
  130. if (textBefore.length > 0) {
  131. return new TextAreaState(textBefore, textBefore.length, textBefore.length, position, position);
  132. }
  133. }
  134. }
  135. return TextAreaState.EMPTY;
  136. }
  137. if (browser.isAndroid) {
  138. // when tapping in the editor on a word, Android enters composition mode.
  139. // in the `compositionstart` event we cannot clear the textarea, because
  140. // it then forgets to ever send a `compositionend`.
  141. // we therefore only write the current word in the textarea
  142. const selection = this._selections[0];
  143. if (selection.isEmpty()) {
  144. const position = selection.getStartPosition();
  145. const [wordAtPosition, positionOffsetInWord] = this._getAndroidWordAtPosition(position);
  146. if (wordAtPosition.length > 0) {
  147. return new TextAreaState(wordAtPosition, positionOffsetInWord, positionOffsetInWord, position, position);
  148. }
  149. }
  150. return TextAreaState.EMPTY;
  151. }
  152. return PagedScreenReaderStrategy.fromEditorSelection(currentState, simpleModel, this._selections[0], this._accessibilityPageSize, this._accessibilitySupport === 0 /* Unknown */);
  153. },
  154. deduceModelPosition: (viewAnchorPosition, deltaOffset, lineFeedCnt) => {
  155. return this._context.model.deduceModelPositionRelativeToViewPosition(viewAnchorPosition, deltaOffset, lineFeedCnt);
  156. }
  157. };
  158. const textAreaWrapper = this._register(new TextAreaWrapper(this.textArea.domNode));
  159. this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, textAreaWrapper, platform.OS, browser));
  160. this._register(this._textAreaInput.onKeyDown((e) => {
  161. this._viewController.emitKeyDown(e);
  162. }));
  163. this._register(this._textAreaInput.onKeyUp((e) => {
  164. this._viewController.emitKeyUp(e);
  165. }));
  166. this._register(this._textAreaInput.onPaste((e) => {
  167. let pasteOnNewLine = false;
  168. let multicursorText = null;
  169. let mode = null;
  170. if (e.metadata) {
  171. pasteOnNewLine = (this._emptySelectionClipboard && !!e.metadata.isFromEmptySelection);
  172. multicursorText = (typeof e.metadata.multicursorText !== 'undefined' ? e.metadata.multicursorText : null);
  173. mode = e.metadata.mode;
  174. }
  175. this._viewController.paste(e.text, pasteOnNewLine, multicursorText, mode);
  176. }));
  177. this._register(this._textAreaInput.onCut(() => {
  178. this._viewController.cut();
  179. }));
  180. this._register(this._textAreaInput.onType((e) => {
  181. if (e.replacePrevCharCnt || e.replaceNextCharCnt || e.positionDelta) {
  182. // must be handled through the new command
  183. if (_debugComposition) {
  184. console.log(` => compositionType: <<${e.text}>>, ${e.replacePrevCharCnt}, ${e.replaceNextCharCnt}, ${e.positionDelta}`);
  185. }
  186. this._viewController.compositionType(e.text, e.replacePrevCharCnt, e.replaceNextCharCnt, e.positionDelta);
  187. }
  188. else {
  189. if (_debugComposition) {
  190. console.log(` => type: <<${e.text}>>`);
  191. }
  192. this._viewController.type(e.text);
  193. }
  194. }));
  195. this._register(this._textAreaInput.onSelectionChangeRequest((modelSelection) => {
  196. this._viewController.setSelection(modelSelection);
  197. }));
  198. this._register(this._textAreaInput.onCompositionStart((e) => {
  199. const lineNumber = this._selections[0].startLineNumber;
  200. const column = this._selections[0].startColumn + e.revealDeltaColumns;
  201. this._context.model.revealRange('keyboard', true, new Range(lineNumber, column, lineNumber, column), 0 /* Simple */, 1 /* Immediate */);
  202. // Find range pixel position
  203. const visibleRange = this._viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column);
  204. if (visibleRange) {
  205. this._visibleTextArea = new VisibleTextAreaData(this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber), visibleRange.left, canUseZeroSizeTextarea ? 0 : 1);
  206. // The textarea might contain more than just the currently composed text
  207. // so we will scroll the textarea as much as possible to the left, which
  208. // means that the browser will perfectly center the currently composed text
  209. // when it scrolls to the right to reveal the textarea cursor.
  210. this.textArea.domNode.scrollLeft = 0;
  211. this._render();
  212. }
  213. // Show the textarea
  214. this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME} ime-input`);
  215. this._viewController.compositionStart();
  216. this._context.model.onCompositionStart();
  217. }));
  218. this._register(this._textAreaInput.onCompositionUpdate((e) => {
  219. if (!this._visibleTextArea) {
  220. return;
  221. }
  222. // adjust width by its size
  223. this._visibleTextArea = this._visibleTextArea.setWidth(measureText(e.data, this._fontInfo));
  224. // The textarea might contain more than just the currently composed text
  225. // so we will scroll the textarea as much as possible to the left, which
  226. // means that the browser will perfectly center the currently composed text
  227. // when it scrolls to the right to reveal the textarea cursor.
  228. this.textArea.domNode.scrollLeft = 0;
  229. this._render();
  230. }));
  231. this._register(this._textAreaInput.onCompositionEnd(() => {
  232. this._visibleTextArea = null;
  233. this._render();
  234. this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
  235. this._viewController.compositionEnd();
  236. this._context.model.onCompositionEnd();
  237. }));
  238. this._register(this._textAreaInput.onFocus(() => {
  239. this._context.model.setHasFocus(true);
  240. }));
  241. this._register(this._textAreaInput.onBlur(() => {
  242. this._context.model.setHasFocus(false);
  243. }));
  244. }
  245. dispose() {
  246. super.dispose();
  247. }
  248. _getAndroidWordAtPosition(position) {
  249. const ANDROID_WORD_SEPARATORS = '`~!@#$%^&*()-=+[{]}\\|;:",.<>/?';
  250. const lineContent = this._context.model.getLineContent(position.lineNumber);
  251. const wordSeparators = getMapForWordSeparators(ANDROID_WORD_SEPARATORS);
  252. let goingLeft = true;
  253. let startColumn = position.column;
  254. let goingRight = true;
  255. let endColumn = position.column;
  256. let distance = 0;
  257. while (distance < 50 && (goingLeft || goingRight)) {
  258. if (goingLeft && startColumn <= 1) {
  259. goingLeft = false;
  260. }
  261. if (goingLeft) {
  262. const charCode = lineContent.charCodeAt(startColumn - 2);
  263. const charClass = wordSeparators.get(charCode);
  264. if (charClass !== 0 /* Regular */) {
  265. goingLeft = false;
  266. }
  267. else {
  268. startColumn--;
  269. }
  270. }
  271. if (goingRight && endColumn > lineContent.length) {
  272. goingRight = false;
  273. }
  274. if (goingRight) {
  275. const charCode = lineContent.charCodeAt(endColumn - 1);
  276. const charClass = wordSeparators.get(charCode);
  277. if (charClass !== 0 /* Regular */) {
  278. goingRight = false;
  279. }
  280. else {
  281. endColumn++;
  282. }
  283. }
  284. distance++;
  285. }
  286. return [lineContent.substring(startColumn - 1, endColumn - 1), position.column - startColumn];
  287. }
  288. _getWordBeforePosition(position) {
  289. const lineContent = this._context.model.getLineContent(position.lineNumber);
  290. const wordSeparators = getMapForWordSeparators(this._context.configuration.options.get(116 /* wordSeparators */));
  291. let column = position.column;
  292. let distance = 0;
  293. while (column > 1) {
  294. const charCode = lineContent.charCodeAt(column - 2);
  295. const charClass = wordSeparators.get(charCode);
  296. if (charClass !== 0 /* Regular */ || distance > 50) {
  297. return lineContent.substring(column - 1, position.column - 1);
  298. }
  299. distance++;
  300. column--;
  301. }
  302. return lineContent.substring(0, position.column - 1);
  303. }
  304. _getCharacterBeforePosition(position) {
  305. if (position.column > 1) {
  306. const lineContent = this._context.model.getLineContent(position.lineNumber);
  307. const charBefore = lineContent.charAt(position.column - 2);
  308. if (!strings.isHighSurrogate(charBefore.charCodeAt(0))) {
  309. return charBefore;
  310. }
  311. }
  312. return '';
  313. }
  314. _getAriaLabel(options) {
  315. const accessibilitySupport = options.get(2 /* accessibilitySupport */);
  316. if (accessibilitySupport === 1 /* Disabled */) {
  317. return nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press {0} for options.", platform.isLinux ? 'Shift+Alt+F1' : 'Alt+F1');
  318. }
  319. return options.get(4 /* ariaLabel */);
  320. }
  321. _setAccessibilityOptions(options) {
  322. this._accessibilitySupport = options.get(2 /* accessibilitySupport */);
  323. const accessibilityPageSize = options.get(3 /* accessibilityPageSize */);
  324. if (this._accessibilitySupport === 2 /* Enabled */ && accessibilityPageSize === EditorOptions.accessibilityPageSize.defaultValue) {
  325. // If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 500 for a better experience
  326. this._accessibilityPageSize = 500;
  327. }
  328. else {
  329. this._accessibilityPageSize = accessibilityPageSize;
  330. }
  331. }
  332. // --- begin event handlers
  333. onConfigurationChanged(e) {
  334. const options = this._context.configuration.options;
  335. const layoutInfo = options.get(130 /* layoutInfo */);
  336. this._setAccessibilityOptions(options);
  337. this._contentLeft = layoutInfo.contentLeft;
  338. this._contentWidth = layoutInfo.contentWidth;
  339. this._contentHeight = layoutInfo.height;
  340. this._fontInfo = options.get(43 /* fontInfo */);
  341. this._lineHeight = options.get(58 /* lineHeight */);
  342. this._emptySelectionClipboard = options.get(32 /* emptySelectionClipboard */);
  343. this._copyWithSyntaxHighlighting = options.get(21 /* copyWithSyntaxHighlighting */);
  344. this.textArea.setAttribute('aria-label', this._getAriaLabel(options));
  345. this.textArea.setAttribute('tabindex', String(options.get(111 /* tabIndex */)));
  346. if (e.hasChanged(30 /* domReadOnly */) || e.hasChanged(80 /* readOnly */)) {
  347. if (options.get(30 /* domReadOnly */) && options.get(80 /* readOnly */)) {
  348. this.textArea.setAttribute('readonly', 'true');
  349. }
  350. else {
  351. this.textArea.removeAttribute('readonly');
  352. }
  353. }
  354. if (e.hasChanged(2 /* accessibilitySupport */)) {
  355. this._textAreaInput.writeScreenReaderContent('strategy changed');
  356. }
  357. return true;
  358. }
  359. onCursorStateChanged(e) {
  360. this._selections = e.selections.slice(0);
  361. this._modelSelections = e.modelSelections.slice(0);
  362. this._textAreaInput.writeScreenReaderContent('selection changed');
  363. return true;
  364. }
  365. onDecorationsChanged(e) {
  366. // true for inline decorations that can end up relayouting text
  367. return true;
  368. }
  369. onFlushed(e) {
  370. return true;
  371. }
  372. onLinesChanged(e) {
  373. return true;
  374. }
  375. onLinesDeleted(e) {
  376. return true;
  377. }
  378. onLinesInserted(e) {
  379. return true;
  380. }
  381. onScrollChanged(e) {
  382. this._scrollLeft = e.scrollLeft;
  383. this._scrollTop = e.scrollTop;
  384. return true;
  385. }
  386. onZonesChanged(e) {
  387. return true;
  388. }
  389. // --- end event handlers
  390. // --- begin view API
  391. isFocused() {
  392. return this._textAreaInput.isFocused();
  393. }
  394. focusTextArea() {
  395. this._textAreaInput.focusTextArea();
  396. }
  397. getLastRenderData() {
  398. return this._lastRenderPosition;
  399. }
  400. setAriaOptions(options) {
  401. if (options.activeDescendant) {
  402. this.textArea.setAttribute('aria-haspopup', 'true');
  403. this.textArea.setAttribute('aria-autocomplete', 'list');
  404. this.textArea.setAttribute('aria-activedescendant', options.activeDescendant);
  405. }
  406. else {
  407. this.textArea.setAttribute('aria-haspopup', 'false');
  408. this.textArea.setAttribute('aria-autocomplete', 'both');
  409. this.textArea.removeAttribute('aria-activedescendant');
  410. }
  411. if (options.role) {
  412. this.textArea.setAttribute('role', options.role);
  413. }
  414. }
  415. prepareRender(ctx) {
  416. this._primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn);
  417. this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(this._primaryCursorPosition);
  418. }
  419. render(ctx) {
  420. this._textAreaInput.writeScreenReaderContent('render');
  421. this._render();
  422. }
  423. _render() {
  424. if (this._visibleTextArea) {
  425. // The text area is visible for composition reasons
  426. this._renderInsideEditor(null, this._visibleTextArea.top - this._scrollTop, this._contentLeft + this._visibleTextArea.left - this._scrollLeft, this._visibleTextArea.width, this._lineHeight);
  427. return;
  428. }
  429. if (!this._primaryCursorVisibleRange) {
  430. // The primary cursor is outside the viewport => place textarea to the top left
  431. this._renderAtTopLeft();
  432. return;
  433. }
  434. const left = this._contentLeft + this._primaryCursorVisibleRange.left - this._scrollLeft;
  435. if (left < this._contentLeft || left > this._contentLeft + this._contentWidth) {
  436. // cursor is outside the viewport
  437. this._renderAtTopLeft();
  438. return;
  439. }
  440. const top = this._context.viewLayout.getVerticalOffsetForLineNumber(this._selections[0].positionLineNumber) - this._scrollTop;
  441. if (top < 0 || top > this._contentHeight) {
  442. // cursor is outside the viewport
  443. this._renderAtTopLeft();
  444. return;
  445. }
  446. // The primary cursor is in the viewport (at least vertically) => place textarea on the cursor
  447. if (platform.isMacintosh) {
  448. // For the popup emoji input, we will make the text area as high as the line height
  449. // We will also make the fontSize and lineHeight the correct dimensions to help with the placement of these pickers
  450. this._renderInsideEditor(this._primaryCursorPosition, top, left, canUseZeroSizeTextarea ? 0 : 1, this._lineHeight);
  451. // In case the textarea contains a word, we're going to try to align the textarea's cursor
  452. // with our cursor by scrolling the textarea as much as possible
  453. this.textArea.domNode.scrollLeft = this._primaryCursorVisibleRange.left;
  454. const lineCount = this._newlinecount(this.textArea.domNode.value.substr(0, this.textArea.domNode.selectionStart));
  455. this.textArea.domNode.scrollTop = lineCount * this._lineHeight;
  456. return;
  457. }
  458. this._renderInsideEditor(this._primaryCursorPosition, top, left, canUseZeroSizeTextarea ? 0 : 1, canUseZeroSizeTextarea ? 0 : 1);
  459. }
  460. _newlinecount(text) {
  461. let result = 0;
  462. let startIndex = -1;
  463. do {
  464. startIndex = text.indexOf('\n', startIndex + 1);
  465. if (startIndex === -1) {
  466. break;
  467. }
  468. result++;
  469. } while (true);
  470. return result;
  471. }
  472. _renderInsideEditor(renderedPosition, top, left, width, height) {
  473. this._lastRenderPosition = renderedPosition;
  474. const ta = this.textArea;
  475. const tac = this.textAreaCover;
  476. Configuration.applyFontInfo(ta, this._fontInfo);
  477. ta.setTop(top);
  478. ta.setLeft(left);
  479. ta.setWidth(width);
  480. ta.setHeight(height);
  481. tac.setTop(0);
  482. tac.setLeft(0);
  483. tac.setWidth(0);
  484. tac.setHeight(0);
  485. }
  486. _renderAtTopLeft() {
  487. this._lastRenderPosition = null;
  488. const ta = this.textArea;
  489. const tac = this.textAreaCover;
  490. Configuration.applyFontInfo(ta, this._fontInfo);
  491. ta.setTop(0);
  492. ta.setLeft(0);
  493. tac.setTop(0);
  494. tac.setLeft(0);
  495. if (canUseZeroSizeTextarea) {
  496. ta.setWidth(0);
  497. ta.setHeight(0);
  498. tac.setWidth(0);
  499. tac.setHeight(0);
  500. return;
  501. }
  502. // (in WebKit the textarea is 1px by 1px because it cannot handle input to a 0x0 textarea)
  503. // specifically, when doing Korean IME, setting the textarea to 0x0 breaks IME badly.
  504. ta.setWidth(1);
  505. ta.setHeight(1);
  506. tac.setWidth(1);
  507. tac.setHeight(1);
  508. const options = this._context.configuration.options;
  509. if (options.get(49 /* glyphMargin */)) {
  510. tac.setClassName('monaco-editor-background textAreaCover ' + Margin.OUTER_CLASS_NAME);
  511. }
  512. else {
  513. if (options.get(59 /* lineNumbers */).renderType !== 0 /* Off */) {
  514. tac.setClassName('monaco-editor-background textAreaCover ' + LineNumbersOverlay.CLASS_NAME);
  515. }
  516. else {
  517. tac.setClassName('monaco-editor-background textAreaCover');
  518. }
  519. }
  520. }
  521. }
  522. function measureText(text, fontInfo) {
  523. // adjust width by its size
  524. const canvasElem = document.createElement('canvas');
  525. const context = canvasElem.getContext('2d');
  526. context.font = createFontString(fontInfo);
  527. const metrics = context.measureText(text);
  528. if (browser.isFirefox) {
  529. return metrics.width + 2; // +2 for Japanese...
  530. }
  531. else {
  532. return metrics.width;
  533. }
  534. }
  535. function createFontString(bareFontInfo) {
  536. return doCreateFontString('normal', bareFontInfo.fontWeight, bareFontInfo.fontSize, bareFontInfo.lineHeight, bareFontInfo.fontFamily);
  537. }
  538. function doCreateFontString(fontStyle, fontWeight, fontSize, lineHeight, fontFamily) {
  539. // The full font syntax is:
  540. // style | variant | weight | stretch | size/line-height | fontFamily
  541. // (https://developer.mozilla.org/en-US/docs/Web/CSS/font)
  542. // But it appears Edge and IE11 cannot properly parse `stretch`.
  543. return `${fontStyle} normal ${fontWeight} ${fontSize}px / ${lineHeight}px ${fontFamily}`;
  544. }