Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix Korean(and Chinese, Japanese) IME behavior (#4541) #5615

Merged
merged 1 commit into from
May 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/vs/base/browser/compositionEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

'use strict';

export interface ICompositionEvent {
data: string;
locale: string;
}
45 changes: 35 additions & 10 deletions src/vs/editor/browser/controller/keyboardHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {Disposable, IDisposable, dispose} from 'vs/base/common/lifecycle';
import * as browser from 'vs/base/browser/browser';
import * as dom from 'vs/base/browser/dom';
import {IKeyboardEvent} from 'vs/base/browser/keyboardEvent';
import {ICompositionEvent} from 'vs/base/browser/compositionEvent';
import {StyleMutator} from 'vs/base/browser/styleMutator';
import {GlobalScreenReaderNVDA} from 'vs/editor/common/config/commonEditorConfig';
import {TextAreaHandler} from 'vs/editor/common/controller/textAreaHandler';
Expand Down Expand Up @@ -104,11 +105,14 @@ class TextAreaWrapper extends Disposable implements ITextAreaWrapper {
private _onKeyPress = this._register(new Emitter<IKeyboardEventWrapper>());
public onKeyPress: Event<IKeyboardEventWrapper> = this._onKeyPress.event;

private _onCompositionStart = this._register(new Emitter<void>());
public onCompositionStart: Event<void> = this._onCompositionStart.event;
private _onCompositionStart = this._register(new Emitter<ICompositionEvent>());
public onCompositionStart: Event<ICompositionEvent> = this._onCompositionStart.event;

private _onCompositionEnd = this._register(new Emitter<void>());
public onCompositionEnd: Event<void> = this._onCompositionEnd.event;
private _onCompositionUpdate = this._register(new Emitter<ICompositionEvent>());
public onCompositionUpdate: Event<ICompositionEvent> = this._onCompositionUpdate.event;

private _onCompositionEnd = this._register(new Emitter<ICompositionEvent>());
public onCompositionEnd: Event<ICompositionEvent> = this._onCompositionEnd.event;

private _onInput = this._register(new Emitter<void>());
public onInput: Event<void> = this._onInput.event;
Expand All @@ -129,8 +133,9 @@ class TextAreaWrapper extends Disposable implements ITextAreaWrapper {
this._register(dom.addStandardDisposableListener(this._textArea, 'keydown', (e) => this._onKeyDown.fire(new KeyboardEventWrapper(e))));
this._register(dom.addStandardDisposableListener(this._textArea, 'keyup', (e) => this._onKeyUp.fire(new KeyboardEventWrapper(e))));
this._register(dom.addStandardDisposableListener(this._textArea, 'keypress', (e) => this._onKeyPress.fire(new KeyboardEventWrapper(e))));
this._register(dom.addDisposableListener(this._textArea, 'compositionstart', (e) => this._onCompositionStart.fire()));
this._register(dom.addDisposableListener(this._textArea, 'compositionend', (e) => this._onCompositionEnd.fire()));
this._register(dom.addDisposableListener(this._textArea, 'compositionstart', (e) => this._onCompositionStart.fire(e)));
this._register(dom.addDisposableListener(this._textArea, 'compositionupdate', (e) => this._onCompositionUpdate.fire(e)));
this._register(dom.addDisposableListener(this._textArea, 'compositionend', (e) => this._onCompositionEnd.fire(e)));
this._register(dom.addDisposableListener(this._textArea, 'input', (e) => this._onInput.fire()));
this._register(dom.addDisposableListener(this._textArea, 'cut', (e:ClipboardEvent) => this._onCut.fire(new ClipboardEventWrapper(e))));
this._register(dom.addDisposableListener(this._textArea, 'copy', (e:ClipboardEvent) => this._onCopy.fire(new ClipboardEventWrapper(e))));
Expand Down Expand Up @@ -203,6 +208,8 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable {
private contentWidth:number;
private scrollLeft:number;

private visibleRange:editorCommon.VisibleRange;

constructor(context:IViewContext, viewController:IViewController, viewHelper:IKeyboardHandlerHelper) {
super();

Expand Down Expand Up @@ -241,11 +248,11 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable {
this.context.privateViewEventBus.emit(editorCommon.ViewEventNames.RevealRangeEvent, revealPositionEvent);

// Find range pixel position
let visibleRange = this.viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column);
this.visibleRange = this.viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column);

if (visibleRange) {
StyleMutator.setTop(this.textArea.actual, visibleRange.top);
StyleMutator.setLeft(this.textArea.actual, this.contentLeft + visibleRange.left - this.scrollLeft);
if (this.visibleRange) {
StyleMutator.setTop(this.textArea.actual, this.visibleRange.top);
StyleMutator.setLeft(this.textArea.actual, this.contentLeft + this.visibleRange.left - this.scrollLeft);
}

if (browser.isIE11orEarlier) {
Expand All @@ -256,12 +263,25 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable {
StyleMutator.setHeight(this.textArea.actual, this.context.configuration.editor.lineHeight);
dom.addClass(this.viewHelper.viewDomNode, 'ime-input');
}));

this._toDispose.push(this.textAreaHandler.onCompositionUpdate((e) => {

// adjust width by its size
let canvasElem = <HTMLCanvasElement>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) => {
this.textArea.actual.style.height = '';
this.textArea.actual.style.width = '';
StyleMutator.setLeft(this.textArea.actual, 0);
StyleMutator.setTop(this.textArea.actual, 0);
dom.removeClass(this.viewHelper.viewDomNode, 'ime-input');

this.visibleRange = null;
}));
this._toDispose.push(GlobalScreenReaderNVDA.onChange((value) => {
this.textAreaHandler.setStrategy(this._getStrategy());
Expand Down Expand Up @@ -302,8 +322,13 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable {
return false;
}


public onScrollChanged(e:editorCommon.IScrollEvent): boolean {
this.scrollLeft = e.scrollLeft;
if (this.visibleRange) {
StyleMutator.setTop(this.textArea.actual, this.visibleRange.top);
StyleMutator.setLeft(this.textArea.actual, this.contentLeft + this.visibleRange.left - this.scrollLeft);
}
return false;
}

Expand Down
41 changes: 32 additions & 9 deletions src/vs/editor/common/controller/textAreaHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {IClipboardEvent, IKeyboardEventWrapper, ISimpleModel, ITextAreaWrapper,
import {Position} from 'vs/editor/common/core/position';
import {Range} from 'vs/editor/common/core/range';
import {EndOfLinePreference, IEditorPosition, IEditorRange} from 'vs/editor/common/editorCommon';
import {ICompositionEvent} from 'vs/base/browser/compositionEvent';

enum ReadFromTextArea {
Type,
Expand Down Expand Up @@ -56,8 +57,11 @@ export class TextAreaHandler extends Disposable {
private _onCompositionStart = this._register(new Emitter<ICompositionStartData>());
public onCompositionStart: Event<ICompositionStartData> = this._onCompositionStart.event;

private _onCompositionEnd = this._register(new Emitter<void>());
public onCompositionEnd: Event<void> = this._onCompositionEnd.event;
private _onCompositionUpdate = this._register(new Emitter<ICompositionEvent>());
public onCompositionUpdate: Event<ICompositionEvent> = this._onCompositionUpdate.event;

private _onCompositionEnd = this._register(new Emitter<ICompositionEvent>());
public onCompositionEnd: Event<ICompositionEvent> = this._onCompositionEnd.event;

private Browser:IBrowser;
private textArea:ITextAreaWrapper;
Expand Down Expand Up @@ -108,16 +112,16 @@ export class TextAreaHandler extends Disposable {

this.textareaIsShownAtCursor = false;

this._register(this.textArea.onCompositionStart(() => {
let timeSinceLastCompositionEnd = (new Date().getTime()) - this.lastCompositionEndTime;
this._register(this.textArea.onCompositionStart((e) => {

if (this.textareaIsShownAtCursor) {
return;
}

this.textareaIsShownAtCursor = true;

// In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled.
let shouldEmptyTextArea = (timeSinceLastCompositionEnd >= 100);
let shouldEmptyTextArea = true;
if (shouldEmptyTextArea) {
if (!this.Browser.isIE11orEarlier) {
this.setTextAreaState('compositionstart', this.textAreaState.toEmpty());
Expand All @@ -136,13 +140,26 @@ export class TextAreaHandler extends Disposable {
showAtLineNumber = this.cursorPosition.lineNumber;
showAtColumn = this.cursorPosition.column;
}

this._onCompositionStart.fire({
showAtLineNumber: showAtLineNumber,
showAtColumn: showAtColumn
});
}));

this._register(this.textArea.onCompositionUpdate((e) => {
this.textAreaState = this.textAreaState.fromText(e.data);
let typeInput = this.textAreaState.updateComposition();
if (this._nextCommand === ReadFromTextArea.Type) {
if (typeInput.text !== '') {
this._onType.fire(typeInput);
}
} else {
this.executePaste(typeInput.text);
this._nextCommand = ReadFromTextArea.Type;
}
this._onCompositionUpdate.fire(e);
}));

let readFromTextArea = () => {
this.textAreaState = this.textAreaState.fromTextArea(this.textArea);
let typeInput = this.textAreaState.deduceInput();
Expand All @@ -157,9 +174,11 @@ export class TextAreaHandler extends Disposable {
}
};

this._register(this.textArea.onCompositionEnd(() => {
// console.log('onCompositionEnd: ' + this.textArea.getValue());
// readFromTextArea();
this._register(this.textArea.onCompositionEnd((e) => {
// console.log('onCompositionEnd: ' + e.data);
this.textAreaState = this.textAreaState.fromText(e.data);
let typeInput = this.textAreaState.updateComposition();
this._onType.fire(typeInput);

this.lastCompositionEndTime = (new Date()).getTime();
if (!this.textareaIsShownAtCursor) {
Expand Down Expand Up @@ -238,6 +257,10 @@ export class TextAreaHandler extends Disposable {
this._writePlaceholderAndSelectTextArea('selection changed');
}

public getCursorPosition(): IEditorPosition {
return this.cursorPosition;
}

public setCursorPosition(primary: IEditorPosition): void {
this.cursorPosition = primary;
}
Expand Down
21 changes: 19 additions & 2 deletions src/vs/editor/common/controller/textAreaState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Event from 'vs/base/common/event';
import {commonPrefixLength, commonSuffixLength} from 'vs/base/common/strings';
import {Range} from 'vs/editor/common/core/range';
import {EndOfLinePreference, IEditorPosition, IEditorRange, IRange} from 'vs/editor/common/editorCommon';
import {ICompositionEvent} from 'vs/base/browser/compositionEvent';

export interface IClipboardEvent {
canUseTextData(): boolean;
Expand All @@ -26,8 +27,9 @@ export interface ITextAreaWrapper {
onKeyDown: Event<IKeyboardEventWrapper>;
onKeyUp: Event<IKeyboardEventWrapper>;
onKeyPress: Event<IKeyboardEventWrapper>;
onCompositionStart: Event<void>;
onCompositionEnd: Event<void>;
onCompositionStart: Event<ICompositionEvent>;
onCompositionUpdate: Event<ICompositionEvent>;
onCompositionEnd: Event<ICompositionEvent>;
onInput: Event<void>;
onCut: Event<IClipboardEvent>;
onCopy: Event<IClipboardEvent>;
Expand Down Expand Up @@ -105,6 +107,21 @@ export abstract class TextAreaState {

public abstract fromText(text:string): TextAreaState;

public updateComposition(): ITypeData {
if (!this.previousState) {
// This is the EMPTY state
return {
text: '',
replaceCharCnt: 0
};
}

return {
text: this.value,
replaceCharCnt: this.previousState.selectionEnd - this.previousState.selectionStart
};
}

public abstract resetSelection(): TextAreaState;

public getSelectionStart(): number {
Expand Down
12 changes: 8 additions & 4 deletions src/vs/editor/test/common/mocks/mockTextAreaWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import Event, {Emitter} from 'vs/base/common/event';
import {Disposable} from 'vs/base/common/lifecycle';
import {IClipboardEvent, IKeyboardEventWrapper, ITextAreaWrapper} from 'vs/editor/common/controller/textAreaState';
import {ICompositionEvent} from 'vs/base/browser/compositionEvent';

export class MockTextAreaWrapper extends Disposable implements ITextAreaWrapper {

Expand All @@ -19,11 +20,14 @@ export class MockTextAreaWrapper extends Disposable implements ITextAreaWrapper
private _onKeyPress = this._register(new Emitter<IKeyboardEventWrapper>());
public onKeyPress: Event<IKeyboardEventWrapper> = this._onKeyPress.event;

private _onCompositionStart = this._register(new Emitter<void>());
public onCompositionStart: Event<void> = this._onCompositionStart.event;
private _onCompositionStart = this._register(new Emitter<ICompositionEvent>());
public onCompositionStart: Event<ICompositionEvent> = this._onCompositionStart.event;

private _onCompositionEnd = this._register(new Emitter<void>());
public onCompositionEnd: Event<void> = this._onCompositionEnd.event;
private _onCompositionUpdate = this._register(new Emitter<ICompositionEvent>());
public onCompositionUpdate: Event<ICompositionEvent> = this._onCompositionUpdate.event;

private _onCompositionEnd = this._register(new Emitter<ICompositionEvent>());
public onCompositionEnd: Event<ICompositionEvent> = this._onCompositionEnd.event;

private _onInput = this._register(new Emitter<void>());
public onInput: Event<void> = this._onInput.event;
Expand Down