Skip to content

Commit

Permalink
Merge pull request #5615 from ioklo/topic/ime-fix
Browse files Browse the repository at this point in the history
fix Korean(and Chinese, Japanese) IME behavior (#4541)
  • Loading branch information
alexdima committed May 4, 2016
2 parents fc90ee7 + 5939b5c commit dacfff3
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 28 deletions.
44 changes: 33 additions & 11 deletions src/vs/editor/browser/controller/keyboardHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {IKeyboardEvent} from 'vs/base/browser/keyboardEvent';
import {StyleMutator} from 'vs/base/browser/styleMutator';
import {GlobalScreenReaderNVDA} from 'vs/editor/common/config/commonEditorConfig';
import {TextAreaHandler} from 'vs/editor/common/controller/textAreaHandler';
import {IClipboardEvent, IKeyboardEventWrapper, ITextAreaWrapper, TextAreaStrategy} from 'vs/editor/common/controller/textAreaState';
import {IClipboardEvent, ICompositionEvent, IKeyboardEventWrapper, ITextAreaWrapper, TextAreaStrategy} from 'vs/editor/common/controller/textAreaState';
import {Range} from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import {ViewEventHandler} from 'vs/editor/common/viewModel/viewEventHandler';
Expand Down Expand Up @@ -114,11 +114,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 @@ -139,8 +142,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 @@ -213,6 +217,8 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable {
private contentWidth:number;
private scrollLeft:number;

private visibleRange:VisibleRange;

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

Expand Down Expand Up @@ -252,11 +258,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 @@ -267,12 +273,24 @@ 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 @@ -316,6 +334,10 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable {

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
31 changes: 21 additions & 10 deletions src/vs/editor/common/controller/textAreaHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {RunOnceScheduler} from 'vs/base/common/async';
import Event, {Emitter} from 'vs/base/common/event';
import {CommonKeybindings} from 'vs/base/common/keyCodes';
import {Disposable} from 'vs/base/common/lifecycle';
import {IClipboardEvent, IKeyboardEventWrapper, ISimpleModel, ITextAreaWrapper, ITypeData, TextAreaState, TextAreaStrategy, createTextAreaState} from 'vs/editor/common/controller/textAreaState';
import {IClipboardEvent, ICompositionEvent, IKeyboardEventWrapper, ISimpleModel, ITextAreaWrapper, ITypeData, TextAreaState, TextAreaStrategy, createTextAreaState} from 'vs/editor/common/controller/textAreaState';
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';
Expand Down Expand Up @@ -56,8 +56,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 +111,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 +139,19 @@ 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();
this._onType.fire(typeInput);
this._onCompositionUpdate.fire(e);
}));

let readFromTextArea = () => {
this.textAreaState = this.textAreaState.fromTextArea(this.textArea);
let typeInput = this.textAreaState.deduceInput();
Expand All @@ -157,9 +166,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
25 changes: 23 additions & 2 deletions src/vs/editor/common/controller/textAreaState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export interface IClipboardEvent {
getTextData(): string;
}

export interface ICompositionEvent {
data: string;
locale: string;
}

export interface IKeyboardEventWrapper {
_actual: any;
equals(keybinding:number): boolean;
Expand All @@ -26,8 +31,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 +111,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
13 changes: 8 additions & 5 deletions src/vs/editor/test/common/mocks/mockTextAreaWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,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 {IClipboardEvent, ICompositionEvent, IKeyboardEventWrapper, ITextAreaWrapper} from 'vs/editor/common/controller/textAreaState';

export class MockTextAreaWrapper extends Disposable implements ITextAreaWrapper {

Expand All @@ -19,11 +19,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

0 comments on commit dacfff3

Please sign in to comment.