Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextState.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 { commonPrefixLength, commonSuffixLength } from '../../../../../base/common/strings.js';
7
import { Position } from '../../../../common/core/position.js';
8
import { Range } from '../../../../common/core/range.js';
9
import { ISimpleScreenReaderContentState } from '../screenReaderUtils.js';
10
11
export const _debugComposition = false;
12
13
export interface ITextAreaWrapper {
14
getValue(): string;
15
setValue(reason: string, value: string): void;
16
17
getSelectionStart(): number;
18
getSelectionEnd(): number;
19
setSelectionRange(reason: string, selectionStart: number, selectionEnd: number): void;
20
}
21
22
export interface ITypeData {
23
text: string;
24
replacePrevCharCnt: number;
25
replaceNextCharCnt: number;
26
positionDelta: number;
27
}
28
29
export class TextAreaState {
30
31
public static readonly EMPTY = new TextAreaState('', 0, 0, null, undefined);
32
33
constructor(
34
public readonly value: string,
35
/** the offset where selection starts inside `value` */
36
public readonly selectionStart: number,
37
/** the offset where selection ends inside `value` */
38
public readonly selectionEnd: number,
39
/** the editor range in the view coordinate system that matches the selection inside `value` */
40
public readonly selection: Range | null,
41
/** the visible line count (wrapped, not necessarily matching \n characters) for the text in `value` before `selectionStart` */
42
public readonly newlineCountBeforeSelection: number | undefined,
43
) { }
44
45
public toString(): string {
46
return `[ <${this.value}>, selectionStart: ${this.selectionStart}, selectionEnd: ${this.selectionEnd}]`;
47
}
48
49
public static readFromTextArea(textArea: ITextAreaWrapper, previousState: TextAreaState | null): TextAreaState {
50
const value = textArea.getValue();
51
const selectionStart = textArea.getSelectionStart();
52
const selectionEnd = textArea.getSelectionEnd();
53
let newlineCountBeforeSelection: number | undefined = undefined;
54
if (previousState) {
55
const valueBeforeSelectionStart = value.substring(0, selectionStart);
56
const previousValueBeforeSelectionStart = previousState.value.substring(0, previousState.selectionStart);
57
if (valueBeforeSelectionStart === previousValueBeforeSelectionStart) {
58
newlineCountBeforeSelection = previousState.newlineCountBeforeSelection;
59
}
60
}
61
return new TextAreaState(value, selectionStart, selectionEnd, null, newlineCountBeforeSelection);
62
}
63
64
public collapseSelection(): TextAreaState {
65
if (this.selectionStart === this.value.length) {
66
return this;
67
}
68
return new TextAreaState(this.value, this.value.length, this.value.length, null, undefined);
69
}
70
71
public isWrittenToTextArea(textArea: ITextAreaWrapper, select: boolean): boolean {
72
const valuesEqual = this.value === textArea.getValue();
73
if (!select) {
74
return valuesEqual;
75
}
76
const selectionsEqual = this.selectionStart === textArea.getSelectionStart() && this.selectionEnd === textArea.getSelectionEnd();
77
return selectionsEqual && valuesEqual;
78
}
79
80
public writeToTextArea(reason: string, textArea: ITextAreaWrapper, select: boolean): void {
81
if (_debugComposition) {
82
console.log(`writeToTextArea ${reason}: ${this.toString()}`);
83
}
84
textArea.setValue(reason, this.value);
85
if (select) {
86
textArea.setSelectionRange(reason, this.selectionStart, this.selectionEnd);
87
}
88
}
89
90
public deduceEditorPosition(offset: number): [Position | null, number, number] {
91
if (offset <= this.selectionStart) {
92
const str = this.value.substring(offset, this.selectionStart);
93
return this._finishDeduceEditorPosition(this.selection?.getStartPosition() ?? null, str, -1);
94
}
95
if (offset >= this.selectionEnd) {
96
const str = this.value.substring(this.selectionEnd, offset);
97
return this._finishDeduceEditorPosition(this.selection?.getEndPosition() ?? null, str, 1);
98
}
99
const str1 = this.value.substring(this.selectionStart, offset);
100
if (str1.indexOf(String.fromCharCode(8230)) === -1) {
101
return this._finishDeduceEditorPosition(this.selection?.getStartPosition() ?? null, str1, 1);
102
}
103
const str2 = this.value.substring(offset, this.selectionEnd);
104
return this._finishDeduceEditorPosition(this.selection?.getEndPosition() ?? null, str2, -1);
105
}
106
107
private _finishDeduceEditorPosition(anchor: Position | null, deltaText: string, signum: number): [Position | null, number, number] {
108
let lineFeedCnt = 0;
109
let lastLineFeedIndex = -1;
110
while ((lastLineFeedIndex = deltaText.indexOf('\n', lastLineFeedIndex + 1)) !== -1) {
111
lineFeedCnt++;
112
}
113
return [anchor, signum * deltaText.length, lineFeedCnt];
114
}
115
116
public static deduceInput(previousState: TextAreaState, currentState: TextAreaState, couldBeEmojiInput: boolean): ITypeData {
117
if (!previousState) {
118
// This is the EMPTY state
119
return {
120
text: '',
121
replacePrevCharCnt: 0,
122
replaceNextCharCnt: 0,
123
positionDelta: 0
124
};
125
}
126
127
if (_debugComposition) {
128
console.log('------------------------deduceInput');
129
console.log(`PREVIOUS STATE: ${previousState.toString()}`);
130
console.log(`CURRENT STATE: ${currentState.toString()}`);
131
}
132
133
const prefixLength = Math.min(
134
commonPrefixLength(previousState.value, currentState.value),
135
previousState.selectionStart,
136
currentState.selectionStart
137
);
138
const suffixLength = Math.min(
139
commonSuffixLength(previousState.value, currentState.value),
140
previousState.value.length - previousState.selectionEnd,
141
currentState.value.length - currentState.selectionEnd
142
);
143
const previousValue = previousState.value.substring(prefixLength, previousState.value.length - suffixLength);
144
const currentValue = currentState.value.substring(prefixLength, currentState.value.length - suffixLength);
145
const previousSelectionStart = previousState.selectionStart - prefixLength;
146
const previousSelectionEnd = previousState.selectionEnd - prefixLength;
147
const currentSelectionStart = currentState.selectionStart - prefixLength;
148
const currentSelectionEnd = currentState.selectionEnd - prefixLength;
149
150
if (_debugComposition) {
151
console.log(`AFTER DIFFING PREVIOUS STATE: <${previousValue}>, selectionStart: ${previousSelectionStart}, selectionEnd: ${previousSelectionEnd}`);
152
console.log(`AFTER DIFFING CURRENT STATE: <${currentValue}>, selectionStart: ${currentSelectionStart}, selectionEnd: ${currentSelectionEnd}`);
153
}
154
155
if (currentSelectionStart === currentSelectionEnd) {
156
// no current selection
157
const replacePreviousCharacters = (previousState.selectionStart - prefixLength);
158
if (_debugComposition) {
159
console.log(`REMOVE PREVIOUS: ${replacePreviousCharacters} chars`);
160
}
161
162
return {
163
text: currentValue,
164
replacePrevCharCnt: replacePreviousCharacters,
165
replaceNextCharCnt: 0,
166
positionDelta: 0
167
};
168
}
169
170
// there is a current selection => composition case
171
const replacePreviousCharacters = previousSelectionEnd - previousSelectionStart;
172
return {
173
text: currentValue,
174
replacePrevCharCnt: replacePreviousCharacters,
175
replaceNextCharCnt: 0,
176
positionDelta: 0
177
};
178
}
179
180
public static deduceAndroidCompositionInput(previousState: TextAreaState, currentState: TextAreaState): ITypeData {
181
if (!previousState) {
182
// This is the EMPTY state
183
return {
184
text: '',
185
replacePrevCharCnt: 0,
186
replaceNextCharCnt: 0,
187
positionDelta: 0
188
};
189
}
190
191
if (_debugComposition) {
192
console.log('------------------------deduceAndroidCompositionInput');
193
console.log(`PREVIOUS STATE: ${previousState.toString()}`);
194
console.log(`CURRENT STATE: ${currentState.toString()}`);
195
}
196
197
if (previousState.value === currentState.value) {
198
return {
199
text: '',
200
replacePrevCharCnt: 0,
201
replaceNextCharCnt: 0,
202
positionDelta: currentState.selectionEnd - previousState.selectionEnd
203
};
204
}
205
206
const prefixLength = Math.min(commonPrefixLength(previousState.value, currentState.value), previousState.selectionEnd);
207
const suffixLength = Math.min(commonSuffixLength(previousState.value, currentState.value), previousState.value.length - previousState.selectionEnd);
208
const previousValue = previousState.value.substring(prefixLength, previousState.value.length - suffixLength);
209
const currentValue = currentState.value.substring(prefixLength, currentState.value.length - suffixLength);
210
const previousSelectionStart = previousState.selectionStart - prefixLength;
211
const previousSelectionEnd = previousState.selectionEnd - prefixLength;
212
const currentSelectionStart = currentState.selectionStart - prefixLength;
213
const currentSelectionEnd = currentState.selectionEnd - prefixLength;
214
215
if (_debugComposition) {
216
console.log(`AFTER DIFFING PREVIOUS STATE: <${previousValue}>, selectionStart: ${previousSelectionStart}, selectionEnd: ${previousSelectionEnd}`);
217
console.log(`AFTER DIFFING CURRENT STATE: <${currentValue}>, selectionStart: ${currentSelectionStart}, selectionEnd: ${currentSelectionEnd}`);
218
}
219
220
return {
221
text: currentValue,
222
replacePrevCharCnt: previousSelectionEnd,
223
replaceNextCharCnt: previousValue.length - previousSelectionEnd,
224
positionDelta: currentSelectionEnd - currentValue.length
225
};
226
}
227
228
public static fromScreenReaderContentState(screenReaderContentState: ISimpleScreenReaderContentState) {
229
return new TextAreaState(
230
screenReaderContentState.value,
231
screenReaderContentState.selectionStart,
232
screenReaderContentState.selectionEnd,
233
screenReaderContentState.selection,
234
screenReaderContentState.newlineCountBeforeSelection
235
);
236
}
237
}
238
239