blots-editor 0.0.10 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -163,8 +163,19 @@ export class Document {
|
|
|
163
163
|
/** Set the cursor, clamping out-of-range values (line to `[0, lineCount-1]`, column to end of line). */
|
|
164
164
|
setCursor(line, logical) {
|
|
165
165
|
const l = Math.max(0, Math.min(line, this._lines.length - 1));
|
|
166
|
-
const
|
|
167
|
-
|
|
166
|
+
const text = this._lines[l].text;
|
|
167
|
+
let col = Math.max(0, Math.min(logical, text.length));
|
|
168
|
+
// The cursor must never sit between the halves of a surrogate pair —
|
|
169
|
+
// typing there would split the emoji — so snap it to the pair start.
|
|
170
|
+
if (col > 0 &&
|
|
171
|
+
col < text.length &&
|
|
172
|
+
text.charCodeAt(col) >= 0xdc00 &&
|
|
173
|
+
text.charCodeAt(col) <= 0xdfff &&
|
|
174
|
+
text.charCodeAt(col - 1) >= 0xd800 &&
|
|
175
|
+
text.charCodeAt(col - 1) <= 0xdbff) {
|
|
176
|
+
col--;
|
|
177
|
+
}
|
|
178
|
+
this._cursor = { line: l, logical: col };
|
|
168
179
|
// Any cursor movement releases the manual view-scroll lock so the
|
|
169
180
|
// viewport follows the cursor again.
|
|
170
181
|
this._viewScrollTop = null;
|
|
@@ -189,13 +200,28 @@ export class Document {
|
|
|
189
200
|
moveLeft() {
|
|
190
201
|
const { line, logical } = this._cursor;
|
|
191
202
|
if (logical > 0) {
|
|
192
|
-
|
|
203
|
+
let target = logical - 1;
|
|
204
|
+
// Step over the whole emoji: stopping at its trailing half would
|
|
205
|
+
// leave the cursor inside the pair.
|
|
206
|
+
const ch = this._lines[line].text.charCodeAt(target);
|
|
207
|
+
if (target > 0 && ch >= 0xdc00 && ch <= 0xdfff) {
|
|
208
|
+
target--;
|
|
209
|
+
}
|
|
210
|
+
this.setCursor(line, target);
|
|
193
211
|
}
|
|
194
212
|
}
|
|
195
213
|
moveRight() {
|
|
196
214
|
const { line, logical } = this._cursor;
|
|
197
|
-
|
|
198
|
-
|
|
215
|
+
const text = this._lines[line].text;
|
|
216
|
+
if (logical < text.length) {
|
|
217
|
+
let target = logical + 1;
|
|
218
|
+
// Step over the whole emoji: stopping at its leading half would
|
|
219
|
+
// leave the cursor inside the pair.
|
|
220
|
+
const ch = text.charCodeAt(logical);
|
|
221
|
+
if (target < text.length && ch >= 0xd800 && ch <= 0xdbff) {
|
|
222
|
+
target++;
|
|
223
|
+
}
|
|
224
|
+
this.setCursor(line, target);
|
|
199
225
|
}
|
|
200
226
|
}
|
|
201
227
|
/** Vertical movement aligns by visual column, so wide-character lines do not drift. */
|
|
@@ -26,10 +26,15 @@ export class DeleteBeforeOp {
|
|
|
26
26
|
const { line, logical } = doc.cursor;
|
|
27
27
|
if (logical > 0) {
|
|
28
28
|
const cur = doc.getLine(line);
|
|
29
|
-
|
|
29
|
+
// Deleting only the trailing half of a surrogate pair would leave a
|
|
30
|
+
// broken half-rendering char, so delete the whole code point.
|
|
31
|
+
const last = cur.charCodeAt(logical - 1);
|
|
32
|
+
const isTrail = last >= 0xdc00 && last <= 0xdfff && logical >= 2;
|
|
33
|
+
const units = isTrail ? 2 : 1;
|
|
34
|
+
this._deletedChar = cur.slice(logical - units, logical);
|
|
30
35
|
this._joined = false;
|
|
31
|
-
doc.setLine(line, cur.slice(0, logical -
|
|
32
|
-
doc.setCursor(line, logical -
|
|
36
|
+
doc.setLine(line, cur.slice(0, logical - units) + cur.slice(logical));
|
|
37
|
+
doc.setCursor(line, logical - units);
|
|
33
38
|
}
|
|
34
39
|
else if (line > 0) {
|
|
35
40
|
const prev = doc.getLine(line - 1);
|
|
@@ -45,7 +50,7 @@ export class DeleteBeforeOp {
|
|
|
45
50
|
const { line, logical } = doc.cursor;
|
|
46
51
|
const cur = doc.getLine(line);
|
|
47
52
|
doc.setLine(line, cur.slice(0, logical) + this._deletedChar + cur.slice(logical));
|
|
48
|
-
doc.setCursor(line, logical +
|
|
53
|
+
doc.setCursor(line, logical + this._deletedChar.length);
|
|
49
54
|
}
|
|
50
55
|
else {
|
|
51
56
|
// Cursor sits at the end of the merged line; split it back at that point.
|
|
@@ -65,9 +70,14 @@ export class DeleteAfterOp {
|
|
|
65
70
|
const { line, logical } = doc.cursor;
|
|
66
71
|
const cur = doc.getLine(line);
|
|
67
72
|
if (logical < cur.length) {
|
|
68
|
-
|
|
73
|
+
// Deleting only the leading half of a surrogate pair would leave a
|
|
74
|
+
// broken half-rendering char, so delete the whole code point.
|
|
75
|
+
const first = cur.charCodeAt(logical);
|
|
76
|
+
const isLead = first >= 0xd800 && first <= 0xdbff && logical + 1 < cur.length;
|
|
77
|
+
const units = isLead ? 2 : 1;
|
|
78
|
+
this._deletedChar = cur.slice(logical, logical + units);
|
|
69
79
|
this._joined = false;
|
|
70
|
-
doc.setLine(line, cur.slice(0, logical) + cur.slice(logical +
|
|
80
|
+
doc.setLine(line, cur.slice(0, logical) + cur.slice(logical + units));
|
|
71
81
|
}
|
|
72
82
|
else if (line < doc.lineCount - 1) {
|
|
73
83
|
const next = doc.getLine(line + 1);
|