diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index f0b00dd77ae..468cd724616 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -78,6 +78,8 @@ export const isIE9 = (userAgent.indexOf('MSIE 9') >= 0); export const isIE11orEarlier = isIE11 || isIE10 || isIE9; export const isIE10orEarlier = isIE10 || isIE9; export const isIE10orLater = isIE11 || isIE10; +export const isEdge = (userAgent.indexOf('Edge/') >= 0); +export const isEdgeOrIE = isEdge || isIE11 || isIE10 || isIE9; export const isOpera = (userAgent.indexOf('Opera') >= 0); export const isFirefox = (userAgent.indexOf('Firefox') >= 0); diff --git a/src/vs/editor/browser/controller/keyboardHandler.ts b/src/vs/editor/browser/controller/keyboardHandler.ts index eb2dad31c63..7a31f17f8fd 100644 --- a/src/vs/editor/browser/controller/keyboardHandler.ts +++ b/src/vs/editor/browser/controller/keyboardHandler.ts @@ -90,10 +90,6 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable { StyleMutator.setLeft(this.textArea.actual, this.contentLeft + this.visibleRange.left - this.scrollLeft); } - if (browser.isIE11orEarlier) { - StyleMutator.setWidth(this.textArea.actual, this.contentWidth); - } - // Show the textarea StyleMutator.setHeight(this.textArea.actual, this._context.configuration.editor.lineHeight); dom.addClass(this.viewHelper.viewDomNode, 'ime-input'); @@ -102,12 +98,18 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable { })); this._toDispose.push(this.textAreaHandler.onCompositionUpdate((e) => { - // adjust width by its size - let canvasElem = document.createElement('canvas'); - let context = canvasElem.getContext('2d'); - context.font = window.getComputedStyle(this.textArea.actual).font; - let metrics = context.measureText(e.data); - StyleMutator.setWidth(this.textArea.actual, metrics.width); + if (browser.isEdgeOrIE) { + // Due to isEdgeOrIE (where the textarea was not cleared initially) + // we cannot assume the text consists only of the composited text + StyleMutator.setWidth(this.textArea.actual, 0); + } else { + // adjust width by its size + let canvasElem = document.createElement('canvas'); + let context = canvasElem.getContext('2d'); + context.font = window.getComputedStyle(this.textArea.actual).font; + let metrics = context.measureText(e.data); + StyleMutator.setWidth(this.textArea.actual, metrics.width); + } })); this._toDispose.push(this.textAreaHandler.onCompositionEnd((e) => { diff --git a/src/vs/editor/common/controller/textAreaHandler.ts b/src/vs/editor/common/controller/textAreaHandler.ts index 385ccfc2d4c..360bc504d24 100644 --- a/src/vs/editor/common/controller/textAreaHandler.ts +++ b/src/vs/editor/common/controller/textAreaHandler.ts @@ -21,7 +21,7 @@ enum ReadFromTextArea { export interface IBrowser { isIPad: boolean; isChrome: boolean; - isIE11orEarlier: boolean; + isEdgeOrIE: boolean; isFirefox: boolean; enableEmptySelectionClipboard: boolean; } @@ -122,7 +122,7 @@ export class TextAreaHandler extends Disposable { // In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled. let shouldEmptyTextArea = true; if (shouldEmptyTextArea) { - if (!this.Browser.isIE11orEarlier) { + if (!this.Browser.isEdgeOrIE) { this.setTextAreaState('compositionstart', this.textAreaState.toEmpty()); } } @@ -131,7 +131,7 @@ export class TextAreaHandler extends Disposable { let showAtColumn: number; // In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled. - if (this.Browser.isIE11orEarlier) { + if (this.Browser.isEdgeOrIE) { // Ensure selection start is in viewport showAtLineNumber = this.selection.startLineNumber; showAtColumn = (this.selection.startColumn - this.textAreaState.getSelectionStart()); @@ -172,6 +172,10 @@ export class TextAreaHandler extends Disposable { let typeInput = this.textAreaState.updateComposition(); this._onType.fire(typeInput); + // Due to isEdgeOrIE (where the textarea was not cleared initially) + // we cannot assume the text at the end consists only of the composited text + this.textAreaState = this.textAreaState.fromTextArea(this.textArea); + this.lastCompositionEndTime = (new Date()).getTime(); if (!this.textareaIsShownAtCursor) { return; diff --git a/src/vs/editor/test/browser/controller/imeTester.html b/src/vs/editor/test/browser/controller/imeTester.html new file mode 100644 index 00000000000..4c7c2909be1 --- /dev/null +++ b/src/vs/editor/test/browser/controller/imeTester.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts new file mode 100644 index 00000000000..e34e786af2a --- /dev/null +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TextAreaHandler} from 'vs/editor/common/controller/textAreaHandler'; +import * as browser from 'vs/base/browser/browser'; +import {TextAreaStrategy, ISimpleModel} from 'vs/editor/common/controller/textAreaState'; +import {Range} from 'vs/editor/common/core/range'; +import * as editorCommon from 'vs/editor/common/editorCommon'; +import {TextAreaWrapper} from 'vs/editor/browser/controller/input/textAreaWrapper'; +import {Position} from 'vs/editor/common/core/position'; + +// To run this test, open imeTester.html + +class SingleLineTestModel implements ISimpleModel { + + private _line:string; + private _eol:string; + + constructor(line:string) { + this._line = line; + this._eol = '\n'; + } + + setText(text:string) { + this._line = text; + } + + getLineMaxColumn(lineNumber:number): number { + return this._line.length + 1; + } + + getEOL(): string { + return this._eol; + } + + getValueInRange(range:editorCommon.IRange, eol:editorCommon.EndOfLinePreference): string { + return this._line.substring(range.startColumn - 1, range.endColumn - 1); + } + + getModelLineContent(lineNumber:number): string { + return this._line; + } + + getLineCount(): number { + return 1; + } + + convertViewPositionToModelPosition(viewLineNumber:number, viewColumn:number): Position { + return new Position(viewLineNumber, viewColumn); + } +} + +class TestView { + + private _model: SingleLineTestModel; + + constructor(model:SingleLineTestModel) { + this._model = model; + } + + public paint(output:HTMLElement) { + let r = ''; + for (let i = 1; i <= this._model.getLineCount(); i++) { + let content = this._model.getModelLineContent(i); + r += content + '
'; + } + output.innerHTML = r; + } +} + +function doCreateTest(strategy:TextAreaStrategy, inputStr:string, expectedStr:string): HTMLElement { + let container = document.createElement('div'); + container.className = 'container'; + + let title = document.createElement('div'); + title.className = 'title'; + title.innerHTML = 'Type ' + inputStr + ''; + container.appendChild(title); + + let startBtn = document.createElement('button'); + startBtn.innerHTML = 'Start'; + container.appendChild(startBtn); + + + let input = document.createElement('textarea'); + input.setAttribute('rows', '10'); + input.setAttribute('cols', '40'); + container.appendChild(input); + + let textAreaWrapper = new TextAreaWrapper(input); + + let model = new SingleLineTestModel('some text'); + + let handler = new TextAreaHandler(browser, strategy, textAreaWrapper, model, () => {}); + + input.onfocus = () => { + handler.setHasFocus(true); + }; + input.onblur = () => { + handler.setHasFocus(false); + }; + + let output = document.createElement('pre'); + output.className = 'output'; + container.appendChild(output); + + let check = document.createElement('pre'); + check.className = 'check'; + container.appendChild(check); + + let view = new TestView(model); + + + let cursorOffset: number; + let cursorLength: number; + let updatePosition = (off:number, len:number) => { + cursorOffset = off; + cursorLength = len; + handler.setCursorPosition(new Position(1, 1 + cursorOffset + cursorLength)); + handler.setCursorSelections(new Range(1, 1 + cursorOffset, 1, 1 + cursorOffset + cursorLength), []); + handler.writePlaceholderAndSelectTextAreaSync(); + }; + + let updateModelAndPosition = (text:string, off:number, len:number) => { + model.setText(text); + updatePosition(off, len); + view.paint(output); + + let expected = 'some ' + expectedStr + ' text'; + if (text === expected) { + check.innerHTML = '[GOOD]'; + } else { + check.innerHTML = '[BAD]'; + } + check.innerHTML += expected; + }; + + handler.onType((e) => { + console.log('type text: ' + e.text + ', replaceCharCnt: ' + e.replaceCharCnt); + let text = model.getModelLineContent(1); + let preText = text.substring(0, cursorOffset - e.replaceCharCnt); + let postText = text.substring(cursorOffset + cursorLength); + let midText = e.text; + + updateModelAndPosition(preText + midText + postText, (preText + midText).length, 0); + }); + + view.paint(output); + + startBtn.onclick = function() { + updateModelAndPosition('some text', 5, 0); + input.focus(); + }; + + return container; +} + +export function createTest(): void { + document.body.appendChild(doCreateTest(TextAreaStrategy.NVDA, 'sennsei', 'せんせい')); +} diff --git a/src/vs/editor/test/browser/controller/inputRecorder.html b/src/vs/editor/test/browser/controller/inputRecorder.html new file mode 100644 index 00000000000..3f538b8a04f --- /dev/null +++ b/src/vs/editor/test/browser/controller/inputRecorder.html @@ -0,0 +1,115 @@ + + + + + + + + +

Input Recorder

+

Records input events and logs them in the console

+ + +

+ + + + + \ No newline at end of file