Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/test/browser/controller/imeTester.ts
3296 views
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
6
import { Position } from '../../../common/core/position.js';
7
import { IRange, Range } from '../../../common/core/range.js';
8
import { EndOfLinePreference } from '../../../common/model.js';
9
import * as dom from '../../../../base/browser/dom.js';
10
import * as browser from '../../../../base/browser/browser.js';
11
import * as platform from '../../../../base/common/platform.js';
12
import { mainWindow } from '../../../../base/browser/window.js';
13
import { TestAccessibilityService } from '../../../../platform/accessibility/test/common/testAccessibilityService.js';
14
import { NullLogService } from '../../../../platform/log/common/log.js';
15
import { SimplePagedScreenReaderStrategy } from '../../../browser/controller/editContext/screenReaderUtils.js';
16
import { ISimpleModel } from '../../../common/viewModel/screenReaderSimpleModel.js';
17
import { TextAreaState } from '../../../browser/controller/editContext/textArea/textAreaEditContextState.js';
18
import { ITextAreaInputHost, TextAreaInput, TextAreaWrapper } from '../../../browser/controller/editContext/textArea/textAreaEditContextInput.js';
19
import { Selection } from '../../../common/core/selection.js';
20
21
// To run this test, open imeTester.html
22
23
class SingleLineTestModel implements ISimpleModel {
24
25
private _line: string;
26
27
constructor(line: string) {
28
this._line = line;
29
}
30
31
_setText(text: string) {
32
this._line = text;
33
}
34
35
getLineMaxColumn(lineNumber: number): number {
36
return this._line.length + 1;
37
}
38
39
getValueInRange(range: IRange, eol: EndOfLinePreference): string {
40
return this._line.substring(range.startColumn - 1, range.endColumn - 1);
41
}
42
43
getValueLengthInRange(range: Range, eol: EndOfLinePreference): number {
44
return this.getValueInRange(range, eol).length;
45
}
46
47
modifyPosition(position: Position, offset: number): Position {
48
const column = Math.min(this.getLineMaxColumn(position.lineNumber), Math.max(1, position.column + offset));
49
return new Position(position.lineNumber, column);
50
}
51
52
getModelLineContent(lineNumber: number): string {
53
return this._line;
54
}
55
56
getLineCount(): number {
57
return 1;
58
}
59
}
60
61
class TestView {
62
63
private readonly _model: SingleLineTestModel;
64
65
constructor(model: SingleLineTestModel) {
66
this._model = model;
67
}
68
69
public paint(output: HTMLElement) {
70
dom.clearNode(output);
71
for (let i = 1; i <= this._model.getLineCount(); i++) {
72
const textNode = document.createTextNode(this._model.getModelLineContent(i));
73
output.appendChild(textNode);
74
const br = document.createElement('br');
75
output.appendChild(br);
76
}
77
}
78
}
79
80
function doCreateTest(description: string, inputStr: string, expectedStr: string): HTMLElement {
81
let cursorOffset: number = 0;
82
let cursorLength: number = 0;
83
84
const container = document.createElement('div');
85
container.className = 'container';
86
87
const title = document.createElement('div');
88
title.className = 'title';
89
90
const inputStrStrong = document.createElement('strong');
91
inputStrStrong.innerText = inputStr;
92
93
title.innerText = description + '. Type ';
94
title.appendChild(inputStrStrong);
95
96
container.appendChild(title);
97
98
const startBtn = document.createElement('button');
99
startBtn.innerText = 'Start';
100
container.appendChild(startBtn);
101
102
103
const input = document.createElement('textarea');
104
input.setAttribute('rows', '10');
105
input.setAttribute('cols', '40');
106
container.appendChild(input);
107
108
const model = new SingleLineTestModel('some text');
109
const screenReaderStrategy = new SimplePagedScreenReaderStrategy();
110
const textAreaInputHost: ITextAreaInputHost = {
111
getDataToCopy: () => {
112
return {
113
isFromEmptySelection: false,
114
multicursorText: null,
115
text: '',
116
html: undefined,
117
mode: null
118
};
119
},
120
getScreenReaderContent: (): TextAreaState => {
121
const selection = new Selection(1, 1 + cursorOffset, 1, 1 + cursorOffset + cursorLength);
122
123
const screenReaderContentState = screenReaderStrategy.fromEditorSelection(model, selection, 10, true);
124
return TextAreaState.fromScreenReaderContentState(screenReaderContentState);
125
},
126
deduceModelPosition: (viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position => {
127
return null!;
128
}
129
};
130
131
const handler = new TextAreaInput(textAreaInputHost, new TextAreaWrapper(input), platform.OS, {
132
isAndroid: browser.isAndroid,
133
isFirefox: browser.isFirefox,
134
isChrome: browser.isChrome,
135
isSafari: browser.isSafari,
136
}, new TestAccessibilityService(), new NullLogService());
137
138
const output = document.createElement('pre');
139
output.className = 'output';
140
container.appendChild(output);
141
142
const check = document.createElement('pre');
143
check.className = 'check';
144
container.appendChild(check);
145
146
const br = document.createElement('br');
147
br.style.clear = 'both';
148
container.appendChild(br);
149
150
const view = new TestView(model);
151
152
const updatePosition = (off: number, len: number) => {
153
cursorOffset = off;
154
cursorLength = len;
155
handler.writeNativeTextAreaContent('selection changed');
156
handler.focusTextArea();
157
};
158
159
const updateModelAndPosition = (text: string, off: number, len: number) => {
160
model._setText(text);
161
updatePosition(off, len);
162
view.paint(output);
163
164
const expected = 'some ' + expectedStr + ' text';
165
if (text === expected) {
166
check.innerText = '[GOOD]';
167
check.className = 'check good';
168
} else {
169
check.innerText = '[BAD]';
170
check.className = 'check bad';
171
}
172
check.appendChild(document.createTextNode(expected));
173
};
174
175
handler.onType((e) => {
176
console.log('type text: ' + e.text + ', replaceCharCnt: ' + e.replacePrevCharCnt);
177
const text = model.getModelLineContent(1);
178
const preText = text.substring(0, cursorOffset - e.replacePrevCharCnt);
179
const postText = text.substring(cursorOffset + cursorLength);
180
const midText = e.text;
181
182
updateModelAndPosition(preText + midText + postText, (preText + midText).length, 0);
183
});
184
185
view.paint(output);
186
187
startBtn.onclick = function () {
188
updateModelAndPosition('some text', 5, 0);
189
input.focus();
190
};
191
192
return container;
193
}
194
195
const TESTS = [
196
{ description: 'Japanese IME 1', in: 'sennsei [Enter]', out: 'せんせい' },
197
{ description: 'Japanese IME 2', in: 'konnichiha [Enter]', out: 'こんいちは' },
198
{ description: 'Japanese IME 3', in: 'mikann [Enter]', out: 'みかん' },
199
{ description: 'Korean IME 1', in: 'gksrmf [Space]', out: '한글 ' },
200
{ description: 'Chinese IME 1', in: '.,', out: '。,' },
201
{ description: 'Chinese IME 2', in: 'ni [Space] hao [Space]', out: '你好' },
202
{ description: 'Chinese IME 3', in: 'hazni [Space]', out: '哈祝你' },
203
{ description: 'Mac dead key 1', in: '`.', out: '`.' },
204
{ description: 'Mac hold key 1', in: 'e long press and 1', out: 'é' }
205
];
206
207
TESTS.forEach((t) => {
208
mainWindow.document.body.appendChild(doCreateTest(t.description, t.in, t.out));
209
});
210
211