Skip to content

Commit dacfff3

Browse files
committed
Merge pull request #5615 from ioklo/topic/ime-fix
fix Korean(and Chinese, Japanese) IME behavior (#4541)
2 parents fc90ee7 + 5939b5c commit dacfff3

4 files changed

Lines changed: 85 additions & 28 deletions

File tree

src/vs/editor/browser/controller/keyboardHandler.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {IKeyboardEvent} from 'vs/base/browser/keyboardEvent';
1212
import {StyleMutator} from 'vs/base/browser/styleMutator';
1313
import {GlobalScreenReaderNVDA} from 'vs/editor/common/config/commonEditorConfig';
1414
import {TextAreaHandler} from 'vs/editor/common/controller/textAreaHandler';
15-
import {IClipboardEvent, IKeyboardEventWrapper, ITextAreaWrapper, TextAreaStrategy} from 'vs/editor/common/controller/textAreaState';
15+
import {IClipboardEvent, ICompositionEvent, IKeyboardEventWrapper, ITextAreaWrapper, TextAreaStrategy} from 'vs/editor/common/controller/textAreaState';
1616
import {Range} from 'vs/editor/common/core/range';
1717
import * as editorCommon from 'vs/editor/common/editorCommon';
1818
import {ViewEventHandler} from 'vs/editor/common/viewModel/viewEventHandler';
@@ -114,11 +114,14 @@ class TextAreaWrapper extends Disposable implements ITextAreaWrapper {
114114
private _onKeyPress = this._register(new Emitter<IKeyboardEventWrapper>());
115115
public onKeyPress: Event<IKeyboardEventWrapper> = this._onKeyPress.event;
116116

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

120-
private _onCompositionEnd = this._register(new Emitter<void>());
121-
public onCompositionEnd: Event<void> = this._onCompositionEnd.event;
120+
private _onCompositionUpdate = this._register(new Emitter<ICompositionEvent>());
121+
public onCompositionUpdate: Event<ICompositionEvent> = this._onCompositionUpdate.event;
122+
123+
private _onCompositionEnd = this._register(new Emitter<ICompositionEvent>());
124+
public onCompositionEnd: Event<ICompositionEvent> = this._onCompositionEnd.event;
122125

123126
private _onInput = this._register(new Emitter<void>());
124127
public onInput: Event<void> = this._onInput.event;
@@ -139,8 +142,9 @@ class TextAreaWrapper extends Disposable implements ITextAreaWrapper {
139142
this._register(dom.addStandardDisposableListener(this._textArea, 'keydown', (e) => this._onKeyDown.fire(new KeyboardEventWrapper(e))));
140143
this._register(dom.addStandardDisposableListener(this._textArea, 'keyup', (e) => this._onKeyUp.fire(new KeyboardEventWrapper(e))));
141144
this._register(dom.addStandardDisposableListener(this._textArea, 'keypress', (e) => this._onKeyPress.fire(new KeyboardEventWrapper(e))));
142-
this._register(dom.addDisposableListener(this._textArea, 'compositionstart', (e) => this._onCompositionStart.fire()));
143-
this._register(dom.addDisposableListener(this._textArea, 'compositionend', (e) => this._onCompositionEnd.fire()));
145+
this._register(dom.addDisposableListener(this._textArea, 'compositionstart', (e) => this._onCompositionStart.fire(e)));
146+
this._register(dom.addDisposableListener(this._textArea, 'compositionupdate', (e) => this._onCompositionUpdate.fire(e)));
147+
this._register(dom.addDisposableListener(this._textArea, 'compositionend', (e) => this._onCompositionEnd.fire(e)));
144148
this._register(dom.addDisposableListener(this._textArea, 'input', (e) => this._onInput.fire()));
145149
this._register(dom.addDisposableListener(this._textArea, 'cut', (e:ClipboardEvent) => this._onCut.fire(new ClipboardEventWrapper(e))));
146150
this._register(dom.addDisposableListener(this._textArea, 'copy', (e:ClipboardEvent) => this._onCopy.fire(new ClipboardEventWrapper(e))));
@@ -213,6 +217,8 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable {
213217
private contentWidth:number;
214218
private scrollLeft:number;
215219

220+
private visibleRange:VisibleRange;
221+
216222
constructor(context:ViewContext, viewController:IViewController, viewHelper:IKeyboardHandlerHelper) {
217223
super();
218224

@@ -252,11 +258,11 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable {
252258
this._context.privateViewEventBus.emit(editorCommon.ViewEventNames.RevealRangeEvent, revealPositionEvent);
253259

254260
// Find range pixel position
255-
let visibleRange = this.viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column);
261+
this.visibleRange = this.viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column);
256262

257-
if (visibleRange) {
258-
StyleMutator.setTop(this.textArea.actual, visibleRange.top);
259-
StyleMutator.setLeft(this.textArea.actual, this.contentLeft + visibleRange.left - this.scrollLeft);
263+
if (this.visibleRange) {
264+
StyleMutator.setTop(this.textArea.actual, this.visibleRange.top);
265+
StyleMutator.setLeft(this.textArea.actual, this.contentLeft + this.visibleRange.left - this.scrollLeft);
260266
}
261267

262268
if (browser.isIE11orEarlier) {
@@ -267,12 +273,24 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable {
267273
StyleMutator.setHeight(this.textArea.actual, this._context.configuration.editor.lineHeight);
268274
dom.addClass(this.viewHelper.viewDomNode, 'ime-input');
269275
}));
276+
277+
this._toDispose.push(this.textAreaHandler.onCompositionUpdate((e) => {
278+
// adjust width by its size
279+
let canvasElem = <HTMLCanvasElement>document.createElement('canvas');
280+
let context = canvasElem.getContext('2d');
281+
context.font = window.getComputedStyle(this.textArea.actual).font;
282+
let metrics = context.measureText(e.data);
283+
StyleMutator.setWidth(this.textArea.actual, metrics.width);
284+
}));
285+
270286
this._toDispose.push(this.textAreaHandler.onCompositionEnd((e) => {
271287
this.textArea.actual.style.height = '';
272288
this.textArea.actual.style.width = '';
273289
StyleMutator.setLeft(this.textArea.actual, 0);
274290
StyleMutator.setTop(this.textArea.actual, 0);
275291
dom.removeClass(this.viewHelper.viewDomNode, 'ime-input');
292+
293+
this.visibleRange = null;
276294
}));
277295
this._toDispose.push(GlobalScreenReaderNVDA.onChange((value) => {
278296
this.textAreaHandler.setStrategy(this._getStrategy());
@@ -316,6 +334,10 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable {
316334

317335
public onScrollChanged(e:editorCommon.IScrollEvent): boolean {
318336
this.scrollLeft = e.scrollLeft;
337+
if (this.visibleRange) {
338+
StyleMutator.setTop(this.textArea.actual, this.visibleRange.top);
339+
StyleMutator.setLeft(this.textArea.actual, this.contentLeft + this.visibleRange.left - this.scrollLeft);
340+
}
319341
return false;
320342
}
321343

src/vs/editor/common/controller/textAreaHandler.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {RunOnceScheduler} from 'vs/base/common/async';
88
import Event, {Emitter} from 'vs/base/common/event';
99
import {CommonKeybindings} from 'vs/base/common/keyCodes';
1010
import {Disposable} from 'vs/base/common/lifecycle';
11-
import {IClipboardEvent, IKeyboardEventWrapper, ISimpleModel, ITextAreaWrapper, ITypeData, TextAreaState, TextAreaStrategy, createTextAreaState} from 'vs/editor/common/controller/textAreaState';
11+
import {IClipboardEvent, ICompositionEvent, IKeyboardEventWrapper, ISimpleModel, ITextAreaWrapper, ITypeData, TextAreaState, TextAreaStrategy, createTextAreaState} from 'vs/editor/common/controller/textAreaState';
1212
import {Position} from 'vs/editor/common/core/position';
1313
import {Range} from 'vs/editor/common/core/range';
1414
import {EndOfLinePreference, IEditorPosition, IEditorRange} from 'vs/editor/common/editorCommon';
@@ -56,8 +56,11 @@ export class TextAreaHandler extends Disposable {
5656
private _onCompositionStart = this._register(new Emitter<ICompositionStartData>());
5757
public onCompositionStart: Event<ICompositionStartData> = this._onCompositionStart.event;
5858

59-
private _onCompositionEnd = this._register(new Emitter<void>());
60-
public onCompositionEnd: Event<void> = this._onCompositionEnd.event;
59+
private _onCompositionUpdate = this._register(new Emitter<ICompositionEvent>());
60+
public onCompositionUpdate: Event<ICompositionEvent> = this._onCompositionUpdate.event;
61+
62+
private _onCompositionEnd = this._register(new Emitter<ICompositionEvent>());
63+
public onCompositionEnd: Event<ICompositionEvent> = this._onCompositionEnd.event;
6164

6265
private Browser:IBrowser;
6366
private textArea:ITextAreaWrapper;
@@ -108,16 +111,16 @@ export class TextAreaHandler extends Disposable {
108111

109112
this.textareaIsShownAtCursor = false;
110113

111-
this._register(this.textArea.onCompositionStart(() => {
112-
let timeSinceLastCompositionEnd = (new Date().getTime()) - this.lastCompositionEndTime;
114+
this._register(this.textArea.onCompositionStart((e) => {
115+
113116
if (this.textareaIsShownAtCursor) {
114117
return;
115118
}
116119

117120
this.textareaIsShownAtCursor = true;
118121

119122
// In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled.
120-
let shouldEmptyTextArea = (timeSinceLastCompositionEnd >= 100);
123+
let shouldEmptyTextArea = true;
121124
if (shouldEmptyTextArea) {
122125
if (!this.Browser.isIE11orEarlier) {
123126
this.setTextAreaState('compositionstart', this.textAreaState.toEmpty());
@@ -136,13 +139,19 @@ export class TextAreaHandler extends Disposable {
136139
showAtLineNumber = this.cursorPosition.lineNumber;
137140
showAtColumn = this.cursorPosition.column;
138141
}
139-
140142
this._onCompositionStart.fire({
141143
showAtLineNumber: showAtLineNumber,
142144
showAtColumn: showAtColumn
143145
});
144146
}));
145147

148+
this._register(this.textArea.onCompositionUpdate((e) => {
149+
this.textAreaState = this.textAreaState.fromText(e.data);
150+
let typeInput = this.textAreaState.updateComposition();
151+
this._onType.fire(typeInput);
152+
this._onCompositionUpdate.fire(e);
153+
}));
154+
146155
let readFromTextArea = () => {
147156
this.textAreaState = this.textAreaState.fromTextArea(this.textArea);
148157
let typeInput = this.textAreaState.deduceInput();
@@ -157,9 +166,11 @@ export class TextAreaHandler extends Disposable {
157166
}
158167
};
159168

160-
this._register(this.textArea.onCompositionEnd(() => {
161-
// console.log('onCompositionEnd: ' + this.textArea.getValue());
162-
// readFromTextArea();
169+
this._register(this.textArea.onCompositionEnd((e) => {
170+
// console.log('onCompositionEnd: ' + e.data);
171+
this.textAreaState = this.textAreaState.fromText(e.data);
172+
let typeInput = this.textAreaState.updateComposition();
173+
this._onType.fire(typeInput);
163174

164175
this.lastCompositionEndTime = (new Date()).getTime();
165176
if (!this.textareaIsShownAtCursor) {

src/vs/editor/common/controller/textAreaState.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export interface IClipboardEvent {
1515
getTextData(): string;
1616
}
1717

18+
export interface ICompositionEvent {
19+
data: string;
20+
locale: string;
21+
}
22+
1823
export interface IKeyboardEventWrapper {
1924
_actual: any;
2025
equals(keybinding:number): boolean;
@@ -26,8 +31,9 @@ export interface ITextAreaWrapper {
2631
onKeyDown: Event<IKeyboardEventWrapper>;
2732
onKeyUp: Event<IKeyboardEventWrapper>;
2833
onKeyPress: Event<IKeyboardEventWrapper>;
29-
onCompositionStart: Event<void>;
30-
onCompositionEnd: Event<void>;
34+
onCompositionStart: Event<ICompositionEvent>;
35+
onCompositionUpdate: Event<ICompositionEvent>;
36+
onCompositionEnd: Event<ICompositionEvent>;
3137
onInput: Event<void>;
3238
onCut: Event<IClipboardEvent>;
3339
onCopy: Event<IClipboardEvent>;
@@ -105,6 +111,21 @@ export abstract class TextAreaState {
105111

106112
public abstract fromText(text:string): TextAreaState;
107113

114+
public updateComposition(): ITypeData {
115+
if (!this.previousState) {
116+
// This is the EMPTY state
117+
return {
118+
text: '',
119+
replaceCharCnt: 0
120+
};
121+
}
122+
123+
return {
124+
text: this.value,
125+
replaceCharCnt: this.previousState.selectionEnd - this.previousState.selectionStart
126+
};
127+
}
128+
108129
public abstract resetSelection(): TextAreaState;
109130

110131
public getSelectionStart(): number {

src/vs/editor/test/common/mocks/mockTextAreaWrapper.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import Event, {Emitter} from 'vs/base/common/event';
88
import {Disposable} from 'vs/base/common/lifecycle';
9-
import {IClipboardEvent, IKeyboardEventWrapper, ITextAreaWrapper} from 'vs/editor/common/controller/textAreaState';
9+
import {IClipboardEvent, ICompositionEvent, IKeyboardEventWrapper, ITextAreaWrapper} from 'vs/editor/common/controller/textAreaState';
1010

1111
export class MockTextAreaWrapper extends Disposable implements ITextAreaWrapper {
1212

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

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

25-
private _onCompositionEnd = this._register(new Emitter<void>());
26-
public onCompositionEnd: Event<void> = this._onCompositionEnd.event;
25+
private _onCompositionUpdate = this._register(new Emitter<ICompositionEvent>());
26+
public onCompositionUpdate: Event<ICompositionEvent> = this._onCompositionUpdate.event;
27+
28+
private _onCompositionEnd = this._register(new Emitter<ICompositionEvent>());
29+
public onCompositionEnd: Event<ICompositionEvent> = this._onCompositionEnd.event;
2730

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

0 commit comments

Comments
 (0)