ep_vim 0.1.0 → 0.5.0
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.
- package/README.md +1 -1
- package/ep.json +1 -0
- package/package.json +8 -1
- package/static/js/index.js +564 -468
- package/static/js/vim-core.js +320 -0
- package/static/js/vim-core.test.js +658 -0
package/static/js/index.js
CHANGED
|
@@ -1,63 +1,58 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
clampLine,
|
|
5
|
+
clampChar,
|
|
6
|
+
getLineText,
|
|
7
|
+
firstNonBlank,
|
|
8
|
+
wordForward,
|
|
9
|
+
wordBackward,
|
|
10
|
+
wordEnd,
|
|
11
|
+
charSearchPos,
|
|
12
|
+
motionRange,
|
|
13
|
+
charMotionRange,
|
|
14
|
+
innerWordRange,
|
|
15
|
+
innerQuoteRange,
|
|
16
|
+
innerBracketRange,
|
|
17
|
+
getVisualSelection,
|
|
18
|
+
paragraphForward,
|
|
19
|
+
paragraphBackward,
|
|
20
|
+
getTextInRange,
|
|
21
|
+
} = require("./vim-core");
|
|
2
22
|
|
|
3
23
|
// --- State variables ---
|
|
4
24
|
|
|
5
|
-
let vimEnabled = localStorage.getItem(
|
|
25
|
+
let vimEnabled = localStorage.getItem("ep_vimEnabled") === "true";
|
|
6
26
|
let insertMode = false;
|
|
7
27
|
let visualMode = null;
|
|
8
28
|
let visualAnchor = null;
|
|
9
29
|
let visualCursor = null;
|
|
10
30
|
let pendingKey = null;
|
|
11
31
|
let pendingCount = null;
|
|
12
|
-
let countBuffer =
|
|
32
|
+
let countBuffer = "";
|
|
13
33
|
let register = null;
|
|
14
34
|
let marks = {};
|
|
15
35
|
let editorDoc = null;
|
|
16
36
|
let currentRep = null;
|
|
37
|
+
let desiredColumn = null;
|
|
38
|
+
let lastCharSearch = null;
|
|
17
39
|
|
|
18
|
-
|
|
40
|
+
const QUOTE_CHARS = new Set(['"', "'"]);
|
|
41
|
+
const BRACKET_CHARS = new Set(["(", ")", "{", "}", "[", "]"]);
|
|
19
42
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const clampChar = (char, lineText) => Math.max(0, Math.min(char, lineText.length - 1));
|
|
26
|
-
|
|
27
|
-
const getLineText = (rep, line) => rep.lines.atIndex(line).text;
|
|
28
|
-
|
|
29
|
-
const firstNonBlank = (lineText) => {
|
|
30
|
-
let i = 0;
|
|
31
|
-
while (i < lineText.length && isWhitespace(lineText[i])) i++;
|
|
32
|
-
return i;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const findCharForward = (lineText, startChar, targetChar, count) => {
|
|
36
|
-
let found = 0;
|
|
37
|
-
for (let i = startChar + 1; i < lineText.length; i++) {
|
|
38
|
-
if (lineText[i] === targetChar) {
|
|
39
|
-
found++;
|
|
40
|
-
if (found === count) return i;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return -1;
|
|
43
|
+
const innerTextObjectRange = (key, lineText, char) => {
|
|
44
|
+
if (key === "w") return innerWordRange(lineText, char);
|
|
45
|
+
if (QUOTE_CHARS.has(key)) return innerQuoteRange(lineText, char, key);
|
|
46
|
+
if (BRACKET_CHARS.has(key)) return innerBracketRange(lineText, char, key);
|
|
47
|
+
return null;
|
|
44
48
|
};
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
let found = 0;
|
|
48
|
-
for (let i = startChar - 1; i >= 0; i--) {
|
|
49
|
-
if (lineText[i] === targetChar) {
|
|
50
|
-
found++;
|
|
51
|
-
if (found === count) return i;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return -1;
|
|
55
|
-
};
|
|
50
|
+
// --- Count helpers ---
|
|
56
51
|
|
|
57
52
|
const consumeCount = () => {
|
|
58
|
-
if (countBuffer !==
|
|
53
|
+
if (countBuffer !== "") {
|
|
59
54
|
pendingCount = parseInt(countBuffer, 10);
|
|
60
|
-
countBuffer =
|
|
55
|
+
countBuffer = "";
|
|
61
56
|
} else if (pendingKey === null) {
|
|
62
57
|
pendingCount = null;
|
|
63
58
|
}
|
|
@@ -65,17 +60,17 @@ const consumeCount = () => {
|
|
|
65
60
|
|
|
66
61
|
const getCount = () => pendingCount || 1;
|
|
67
62
|
|
|
63
|
+
// --- Side-effectful helpers ---
|
|
64
|
+
|
|
68
65
|
const setRegister = (value) => {
|
|
69
66
|
register = value;
|
|
70
|
-
const text = Array.isArray(value) ? value.join(
|
|
67
|
+
const text = Array.isArray(value) ? value.join("\n") + "\n" : value;
|
|
71
68
|
navigator.clipboard.writeText(text).catch(() => {});
|
|
72
69
|
};
|
|
73
70
|
|
|
74
|
-
// --- Etherpad API wrappers ---
|
|
75
|
-
|
|
76
71
|
const moveCursor = (editorInfo, line, char) => {
|
|
77
72
|
const pos = [line, char];
|
|
78
|
-
editorInfo.ace_inCallStackIfNecessary(
|
|
73
|
+
editorInfo.ace_inCallStackIfNecessary("vim-move", () => {
|
|
79
74
|
editorInfo.ace_performSelectionChange(pos, pos, false);
|
|
80
75
|
editorInfo.ace_updateBrowserSelectionFromRep();
|
|
81
76
|
});
|
|
@@ -83,16 +78,16 @@ const moveCursor = (editorInfo, line, char) => {
|
|
|
83
78
|
|
|
84
79
|
const clearEmptyLineCursor = () => {
|
|
85
80
|
if (!editorDoc) return;
|
|
86
|
-
const old = editorDoc.querySelector(
|
|
87
|
-
if (old) old.classList.remove(
|
|
81
|
+
const old = editorDoc.querySelector(".vim-empty-line-cursor");
|
|
82
|
+
if (old) old.classList.remove("vim-empty-line-cursor");
|
|
88
83
|
};
|
|
89
84
|
|
|
90
85
|
const moveBlockCursor = (editorInfo, line, char) => {
|
|
91
86
|
clearEmptyLineCursor();
|
|
92
|
-
const lineText = currentRep ? getLineText(currentRep, line) :
|
|
87
|
+
const lineText = currentRep ? getLineText(currentRep, line) : "";
|
|
93
88
|
if (lineText.length === 0 && editorDoc) {
|
|
94
|
-
const lineDiv = editorDoc.body.querySelectorAll(
|
|
95
|
-
if (lineDiv) lineDiv.classList.add(
|
|
89
|
+
const lineDiv = editorDoc.body.querySelectorAll("div")[line];
|
|
90
|
+
if (lineDiv) lineDiv.classList.add("vim-empty-line-cursor");
|
|
96
91
|
selectRange(editorInfo, [line, 0], [line, 0]);
|
|
97
92
|
} else {
|
|
98
93
|
selectRange(editorInfo, [line, char], [line, char + 1]);
|
|
@@ -100,20 +95,20 @@ const moveBlockCursor = (editorInfo, line, char) => {
|
|
|
100
95
|
};
|
|
101
96
|
|
|
102
97
|
const selectRange = (editorInfo, start, end) => {
|
|
103
|
-
editorInfo.ace_inCallStackIfNecessary(
|
|
98
|
+
editorInfo.ace_inCallStackIfNecessary("vim-select", () => {
|
|
104
99
|
editorInfo.ace_performSelectionChange(start, end, false);
|
|
105
100
|
editorInfo.ace_updateBrowserSelectionFromRep();
|
|
106
101
|
});
|
|
107
102
|
};
|
|
108
103
|
|
|
109
104
|
const replaceRange = (editorInfo, start, end, text) => {
|
|
110
|
-
editorInfo.ace_inCallStackIfNecessary(
|
|
105
|
+
editorInfo.ace_inCallStackIfNecessary("vim-edit", () => {
|
|
111
106
|
editorInfo.ace_performDocumentReplaceRange(start, end, text);
|
|
112
107
|
});
|
|
113
108
|
};
|
|
114
109
|
|
|
115
110
|
const undo = (editorInfo) => {
|
|
116
|
-
editorInfo.ace_doUndoRedo(
|
|
111
|
+
editorInfo.ace_doUndoRedo("undo");
|
|
117
112
|
};
|
|
118
113
|
|
|
119
114
|
// --- Mode management ---
|
|
@@ -122,91 +117,30 @@ const setInsertMode = (value) => {
|
|
|
122
117
|
insertMode = value;
|
|
123
118
|
if (value) clearEmptyLineCursor();
|
|
124
119
|
if (editorDoc) {
|
|
125
|
-
editorDoc.body.classList.toggle(
|
|
120
|
+
editorDoc.body.classList.toggle("vim-insert-mode", value);
|
|
126
121
|
}
|
|
127
122
|
};
|
|
128
123
|
|
|
129
124
|
const setVisualMode = (value) => {
|
|
130
125
|
visualMode = value;
|
|
131
126
|
if (editorDoc) {
|
|
132
|
-
editorDoc.body.classList.toggle(
|
|
133
|
-
editorDoc.body.classList.toggle(
|
|
127
|
+
editorDoc.body.classList.toggle("vim-visual-line-mode", value === "line");
|
|
128
|
+
editorDoc.body.classList.toggle("vim-visual-char-mode", value === "char");
|
|
134
129
|
}
|
|
135
130
|
};
|
|
136
131
|
|
|
137
132
|
// --- Visual mode helpers ---
|
|
138
133
|
|
|
139
|
-
const getVisualSelection = (rep) => {
|
|
140
|
-
if (visualMode === 'line') {
|
|
141
|
-
const topLine = Math.min(visualAnchor[0], visualCursor[0]);
|
|
142
|
-
const bottomLine = Math.max(visualAnchor[0], visualCursor[0]);
|
|
143
|
-
const lineCount = rep.lines.length();
|
|
144
|
-
const start = [topLine, 0];
|
|
145
|
-
const end = bottomLine + 1 < lineCount
|
|
146
|
-
? [bottomLine + 1, 0]
|
|
147
|
-
: [bottomLine, getLineText(rep, bottomLine).length];
|
|
148
|
-
return [start, end];
|
|
149
|
-
}
|
|
150
|
-
if (visualAnchor[0] < visualCursor[0] ||
|
|
151
|
-
(visualAnchor[0] === visualCursor[0] && visualAnchor[1] <= visualCursor[1])) {
|
|
152
|
-
return [visualAnchor, visualCursor];
|
|
153
|
-
}
|
|
154
|
-
return [visualCursor, visualAnchor];
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const getTextInRange = (rep, start, end) => {
|
|
158
|
-
if (start[0] === end[0]) {
|
|
159
|
-
return getLineText(rep, start[0]).slice(start[1], end[1]);
|
|
160
|
-
}
|
|
161
|
-
const parts = [];
|
|
162
|
-
parts.push(getLineText(rep, start[0]).slice(start[1]));
|
|
163
|
-
for (let i = start[0] + 1; i < end[0]; i++) {
|
|
164
|
-
parts.push(getLineText(rep, i));
|
|
165
|
-
}
|
|
166
|
-
parts.push(getLineText(rep, end[0]).slice(0, end[1]));
|
|
167
|
-
return parts.join('\n');
|
|
168
|
-
};
|
|
169
|
-
|
|
170
134
|
const updateVisualSelection = (editorInfo, rep) => {
|
|
171
|
-
const [start, end] = getVisualSelection(
|
|
135
|
+
const [start, end] = getVisualSelection(
|
|
136
|
+
visualMode,
|
|
137
|
+
visualAnchor,
|
|
138
|
+
visualCursor,
|
|
139
|
+
rep,
|
|
140
|
+
);
|
|
172
141
|
selectRange(editorInfo, start, end);
|
|
173
142
|
};
|
|
174
143
|
|
|
175
|
-
// --- Word motion helpers ---
|
|
176
|
-
|
|
177
|
-
const wordForward = (lineText, startChar) => {
|
|
178
|
-
let pos = startChar;
|
|
179
|
-
if (pos < lineText.length && isWordChar(lineText[pos])) {
|
|
180
|
-
while (pos < lineText.length && isWordChar(lineText[pos])) pos++;
|
|
181
|
-
} else if (pos < lineText.length && !isWhitespace(lineText[pos])) {
|
|
182
|
-
while (pos < lineText.length && !isWordChar(lineText[pos]) && !isWhitespace(lineText[pos])) pos++;
|
|
183
|
-
}
|
|
184
|
-
while (pos < lineText.length && isWhitespace(lineText[pos])) pos++;
|
|
185
|
-
return pos;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const wordBackward = (lineText, startChar) => {
|
|
189
|
-
let pos = startChar - 1;
|
|
190
|
-
while (pos >= 0 && isWhitespace(lineText[pos])) pos--;
|
|
191
|
-
if (pos >= 0 && isWordChar(lineText[pos])) {
|
|
192
|
-
while (pos > 0 && isWordChar(lineText[pos - 1])) pos--;
|
|
193
|
-
} else {
|
|
194
|
-
while (pos > 0 && !isWordChar(lineText[pos - 1]) && !isWhitespace(lineText[pos - 1])) pos--;
|
|
195
|
-
}
|
|
196
|
-
return Math.max(0, pos);
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const wordEnd = (lineText, startChar) => {
|
|
200
|
-
let pos = startChar + 1;
|
|
201
|
-
while (pos < lineText.length && isWhitespace(lineText[pos])) pos++;
|
|
202
|
-
if (pos < lineText.length && isWordChar(lineText[pos])) {
|
|
203
|
-
while (pos + 1 < lineText.length && isWordChar(lineText[pos + 1])) pos++;
|
|
204
|
-
} else {
|
|
205
|
-
while (pos + 1 < lineText.length && !isWordChar(lineText[pos + 1]) && !isWhitespace(lineText[pos + 1])) pos++;
|
|
206
|
-
}
|
|
207
|
-
return pos;
|
|
208
|
-
};
|
|
209
|
-
|
|
210
144
|
// --- Visual mode key handler ---
|
|
211
145
|
|
|
212
146
|
const handleVisualKey = (rep, editorInfo, key) => {
|
|
@@ -214,11 +148,11 @@ const handleVisualKey = (rep, editorInfo, key) => {
|
|
|
214
148
|
const curChar = visualCursor[1];
|
|
215
149
|
const lineText = getLineText(rep, curLine);
|
|
216
150
|
|
|
217
|
-
if (key >=
|
|
151
|
+
if (key >= "1" && key <= "9") {
|
|
218
152
|
countBuffer += key;
|
|
219
153
|
return true;
|
|
220
154
|
}
|
|
221
|
-
if (key ===
|
|
155
|
+
if (key === "0" && countBuffer !== "") {
|
|
222
156
|
countBuffer += key;
|
|
223
157
|
return true;
|
|
224
158
|
}
|
|
@@ -226,33 +160,30 @@ const handleVisualKey = (rep, editorInfo, key) => {
|
|
|
226
160
|
consumeCount();
|
|
227
161
|
const count = getCount();
|
|
228
162
|
|
|
229
|
-
if (
|
|
163
|
+
if (
|
|
164
|
+
pendingKey === "f" ||
|
|
165
|
+
pendingKey === "F" ||
|
|
166
|
+
pendingKey === "t" ||
|
|
167
|
+
pendingKey === "T"
|
|
168
|
+
) {
|
|
230
169
|
const direction = pendingKey;
|
|
231
170
|
pendingKey = null;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
pos = findCharForward(lineText, curChar, key, count);
|
|
235
|
-
} else if (direction === 'F') {
|
|
236
|
-
pos = findCharBackward(lineText, curChar, key, count);
|
|
237
|
-
} else if (direction === 't') {
|
|
238
|
-
pos = findCharForward(lineText, curChar, key, count);
|
|
239
|
-
if (pos !== -1) pos = pos - 1;
|
|
240
|
-
} else if (direction === 'T') {
|
|
241
|
-
pos = findCharBackward(lineText, curChar, key, count);
|
|
242
|
-
if (pos !== -1) pos = pos + 1;
|
|
243
|
-
}
|
|
171
|
+
lastCharSearch = { direction, target: key };
|
|
172
|
+
const pos = charSearchPos(direction, lineText, curChar, key, count);
|
|
244
173
|
if (pos !== -1) {
|
|
174
|
+
desiredColumn = null;
|
|
245
175
|
visualCursor = [curLine, pos];
|
|
246
176
|
updateVisualSelection(editorInfo, rep);
|
|
247
177
|
}
|
|
248
178
|
return true;
|
|
249
179
|
}
|
|
250
180
|
|
|
251
|
-
if (pendingKey === "'" || pendingKey ===
|
|
181
|
+
if (pendingKey === "'" || pendingKey === "`") {
|
|
252
182
|
const jumpType = pendingKey;
|
|
253
183
|
pendingKey = null;
|
|
254
|
-
if (key >=
|
|
184
|
+
if (key >= "a" && key <= "z" && marks[key]) {
|
|
255
185
|
const [markLine, markChar] = marks[key];
|
|
186
|
+
desiredColumn = null;
|
|
256
187
|
if (jumpType === "'") {
|
|
257
188
|
const targetLineText = getLineText(rep, markLine);
|
|
258
189
|
visualCursor = [markLine, firstNonBlank(targetLineText)];
|
|
@@ -264,49 +195,65 @@ const handleVisualKey = (rep, editorInfo, key) => {
|
|
|
264
195
|
return true;
|
|
265
196
|
}
|
|
266
197
|
|
|
267
|
-
if (key ===
|
|
198
|
+
if (key === "h") {
|
|
199
|
+
desiredColumn = null;
|
|
268
200
|
visualCursor = [curLine, Math.max(0, curChar - count)];
|
|
269
201
|
updateVisualSelection(editorInfo, rep);
|
|
270
202
|
return true;
|
|
271
203
|
}
|
|
272
204
|
|
|
273
|
-
if (key ===
|
|
205
|
+
if (key === "l") {
|
|
206
|
+
desiredColumn = null;
|
|
274
207
|
visualCursor = [curLine, clampChar(curChar + count, lineText)];
|
|
275
208
|
updateVisualSelection(editorInfo, rep);
|
|
276
209
|
return true;
|
|
277
210
|
}
|
|
278
211
|
|
|
279
|
-
if (key ===
|
|
280
|
-
|
|
212
|
+
if (key === "j") {
|
|
213
|
+
if (desiredColumn === null) {
|
|
214
|
+
desiredColumn = curChar;
|
|
215
|
+
}
|
|
216
|
+
const newLine = clampLine(curLine + count, rep);
|
|
217
|
+
const newLineText = getLineText(rep, newLine);
|
|
218
|
+
visualCursor = [newLine, clampChar(desiredColumn, newLineText)];
|
|
281
219
|
updateVisualSelection(editorInfo, rep);
|
|
282
220
|
return true;
|
|
283
221
|
}
|
|
284
222
|
|
|
285
|
-
if (key ===
|
|
286
|
-
|
|
223
|
+
if (key === "k") {
|
|
224
|
+
if (desiredColumn === null) {
|
|
225
|
+
desiredColumn = curChar;
|
|
226
|
+
}
|
|
227
|
+
const newLine = clampLine(curLine - count, rep);
|
|
228
|
+
const newLineText = getLineText(rep, newLine);
|
|
229
|
+
visualCursor = [newLine, clampChar(desiredColumn, newLineText)];
|
|
287
230
|
updateVisualSelection(editorInfo, rep);
|
|
288
231
|
return true;
|
|
289
232
|
}
|
|
290
233
|
|
|
291
|
-
if (key ===
|
|
234
|
+
if (key === "0") {
|
|
235
|
+
desiredColumn = null;
|
|
292
236
|
visualCursor = [curLine, 0];
|
|
293
237
|
updateVisualSelection(editorInfo, rep);
|
|
294
238
|
return true;
|
|
295
239
|
}
|
|
296
240
|
|
|
297
|
-
if (key ===
|
|
241
|
+
if (key === "$") {
|
|
242
|
+
desiredColumn = null;
|
|
298
243
|
visualCursor = [curLine, clampChar(lineText.length - 1, lineText)];
|
|
299
244
|
updateVisualSelection(editorInfo, rep);
|
|
300
245
|
return true;
|
|
301
246
|
}
|
|
302
247
|
|
|
303
|
-
if (key ===
|
|
248
|
+
if (key === "^") {
|
|
249
|
+
desiredColumn = null;
|
|
304
250
|
visualCursor = [curLine, firstNonBlank(lineText)];
|
|
305
251
|
updateVisualSelection(editorInfo, rep);
|
|
306
252
|
return true;
|
|
307
253
|
}
|
|
308
254
|
|
|
309
|
-
if (key ===
|
|
255
|
+
if (key === "w") {
|
|
256
|
+
desiredColumn = null;
|
|
310
257
|
let pos = curChar;
|
|
311
258
|
for (let i = 0; i < count; i++) pos = wordForward(lineText, pos);
|
|
312
259
|
visualCursor = [curLine, clampChar(pos, lineText)];
|
|
@@ -314,7 +261,8 @@ const handleVisualKey = (rep, editorInfo, key) => {
|
|
|
314
261
|
return true;
|
|
315
262
|
}
|
|
316
263
|
|
|
317
|
-
if (key ===
|
|
264
|
+
if (key === "b") {
|
|
265
|
+
desiredColumn = null;
|
|
318
266
|
let pos = curChar;
|
|
319
267
|
for (let i = 0; i < count; i++) pos = wordBackward(lineText, pos);
|
|
320
268
|
visualCursor = [curLine, pos];
|
|
@@ -322,7 +270,8 @@ const handleVisualKey = (rep, editorInfo, key) => {
|
|
|
322
270
|
return true;
|
|
323
271
|
}
|
|
324
272
|
|
|
325
|
-
if (key ===
|
|
273
|
+
if (key === "e") {
|
|
274
|
+
desiredColumn = null;
|
|
326
275
|
let pos = curChar;
|
|
327
276
|
for (let i = 0; i < count; i++) pos = wordEnd(lineText, pos);
|
|
328
277
|
visualCursor = [curLine, clampChar(pos, lineText)];
|
|
@@ -330,8 +279,63 @@ const handleVisualKey = (rep, editorInfo, key) => {
|
|
|
330
279
|
return true;
|
|
331
280
|
}
|
|
332
281
|
|
|
333
|
-
if (key ===
|
|
282
|
+
if (key === ";") {
|
|
283
|
+
if (lastCharSearch) {
|
|
284
|
+
const pos = charSearchPos(
|
|
285
|
+
lastCharSearch.direction,
|
|
286
|
+
lineText,
|
|
287
|
+
curChar,
|
|
288
|
+
lastCharSearch.target,
|
|
289
|
+
count,
|
|
290
|
+
);
|
|
291
|
+
if (pos !== -1) {
|
|
292
|
+
desiredColumn = null;
|
|
293
|
+
visualCursor = [curLine, pos];
|
|
294
|
+
updateVisualSelection(editorInfo, rep);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (key === ",") {
|
|
301
|
+
if (lastCharSearch) {
|
|
302
|
+
const opposite = { f: "F", F: "f", t: "T", T: "t" };
|
|
303
|
+
const reverseDir = opposite[lastCharSearch.direction];
|
|
304
|
+
const pos = charSearchPos(
|
|
305
|
+
reverseDir,
|
|
306
|
+
lineText,
|
|
307
|
+
curChar,
|
|
308
|
+
lastCharSearch.target,
|
|
309
|
+
count,
|
|
310
|
+
);
|
|
311
|
+
if (pos !== -1) {
|
|
312
|
+
desiredColumn = null;
|
|
313
|
+
visualCursor = [curLine, pos];
|
|
314
|
+
updateVisualSelection(editorInfo, rep);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (key === "}") {
|
|
321
|
+
desiredColumn = null;
|
|
322
|
+
const target = paragraphForward(rep, curLine, count);
|
|
323
|
+
visualCursor = [target, 0];
|
|
324
|
+
updateVisualSelection(editorInfo, rep);
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (key === "{") {
|
|
329
|
+
desiredColumn = null;
|
|
330
|
+
const target = paragraphBackward(rep, curLine, count);
|
|
331
|
+
visualCursor = [target, 0];
|
|
332
|
+
updateVisualSelection(editorInfo, rep);
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (key === "G") {
|
|
334
337
|
pendingKey = null;
|
|
338
|
+
desiredColumn = null;
|
|
335
339
|
if (pendingCount !== null) {
|
|
336
340
|
visualCursor = [clampLine(pendingCount - 1, rep), curChar];
|
|
337
341
|
} else {
|
|
@@ -341,32 +345,62 @@ const handleVisualKey = (rep, editorInfo, key) => {
|
|
|
341
345
|
return true;
|
|
342
346
|
}
|
|
343
347
|
|
|
344
|
-
if (key ===
|
|
345
|
-
if (pendingKey ===
|
|
348
|
+
if (key === "g") {
|
|
349
|
+
if (pendingKey === "g") {
|
|
346
350
|
pendingKey = null;
|
|
351
|
+
desiredColumn = null;
|
|
347
352
|
visualCursor = [0, curChar];
|
|
348
353
|
updateVisualSelection(editorInfo, rep);
|
|
349
354
|
} else {
|
|
350
|
-
pendingKey =
|
|
355
|
+
pendingKey = "g";
|
|
351
356
|
}
|
|
352
357
|
return true;
|
|
353
358
|
}
|
|
354
359
|
|
|
355
|
-
if (key ===
|
|
360
|
+
if (key === "f" || key === "F" || key === "t" || key === "T") {
|
|
356
361
|
pendingKey = key;
|
|
357
362
|
return true;
|
|
358
363
|
}
|
|
359
364
|
|
|
360
|
-
if (key === "'" || key ===
|
|
365
|
+
if (key === "'" || key === "`") {
|
|
361
366
|
pendingKey = key;
|
|
362
367
|
return true;
|
|
363
368
|
}
|
|
364
369
|
|
|
365
|
-
if (key ===
|
|
366
|
-
const [start] = getVisualSelection(
|
|
370
|
+
if (key === "~") {
|
|
371
|
+
const [start, end] = getVisualSelection(
|
|
372
|
+
visualMode,
|
|
373
|
+
visualAnchor,
|
|
374
|
+
visualCursor,
|
|
375
|
+
rep,
|
|
376
|
+
);
|
|
377
|
+
const text = getTextInRange(rep, start, end);
|
|
378
|
+
let toggled = "";
|
|
379
|
+
for (let i = 0; i < text.length; i++) {
|
|
380
|
+
const ch = text[i];
|
|
381
|
+
toggled += ch === ch.toLowerCase() ? ch.toUpperCase() : ch.toLowerCase();
|
|
382
|
+
}
|
|
383
|
+
replaceRange(editorInfo, start, end, toggled);
|
|
384
|
+
setVisualMode(null);
|
|
385
|
+
moveBlockCursor(editorInfo, start[0], start[1]);
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (key === "y") {
|
|
390
|
+
const [start] = getVisualSelection(
|
|
391
|
+
visualMode,
|
|
392
|
+
visualAnchor,
|
|
393
|
+
visualCursor,
|
|
394
|
+
rep,
|
|
395
|
+
);
|
|
367
396
|
|
|
368
|
-
if (visualMode ===
|
|
369
|
-
const [, end] = getVisualSelection(
|
|
397
|
+
if (visualMode === "char") {
|
|
398
|
+
const [, end] = getVisualSelection(
|
|
399
|
+
visualMode,
|
|
400
|
+
visualAnchor,
|
|
401
|
+
visualCursor,
|
|
402
|
+
rep,
|
|
403
|
+
);
|
|
370
404
|
setRegister(getTextInRange(rep, start, end));
|
|
371
405
|
setVisualMode(null);
|
|
372
406
|
moveBlockCursor(editorInfo, start[0], start[1]);
|
|
@@ -385,13 +419,18 @@ const handleVisualKey = (rep, editorInfo, key) => {
|
|
|
385
419
|
return true;
|
|
386
420
|
}
|
|
387
421
|
|
|
388
|
-
if (key ===
|
|
389
|
-
const enterInsert = key ===
|
|
390
|
-
const [start, end] = getVisualSelection(
|
|
422
|
+
if (key === "d" || key === "c") {
|
|
423
|
+
const enterInsert = key === "c";
|
|
424
|
+
const [start, end] = getVisualSelection(
|
|
425
|
+
visualMode,
|
|
426
|
+
visualAnchor,
|
|
427
|
+
visualCursor,
|
|
428
|
+
rep,
|
|
429
|
+
);
|
|
391
430
|
|
|
392
|
-
if (visualMode ===
|
|
431
|
+
if (visualMode === "char") {
|
|
393
432
|
setRegister(getTextInRange(rep, start, end));
|
|
394
|
-
replaceRange(editorInfo, start, end,
|
|
433
|
+
replaceRange(editorInfo, start, end, "");
|
|
395
434
|
if (enterInsert) {
|
|
396
435
|
moveCursor(editorInfo, start[0], start[1]);
|
|
397
436
|
setVisualMode(null);
|
|
@@ -415,7 +454,7 @@ const handleVisualKey = (rep, editorInfo, key) => {
|
|
|
415
454
|
if (enterInsert) {
|
|
416
455
|
for (let i = topLine; i <= bottomLine; i++) {
|
|
417
456
|
const text = getLineText(rep, i);
|
|
418
|
-
replaceRange(editorInfo, [topLine, 0], [topLine, text.length],
|
|
457
|
+
replaceRange(editorInfo, [topLine, 0], [topLine, text.length], "");
|
|
419
458
|
}
|
|
420
459
|
moveCursor(editorInfo, topLine, 0);
|
|
421
460
|
setVisualMode(null);
|
|
@@ -425,13 +464,23 @@ const handleVisualKey = (rep, editorInfo, key) => {
|
|
|
425
464
|
|
|
426
465
|
if (bottomLine === totalLines - 1 && topLine > 0) {
|
|
427
466
|
const prevLineLen = getLineText(rep, topLine - 1).length;
|
|
428
|
-
replaceRange(
|
|
467
|
+
replaceRange(
|
|
468
|
+
editorInfo,
|
|
469
|
+
[topLine - 1, prevLineLen],
|
|
470
|
+
[bottomLine, getLineText(rep, bottomLine).length],
|
|
471
|
+
"",
|
|
472
|
+
);
|
|
429
473
|
moveBlockCursor(editorInfo, topLine - 1, 0);
|
|
430
474
|
} else if (bottomLine < totalLines - 1) {
|
|
431
|
-
replaceRange(editorInfo, [topLine, 0], [bottomLine + 1, 0],
|
|
475
|
+
replaceRange(editorInfo, [topLine, 0], [bottomLine + 1, 0], "");
|
|
432
476
|
moveBlockCursor(editorInfo, topLine, 0);
|
|
433
477
|
} else {
|
|
434
|
-
replaceRange(
|
|
478
|
+
replaceRange(
|
|
479
|
+
editorInfo,
|
|
480
|
+
[0, 0],
|
|
481
|
+
[bottomLine, getLineText(rep, bottomLine).length],
|
|
482
|
+
"",
|
|
483
|
+
);
|
|
435
484
|
moveBlockCursor(editorInfo, 0, 0);
|
|
436
485
|
}
|
|
437
486
|
|
|
@@ -450,11 +499,11 @@ const handleNormalKey = (rep, editorInfo, key) => {
|
|
|
450
499
|
const lineCount = rep.lines.length();
|
|
451
500
|
const lineText = getLineText(rep, line);
|
|
452
501
|
|
|
453
|
-
if (key >=
|
|
502
|
+
if (key >= "1" && key <= "9") {
|
|
454
503
|
countBuffer += key;
|
|
455
504
|
return true;
|
|
456
505
|
}
|
|
457
|
-
if (key ===
|
|
506
|
+
if (key === "0" && countBuffer !== "") {
|
|
458
507
|
countBuffer += key;
|
|
459
508
|
return true;
|
|
460
509
|
}
|
|
@@ -462,7 +511,7 @@ const handleNormalKey = (rep, editorInfo, key) => {
|
|
|
462
511
|
consumeCount();
|
|
463
512
|
const count = getCount();
|
|
464
513
|
|
|
465
|
-
if (pendingKey ===
|
|
514
|
+
if (pendingKey === "r") {
|
|
466
515
|
pendingKey = null;
|
|
467
516
|
if (lineText.length > 0) {
|
|
468
517
|
replaceRange(editorInfo, [line, char], [line, char + 1], key);
|
|
@@ -471,64 +520,61 @@ const handleNormalKey = (rep, editorInfo, key) => {
|
|
|
471
520
|
return true;
|
|
472
521
|
}
|
|
473
522
|
|
|
474
|
-
if (
|
|
523
|
+
if (
|
|
524
|
+
pendingKey === "f" ||
|
|
525
|
+
pendingKey === "F" ||
|
|
526
|
+
pendingKey === "t" ||
|
|
527
|
+
pendingKey === "T"
|
|
528
|
+
) {
|
|
475
529
|
const direction = pendingKey;
|
|
476
530
|
pendingKey = null;
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
} else if (direction === 't') {
|
|
483
|
-
pos = findCharForward(lineText, char, key, count);
|
|
484
|
-
if (pos !== -1) pos = pos - 1;
|
|
485
|
-
} else if (direction === 'T') {
|
|
486
|
-
pos = findCharBackward(lineText, char, key, count);
|
|
487
|
-
if (pos !== -1) pos = pos + 1;
|
|
531
|
+
lastCharSearch = { direction, target: key };
|
|
532
|
+
const pos = charSearchPos(direction, lineText, char, key, count);
|
|
533
|
+
if (pos !== -1) {
|
|
534
|
+
desiredColumn = null;
|
|
535
|
+
moveBlockCursor(editorInfo, line, pos);
|
|
488
536
|
}
|
|
489
|
-
if (pos !== -1) moveBlockCursor(editorInfo, line, pos);
|
|
490
537
|
return true;
|
|
491
538
|
}
|
|
492
539
|
|
|
493
|
-
if (
|
|
540
|
+
if (
|
|
541
|
+
pendingKey === "df" ||
|
|
542
|
+
pendingKey === "dF" ||
|
|
543
|
+
pendingKey === "dt" ||
|
|
544
|
+
pendingKey === "dT"
|
|
545
|
+
) {
|
|
494
546
|
const motion = pendingKey[1];
|
|
495
547
|
pendingKey = null;
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
pos = findCharForward(lineText, char, key, count);
|
|
499
|
-
} else {
|
|
500
|
-
pos = findCharBackward(lineText, char, key, count);
|
|
501
|
-
}
|
|
548
|
+
const searchDir = motion === "f" || motion === "t" ? motion : motion;
|
|
549
|
+
const pos = charSearchPos(searchDir, lineText, char, key, count);
|
|
502
550
|
if (pos !== -1) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
delEnd = pos + 1;
|
|
508
|
-
} else if (motion === 't') {
|
|
509
|
-
delStart = char;
|
|
510
|
-
delEnd = pos;
|
|
511
|
-
} else if (motion === 'F') {
|
|
512
|
-
delStart = pos;
|
|
513
|
-
delEnd = char + 1;
|
|
514
|
-
} else if (motion === 'T') {
|
|
515
|
-
delStart = pos + 1;
|
|
516
|
-
delEnd = char + 1;
|
|
517
|
-
}
|
|
518
|
-
if (delEnd > delStart) {
|
|
519
|
-
setRegister(lineText.slice(delStart, delEnd));
|
|
520
|
-
replaceRange(editorInfo, [line, delStart], [line, delEnd], '');
|
|
551
|
+
const range = charMotionRange(motion, char, pos);
|
|
552
|
+
if (range) {
|
|
553
|
+
setRegister(lineText.slice(range.start, range.end));
|
|
554
|
+
replaceRange(editorInfo, [line, range.start], [line, range.end], "");
|
|
521
555
|
const newLineText = getLineText(rep, line);
|
|
522
|
-
moveBlockCursor(editorInfo, line, clampChar(
|
|
556
|
+
moveBlockCursor(editorInfo, line, clampChar(range.start, newLineText));
|
|
523
557
|
}
|
|
524
558
|
}
|
|
525
559
|
return true;
|
|
526
560
|
}
|
|
527
561
|
|
|
528
|
-
if (pendingKey ===
|
|
562
|
+
if (pendingKey === "di") {
|
|
563
|
+
pendingKey = null;
|
|
564
|
+
const range = innerTextObjectRange(key, lineText, char);
|
|
565
|
+
if (range) {
|
|
566
|
+
setRegister(lineText.slice(range.start, range.end));
|
|
567
|
+
replaceRange(editorInfo, [line, range.start], [line, range.end], "");
|
|
568
|
+
const newLineText = getLineText(rep, line);
|
|
569
|
+
moveBlockCursor(editorInfo, line, clampChar(range.start, newLineText));
|
|
570
|
+
}
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (pendingKey === "d") {
|
|
529
575
|
pendingKey = null;
|
|
530
576
|
|
|
531
|
-
if (key ===
|
|
577
|
+
if (key === "d") {
|
|
532
578
|
const deleteCount = Math.min(count, lineCount - line);
|
|
533
579
|
const lastDeleteLine = line + deleteCount - 1;
|
|
534
580
|
const deletedLines = [];
|
|
@@ -538,201 +584,144 @@ const handleNormalKey = (rep, editorInfo, key) => {
|
|
|
538
584
|
setRegister(deletedLines);
|
|
539
585
|
if (lastDeleteLine === lineCount - 1 && line > 0) {
|
|
540
586
|
const prevLineText = getLineText(rep, line - 1);
|
|
541
|
-
replaceRange(
|
|
587
|
+
replaceRange(
|
|
588
|
+
editorInfo,
|
|
589
|
+
[line - 1, prevLineText.length],
|
|
590
|
+
[lastDeleteLine, getLineText(rep, lastDeleteLine).length],
|
|
591
|
+
"",
|
|
592
|
+
);
|
|
542
593
|
moveBlockCursor(editorInfo, line - 1, clampChar(char, prevLineText));
|
|
543
594
|
} else if (lineCount > deleteCount) {
|
|
544
|
-
replaceRange(editorInfo, [line, 0], [lastDeleteLine + 1, 0],
|
|
595
|
+
replaceRange(editorInfo, [line, 0], [lastDeleteLine + 1, 0], "");
|
|
545
596
|
const newLineText = getLineText(rep, line);
|
|
546
597
|
moveBlockCursor(editorInfo, line, clampChar(char, newLineText));
|
|
547
598
|
} else {
|
|
548
|
-
replaceRange(
|
|
599
|
+
replaceRange(
|
|
600
|
+
editorInfo,
|
|
601
|
+
[0, 0],
|
|
602
|
+
[lastDeleteLine, getLineText(rep, lastDeleteLine).length],
|
|
603
|
+
"",
|
|
604
|
+
);
|
|
549
605
|
moveBlockCursor(editorInfo, 0, 0);
|
|
550
606
|
}
|
|
551
607
|
return true;
|
|
552
608
|
}
|
|
553
609
|
|
|
554
|
-
if (key ===
|
|
555
|
-
pendingKey =
|
|
610
|
+
if (key === "i") {
|
|
611
|
+
pendingKey = "di";
|
|
556
612
|
return true;
|
|
557
613
|
}
|
|
558
614
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
if (key === 'w') {
|
|
563
|
-
let pos = char;
|
|
564
|
-
for (let i = 0; i < count; i++) pos = wordForward(lineText, pos);
|
|
565
|
-
delStart = char;
|
|
566
|
-
delEnd = Math.min(pos, lineText.length);
|
|
567
|
-
} else if (key === 'e') {
|
|
568
|
-
let pos = char;
|
|
569
|
-
for (let i = 0; i < count; i++) pos = wordEnd(lineText, pos);
|
|
570
|
-
delStart = char;
|
|
571
|
-
delEnd = Math.min(pos + 1, lineText.length);
|
|
572
|
-
} else if (key === 'b') {
|
|
573
|
-
let pos = char;
|
|
574
|
-
for (let i = 0; i < count; i++) pos = wordBackward(lineText, pos);
|
|
575
|
-
delStart = pos;
|
|
576
|
-
delEnd = char;
|
|
577
|
-
} else if (key === '$') {
|
|
578
|
-
delStart = char;
|
|
579
|
-
delEnd = lineText.length;
|
|
580
|
-
} else if (key === '0') {
|
|
581
|
-
delStart = 0;
|
|
582
|
-
delEnd = char;
|
|
583
|
-
} else if (key === '^') {
|
|
584
|
-
const fnb = firstNonBlank(lineText);
|
|
585
|
-
delStart = Math.min(char, fnb);
|
|
586
|
-
delEnd = Math.max(char, fnb);
|
|
587
|
-
} else if (key === 'h') {
|
|
588
|
-
delStart = Math.max(0, char - count);
|
|
589
|
-
delEnd = char;
|
|
590
|
-
} else if (key === 'l') {
|
|
591
|
-
delStart = char;
|
|
592
|
-
delEnd = Math.min(char + count, lineText.length);
|
|
615
|
+
if (key === "f" || key === "F" || key === "t" || key === "T") {
|
|
616
|
+
pendingKey = "d" + key;
|
|
617
|
+
return true;
|
|
593
618
|
}
|
|
594
619
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
620
|
+
const range = motionRange(key, char, lineText, count);
|
|
621
|
+
if (range && range.end > range.start) {
|
|
622
|
+
setRegister(lineText.slice(range.start, range.end));
|
|
623
|
+
replaceRange(editorInfo, [line, range.start], [line, range.end], "");
|
|
598
624
|
const newLineText = getLineText(rep, line);
|
|
599
|
-
moveBlockCursor(editorInfo, line, clampChar(
|
|
625
|
+
moveBlockCursor(editorInfo, line, clampChar(range.start, newLineText));
|
|
600
626
|
}
|
|
601
627
|
return true;
|
|
602
628
|
}
|
|
603
629
|
|
|
604
|
-
if (
|
|
630
|
+
if (
|
|
631
|
+
pendingKey === "yf" ||
|
|
632
|
+
pendingKey === "yF" ||
|
|
633
|
+
pendingKey === "yt" ||
|
|
634
|
+
pendingKey === "yT"
|
|
635
|
+
) {
|
|
605
636
|
const motion = pendingKey[1];
|
|
606
637
|
pendingKey = null;
|
|
607
|
-
|
|
608
|
-
if (motion === 'f' || motion === 't') {
|
|
609
|
-
pos = findCharForward(lineText, char, key, count);
|
|
610
|
-
} else {
|
|
611
|
-
pos = findCharBackward(lineText, char, key, count);
|
|
612
|
-
}
|
|
638
|
+
const pos = charSearchPos(motion, lineText, char, key, count);
|
|
613
639
|
if (pos !== -1) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
yankStart = char;
|
|
618
|
-
yankEnd = pos + 1;
|
|
619
|
-
} else if (motion === 't') {
|
|
620
|
-
yankStart = char;
|
|
621
|
-
yankEnd = pos;
|
|
622
|
-
} else if (motion === 'F') {
|
|
623
|
-
yankStart = pos;
|
|
624
|
-
yankEnd = char + 1;
|
|
625
|
-
} else if (motion === 'T') {
|
|
626
|
-
yankStart = pos + 1;
|
|
627
|
-
yankEnd = char + 1;
|
|
628
|
-
}
|
|
629
|
-
if (yankEnd > yankStart) {
|
|
630
|
-
setRegister(lineText.slice(yankStart, yankEnd));
|
|
640
|
+
const range = charMotionRange(motion, char, pos);
|
|
641
|
+
if (range) {
|
|
642
|
+
setRegister(lineText.slice(range.start, range.end));
|
|
631
643
|
}
|
|
632
644
|
}
|
|
633
645
|
return true;
|
|
634
646
|
}
|
|
635
647
|
|
|
636
|
-
if (
|
|
648
|
+
if (
|
|
649
|
+
pendingKey === "cf" ||
|
|
650
|
+
pendingKey === "cF" ||
|
|
651
|
+
pendingKey === "ct" ||
|
|
652
|
+
pendingKey === "cT"
|
|
653
|
+
) {
|
|
637
654
|
const motion = pendingKey[1];
|
|
638
655
|
pendingKey = null;
|
|
639
|
-
|
|
640
|
-
if (motion === 'f' || motion === 't') {
|
|
641
|
-
pos = findCharForward(lineText, char, key, count);
|
|
642
|
-
} else {
|
|
643
|
-
pos = findCharBackward(lineText, char, key, count);
|
|
644
|
-
}
|
|
656
|
+
const pos = charSearchPos(motion, lineText, char, key, count);
|
|
645
657
|
if (pos !== -1) {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
} else if (motion === 't') {
|
|
652
|
-
delStart = char;
|
|
653
|
-
delEnd = pos;
|
|
654
|
-
} else if (motion === 'F') {
|
|
655
|
-
delStart = pos;
|
|
656
|
-
delEnd = char + 1;
|
|
657
|
-
} else if (motion === 'T') {
|
|
658
|
-
delStart = pos + 1;
|
|
659
|
-
delEnd = char + 1;
|
|
660
|
-
}
|
|
661
|
-
if (delEnd > delStart) {
|
|
662
|
-
setRegister(lineText.slice(delStart, delEnd));
|
|
663
|
-
replaceRange(editorInfo, [line, delStart], [line, delEnd], '');
|
|
664
|
-
moveCursor(editorInfo, line, delStart);
|
|
658
|
+
const range = charMotionRange(motion, char, pos);
|
|
659
|
+
if (range) {
|
|
660
|
+
setRegister(lineText.slice(range.start, range.end));
|
|
661
|
+
replaceRange(editorInfo, [line, range.start], [line, range.end], "");
|
|
662
|
+
moveCursor(editorInfo, line, range.start);
|
|
665
663
|
setInsertMode(true);
|
|
666
664
|
}
|
|
667
665
|
}
|
|
668
666
|
return true;
|
|
669
667
|
}
|
|
670
668
|
|
|
671
|
-
if (pendingKey ===
|
|
669
|
+
if (pendingKey === "ci") {
|
|
672
670
|
pendingKey = null;
|
|
671
|
+
const range = innerTextObjectRange(key, lineText, char);
|
|
672
|
+
if (range) {
|
|
673
|
+
setRegister(lineText.slice(range.start, range.end));
|
|
674
|
+
replaceRange(editorInfo, [line, range.start], [line, range.end], "");
|
|
675
|
+
moveCursor(editorInfo, line, range.start);
|
|
676
|
+
setInsertMode(true);
|
|
677
|
+
}
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
673
680
|
|
|
674
|
-
|
|
681
|
+
if (pendingKey === "c") {
|
|
682
|
+
pendingKey = null;
|
|
683
|
+
|
|
684
|
+
if (key === "c") {
|
|
675
685
|
setRegister(lineText);
|
|
676
|
-
replaceRange(editorInfo, [line, 0], [line, lineText.length],
|
|
686
|
+
replaceRange(editorInfo, [line, 0], [line, lineText.length], "");
|
|
677
687
|
moveCursor(editorInfo, line, 0);
|
|
678
688
|
setInsertMode(true);
|
|
679
689
|
return true;
|
|
680
690
|
}
|
|
681
691
|
|
|
682
|
-
if (key ===
|
|
683
|
-
pendingKey =
|
|
692
|
+
if (key === "i") {
|
|
693
|
+
pendingKey = "ci";
|
|
684
694
|
return true;
|
|
685
695
|
}
|
|
686
696
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
if (key === 'w') {
|
|
691
|
-
let pos = char;
|
|
692
|
-
for (let i = 0; i < count; i++) pos = wordForward(lineText, pos);
|
|
693
|
-
delStart = char;
|
|
694
|
-
delEnd = Math.min(pos, lineText.length);
|
|
695
|
-
} else if (key === 'e') {
|
|
696
|
-
let pos = char;
|
|
697
|
-
for (let i = 0; i < count; i++) pos = wordEnd(lineText, pos);
|
|
698
|
-
delStart = char;
|
|
699
|
-
delEnd = Math.min(pos + 1, lineText.length);
|
|
700
|
-
} else if (key === 'b') {
|
|
701
|
-
let pos = char;
|
|
702
|
-
for (let i = 0; i < count; i++) pos = wordBackward(lineText, pos);
|
|
703
|
-
delStart = pos;
|
|
704
|
-
delEnd = char;
|
|
705
|
-
} else if (key === '$') {
|
|
706
|
-
delStart = char;
|
|
707
|
-
delEnd = lineText.length;
|
|
708
|
-
} else if (key === '0') {
|
|
709
|
-
delStart = 0;
|
|
710
|
-
delEnd = char;
|
|
711
|
-
} else if (key === '^') {
|
|
712
|
-
const fnb = firstNonBlank(lineText);
|
|
713
|
-
delStart = Math.min(char, fnb);
|
|
714
|
-
delEnd = Math.max(char, fnb);
|
|
715
|
-
} else if (key === 'h') {
|
|
716
|
-
delStart = Math.max(0, char - count);
|
|
717
|
-
delEnd = char;
|
|
718
|
-
} else if (key === 'l') {
|
|
719
|
-
delStart = char;
|
|
720
|
-
delEnd = Math.min(char + count, lineText.length);
|
|
697
|
+
if (key === "f" || key === "F" || key === "t" || key === "T") {
|
|
698
|
+
pendingKey = "c" + key;
|
|
699
|
+
return true;
|
|
721
700
|
}
|
|
722
701
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
702
|
+
const range = motionRange(key, char, lineText, count);
|
|
703
|
+
if (range && range.end > range.start) {
|
|
704
|
+
setRegister(lineText.slice(range.start, range.end));
|
|
705
|
+
replaceRange(editorInfo, [line, range.start], [line, range.end], "");
|
|
706
|
+
moveCursor(editorInfo, line, range.start);
|
|
727
707
|
setInsertMode(true);
|
|
728
708
|
}
|
|
729
709
|
return true;
|
|
730
710
|
}
|
|
731
711
|
|
|
732
|
-
if (pendingKey ===
|
|
712
|
+
if (pendingKey === "yi") {
|
|
713
|
+
pendingKey = null;
|
|
714
|
+
const range = innerTextObjectRange(key, lineText, char);
|
|
715
|
+
if (range) {
|
|
716
|
+
setRegister(lineText.slice(range.start, range.end));
|
|
717
|
+
}
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (pendingKey === "y") {
|
|
733
722
|
pendingKey = null;
|
|
734
723
|
|
|
735
|
-
if (key ===
|
|
724
|
+
if (key === "y") {
|
|
736
725
|
const yankCount = Math.min(count, lineCount - line);
|
|
737
726
|
const lastYankLine = line + yankCount - 1;
|
|
738
727
|
const yankedLines = [];
|
|
@@ -743,66 +732,37 @@ const handleNormalKey = (rep, editorInfo, key) => {
|
|
|
743
732
|
return true;
|
|
744
733
|
}
|
|
745
734
|
|
|
746
|
-
if (key ===
|
|
747
|
-
pendingKey =
|
|
735
|
+
if (key === "i") {
|
|
736
|
+
pendingKey = "yi";
|
|
748
737
|
return true;
|
|
749
738
|
}
|
|
750
739
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
if (key === 'w') {
|
|
755
|
-
let pos = char;
|
|
756
|
-
for (let i = 0; i < count; i++) pos = wordForward(lineText, pos);
|
|
757
|
-
yankStart = char;
|
|
758
|
-
yankEnd = Math.min(pos, lineText.length);
|
|
759
|
-
} else if (key === 'e') {
|
|
760
|
-
let pos = char;
|
|
761
|
-
for (let i = 0; i < count; i++) pos = wordEnd(lineText, pos);
|
|
762
|
-
yankStart = char;
|
|
763
|
-
yankEnd = Math.min(pos + 1, lineText.length);
|
|
764
|
-
} else if (key === 'b') {
|
|
765
|
-
let pos = char;
|
|
766
|
-
for (let i = 0; i < count; i++) pos = wordBackward(lineText, pos);
|
|
767
|
-
yankStart = pos;
|
|
768
|
-
yankEnd = char;
|
|
769
|
-
} else if (key === '$') {
|
|
770
|
-
yankStart = char;
|
|
771
|
-
yankEnd = lineText.length;
|
|
772
|
-
} else if (key === '0') {
|
|
773
|
-
yankStart = 0;
|
|
774
|
-
yankEnd = char;
|
|
775
|
-
} else if (key === '^') {
|
|
776
|
-
const fnb = firstNonBlank(lineText);
|
|
777
|
-
yankStart = Math.min(char, fnb);
|
|
778
|
-
yankEnd = Math.max(char, fnb);
|
|
779
|
-
} else if (key === 'h') {
|
|
780
|
-
yankStart = Math.max(0, char - count);
|
|
781
|
-
yankEnd = char;
|
|
782
|
-
} else if (key === 'l') {
|
|
783
|
-
yankStart = char;
|
|
784
|
-
yankEnd = Math.min(char + count, lineText.length);
|
|
740
|
+
if (key === "f" || key === "F" || key === "t" || key === "T") {
|
|
741
|
+
pendingKey = "y" + key;
|
|
742
|
+
return true;
|
|
785
743
|
}
|
|
786
744
|
|
|
787
|
-
|
|
788
|
-
|
|
745
|
+
const range = motionRange(key, char, lineText, count);
|
|
746
|
+
if (range && range.end > range.start) {
|
|
747
|
+
setRegister(lineText.slice(range.start, range.end));
|
|
789
748
|
}
|
|
790
749
|
return true;
|
|
791
750
|
}
|
|
792
751
|
|
|
793
|
-
if (pendingKey ===
|
|
752
|
+
if (pendingKey === "m") {
|
|
794
753
|
pendingKey = null;
|
|
795
|
-
if (key >=
|
|
754
|
+
if (key >= "a" && key <= "z") {
|
|
796
755
|
marks[key] = [line, char];
|
|
797
756
|
}
|
|
798
757
|
return true;
|
|
799
758
|
}
|
|
800
759
|
|
|
801
|
-
if (pendingKey === "'" || pendingKey ===
|
|
760
|
+
if (pendingKey === "'" || pendingKey === "`") {
|
|
802
761
|
const jumpType = pendingKey;
|
|
803
762
|
pendingKey = null;
|
|
804
|
-
if (key >=
|
|
763
|
+
if (key >= "a" && key <= "z" && marks[key]) {
|
|
805
764
|
const [markLine, markChar] = marks[key];
|
|
765
|
+
desiredColumn = null;
|
|
806
766
|
if (jumpType === "'") {
|
|
807
767
|
const targetLineText = getLineText(rep, markLine);
|
|
808
768
|
moveBlockCursor(editorInfo, markLine, firstNonBlank(targetLineText));
|
|
@@ -813,118 +773,146 @@ const handleNormalKey = (rep, editorInfo, key) => {
|
|
|
813
773
|
return true;
|
|
814
774
|
}
|
|
815
775
|
|
|
816
|
-
if (key ===
|
|
776
|
+
if (key === "h") {
|
|
777
|
+
desiredColumn = null;
|
|
817
778
|
moveBlockCursor(editorInfo, line, Math.max(0, char - count));
|
|
818
779
|
return true;
|
|
819
780
|
}
|
|
820
781
|
|
|
821
|
-
if (key ===
|
|
782
|
+
if (key === "l") {
|
|
783
|
+
desiredColumn = null;
|
|
822
784
|
moveBlockCursor(editorInfo, line, clampChar(char + count, lineText));
|
|
823
785
|
return true;
|
|
824
786
|
}
|
|
825
787
|
|
|
826
|
-
if (key ===
|
|
788
|
+
if (key === "k") {
|
|
789
|
+
if (desiredColumn === null) {
|
|
790
|
+
desiredColumn = char;
|
|
791
|
+
}
|
|
827
792
|
const newLine = clampLine(line - count, rep);
|
|
828
793
|
const newLineText = getLineText(rep, newLine);
|
|
829
|
-
moveBlockCursor(editorInfo, newLine, clampChar(
|
|
794
|
+
moveBlockCursor(editorInfo, newLine, clampChar(desiredColumn, newLineText));
|
|
830
795
|
return true;
|
|
831
796
|
}
|
|
832
797
|
|
|
833
|
-
if (key ===
|
|
798
|
+
if (key === "j") {
|
|
799
|
+
if (desiredColumn === null) {
|
|
800
|
+
desiredColumn = char;
|
|
801
|
+
}
|
|
834
802
|
const newLine = clampLine(line + count, rep);
|
|
835
803
|
const newLineText = getLineText(rep, newLine);
|
|
836
|
-
moveBlockCursor(editorInfo, newLine, clampChar(
|
|
804
|
+
moveBlockCursor(editorInfo, newLine, clampChar(desiredColumn, newLineText));
|
|
837
805
|
return true;
|
|
838
806
|
}
|
|
839
807
|
|
|
840
|
-
if (key ===
|
|
808
|
+
if (key === "0") {
|
|
809
|
+
desiredColumn = null;
|
|
841
810
|
moveBlockCursor(editorInfo, line, 0);
|
|
842
811
|
return true;
|
|
843
812
|
}
|
|
844
813
|
|
|
845
|
-
if (key ===
|
|
814
|
+
if (key === "$") {
|
|
815
|
+
desiredColumn = null;
|
|
846
816
|
moveBlockCursor(editorInfo, line, clampChar(lineText.length - 1, lineText));
|
|
847
817
|
return true;
|
|
848
818
|
}
|
|
849
819
|
|
|
850
|
-
if (key ===
|
|
820
|
+
if (key === "^") {
|
|
821
|
+
desiredColumn = null;
|
|
851
822
|
moveBlockCursor(editorInfo, line, firstNonBlank(lineText));
|
|
852
823
|
return true;
|
|
853
824
|
}
|
|
854
825
|
|
|
855
|
-
if (key ===
|
|
826
|
+
if (key === "x") {
|
|
856
827
|
if (lineText.length > 0) {
|
|
857
828
|
const deleteCount = Math.min(count, lineText.length - char);
|
|
858
|
-
replaceRange(editorInfo, [line, char], [line, char + deleteCount],
|
|
829
|
+
replaceRange(editorInfo, [line, char], [line, char + deleteCount], "");
|
|
859
830
|
const newLineText = getLineText(rep, line);
|
|
860
831
|
moveBlockCursor(editorInfo, line, clampChar(char, newLineText));
|
|
861
832
|
}
|
|
862
833
|
return true;
|
|
863
834
|
}
|
|
864
835
|
|
|
865
|
-
if (key ===
|
|
836
|
+
if (key === "w") {
|
|
837
|
+
desiredColumn = null;
|
|
866
838
|
let pos = char;
|
|
867
839
|
for (let i = 0; i < count; i++) pos = wordForward(lineText, pos);
|
|
868
840
|
moveBlockCursor(editorInfo, line, clampChar(pos, lineText));
|
|
869
841
|
return true;
|
|
870
842
|
}
|
|
871
843
|
|
|
872
|
-
if (key ===
|
|
844
|
+
if (key === "b") {
|
|
845
|
+
desiredColumn = null;
|
|
873
846
|
let pos = char;
|
|
874
847
|
for (let i = 0; i < count; i++) pos = wordBackward(lineText, pos);
|
|
875
848
|
moveBlockCursor(editorInfo, line, pos);
|
|
876
849
|
return true;
|
|
877
850
|
}
|
|
878
851
|
|
|
879
|
-
if (key ===
|
|
880
|
-
replaceRange(
|
|
852
|
+
if (key === "o") {
|
|
853
|
+
replaceRange(
|
|
854
|
+
editorInfo,
|
|
855
|
+
[line, lineText.length],
|
|
856
|
+
[line, lineText.length],
|
|
857
|
+
"\n",
|
|
858
|
+
);
|
|
881
859
|
moveCursor(editorInfo, line + 1, 0);
|
|
882
860
|
setInsertMode(true);
|
|
883
861
|
return true;
|
|
884
862
|
}
|
|
885
863
|
|
|
886
|
-
if (key ===
|
|
887
|
-
replaceRange(editorInfo, [line, 0], [line, 0],
|
|
864
|
+
if (key === "O") {
|
|
865
|
+
replaceRange(editorInfo, [line, 0], [line, 0], "\n");
|
|
888
866
|
moveCursor(editorInfo, line, 0);
|
|
889
867
|
setInsertMode(true);
|
|
890
868
|
return true;
|
|
891
869
|
}
|
|
892
870
|
|
|
893
|
-
if (key ===
|
|
871
|
+
if (key === "u") {
|
|
894
872
|
undo(editorInfo);
|
|
895
873
|
return true;
|
|
896
874
|
}
|
|
897
875
|
|
|
898
|
-
if (key ===
|
|
876
|
+
if (key === "p") {
|
|
899
877
|
if (register !== null) {
|
|
900
|
-
if (typeof register ===
|
|
878
|
+
if (typeof register === "string") {
|
|
901
879
|
const insertPos = Math.min(char + 1, lineText.length);
|
|
902
880
|
const repeated = register.repeat(count);
|
|
903
|
-
replaceRange(
|
|
881
|
+
replaceRange(
|
|
882
|
+
editorInfo,
|
|
883
|
+
[line, insertPos],
|
|
884
|
+
[line, insertPos],
|
|
885
|
+
repeated,
|
|
886
|
+
);
|
|
904
887
|
moveBlockCursor(editorInfo, line, insertPos);
|
|
905
888
|
} else {
|
|
906
|
-
const block = register.join(
|
|
889
|
+
const block = register.join("\n");
|
|
907
890
|
const parts = [];
|
|
908
891
|
for (let i = 0; i < count; i++) parts.push(block);
|
|
909
|
-
const insertText =
|
|
910
|
-
replaceRange(
|
|
892
|
+
const insertText = "\n" + parts.join("\n");
|
|
893
|
+
replaceRange(
|
|
894
|
+
editorInfo,
|
|
895
|
+
[line, lineText.length],
|
|
896
|
+
[line, lineText.length],
|
|
897
|
+
insertText,
|
|
898
|
+
);
|
|
911
899
|
moveBlockCursor(editorInfo, line + 1, 0);
|
|
912
900
|
}
|
|
913
901
|
}
|
|
914
902
|
return true;
|
|
915
903
|
}
|
|
916
904
|
|
|
917
|
-
if (key ===
|
|
905
|
+
if (key === "P") {
|
|
918
906
|
if (register !== null) {
|
|
919
|
-
if (typeof register ===
|
|
907
|
+
if (typeof register === "string") {
|
|
920
908
|
const repeated = register.repeat(count);
|
|
921
909
|
replaceRange(editorInfo, [line, char], [line, char], repeated);
|
|
922
910
|
moveBlockCursor(editorInfo, line, char);
|
|
923
911
|
} else {
|
|
924
|
-
const block = register.join(
|
|
912
|
+
const block = register.join("\n");
|
|
925
913
|
const parts = [];
|
|
926
914
|
for (let i = 0; i < count; i++) parts.push(block);
|
|
927
|
-
const insertText = parts.join(
|
|
915
|
+
const insertText = parts.join("\n") + "\n";
|
|
928
916
|
replaceRange(editorInfo, [line, 0], [line, 0], insertText);
|
|
929
917
|
moveBlockCursor(editorInfo, line, 0);
|
|
930
918
|
}
|
|
@@ -932,7 +920,8 @@ const handleNormalKey = (rep, editorInfo, key) => {
|
|
|
932
920
|
return true;
|
|
933
921
|
}
|
|
934
922
|
|
|
935
|
-
if (key ===
|
|
923
|
+
if (key === "G") {
|
|
924
|
+
desiredColumn = null;
|
|
936
925
|
if (pendingCount !== null) {
|
|
937
926
|
moveBlockCursor(editorInfo, clampLine(pendingCount - 1, rep), 0);
|
|
938
927
|
} else {
|
|
@@ -941,100 +930,191 @@ const handleNormalKey = (rep, editorInfo, key) => {
|
|
|
941
930
|
return true;
|
|
942
931
|
}
|
|
943
932
|
|
|
944
|
-
if (key ===
|
|
945
|
-
if (pendingKey ===
|
|
933
|
+
if (key === "g") {
|
|
934
|
+
if (pendingKey === "g") {
|
|
946
935
|
pendingKey = null;
|
|
936
|
+
desiredColumn = null;
|
|
947
937
|
moveBlockCursor(editorInfo, 0, 0);
|
|
948
938
|
} else {
|
|
949
|
-
pendingKey =
|
|
939
|
+
pendingKey = "g";
|
|
950
940
|
}
|
|
951
941
|
return true;
|
|
952
942
|
}
|
|
953
943
|
|
|
954
|
-
if (key ===
|
|
944
|
+
if (key === "r") {
|
|
955
945
|
if (lineText.length > 0) {
|
|
956
|
-
pendingKey =
|
|
946
|
+
pendingKey = "r";
|
|
957
947
|
}
|
|
958
948
|
return true;
|
|
959
949
|
}
|
|
960
950
|
|
|
961
|
-
if (key ===
|
|
951
|
+
if (key === "f" || key === "F" || key === "t" || key === "T") {
|
|
962
952
|
pendingKey = key;
|
|
963
953
|
return true;
|
|
964
954
|
}
|
|
965
955
|
|
|
966
|
-
if (key ===
|
|
967
|
-
pendingKey =
|
|
956
|
+
if (key === "m") {
|
|
957
|
+
pendingKey = "m";
|
|
968
958
|
return true;
|
|
969
959
|
}
|
|
970
960
|
|
|
971
|
-
if (key === "'" || key ===
|
|
961
|
+
if (key === "'" || key === "`") {
|
|
972
962
|
pendingKey = key;
|
|
973
963
|
return true;
|
|
974
964
|
}
|
|
975
965
|
|
|
976
|
-
if (key ===
|
|
977
|
-
pendingKey =
|
|
966
|
+
if (key === "d") {
|
|
967
|
+
pendingKey = "d";
|
|
978
968
|
return true;
|
|
979
969
|
}
|
|
980
970
|
|
|
981
|
-
if (key ===
|
|
982
|
-
pendingKey =
|
|
971
|
+
if (key === "c") {
|
|
972
|
+
pendingKey = "c";
|
|
983
973
|
return true;
|
|
984
974
|
}
|
|
985
975
|
|
|
986
|
-
if (key ===
|
|
987
|
-
pendingKey =
|
|
976
|
+
if (key === "y") {
|
|
977
|
+
pendingKey = "y";
|
|
988
978
|
return true;
|
|
989
979
|
}
|
|
990
980
|
|
|
991
|
-
if (key ===
|
|
981
|
+
if (key === "Y") {
|
|
992
982
|
setRegister([lineText]);
|
|
993
983
|
return true;
|
|
994
984
|
}
|
|
995
985
|
|
|
996
|
-
if (key ===
|
|
986
|
+
if (key === "J") {
|
|
997
987
|
const joins = Math.min(count, lineCount - 1 - line);
|
|
998
988
|
let cursorChar = lineText.length;
|
|
999
989
|
for (let i = 0; i < joins; i++) {
|
|
1000
990
|
const curLineText = getLineText(rep, line);
|
|
1001
991
|
const nextLineText = getLineText(rep, line + 1);
|
|
1002
|
-
const trimmedNext = nextLineText.replace(/^\s+/,
|
|
1003
|
-
const separator = curLineText.length === 0 ?
|
|
992
|
+
const trimmedNext = nextLineText.replace(/^\s+/, "");
|
|
993
|
+
const separator = curLineText.length === 0 ? "" : " ";
|
|
1004
994
|
if (i === 0) cursorChar = curLineText.length;
|
|
1005
|
-
replaceRange(
|
|
995
|
+
replaceRange(
|
|
996
|
+
editorInfo,
|
|
997
|
+
[line, curLineText.length],
|
|
998
|
+
[line + 1, nextLineText.length],
|
|
999
|
+
separator + trimmedNext,
|
|
1000
|
+
);
|
|
1006
1001
|
}
|
|
1007
1002
|
moveBlockCursor(editorInfo, line, cursorChar);
|
|
1008
1003
|
return true;
|
|
1009
1004
|
}
|
|
1010
1005
|
|
|
1011
|
-
if (key ===
|
|
1006
|
+
if (key === "~") {
|
|
1007
|
+
if (lineText.length > 0) {
|
|
1008
|
+
const toggleCount = Math.min(count, lineText.length - char);
|
|
1009
|
+
const slice = lineText.slice(char, char + toggleCount);
|
|
1010
|
+
let toggled = "";
|
|
1011
|
+
for (let i = 0; i < slice.length; i++) {
|
|
1012
|
+
const ch = slice[i];
|
|
1013
|
+
toggled +=
|
|
1014
|
+
ch === ch.toLowerCase() ? ch.toUpperCase() : ch.toLowerCase();
|
|
1015
|
+
}
|
|
1016
|
+
replaceRange(
|
|
1017
|
+
editorInfo,
|
|
1018
|
+
[line, char],
|
|
1019
|
+
[line, char + toggleCount],
|
|
1020
|
+
toggled,
|
|
1021
|
+
);
|
|
1022
|
+
const newChar = Math.min(char + toggleCount, lineText.length - 1);
|
|
1023
|
+
moveBlockCursor(editorInfo, line, newChar);
|
|
1024
|
+
}
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
if (key === "D") {
|
|
1012
1029
|
setRegister(lineText.slice(char));
|
|
1013
|
-
replaceRange(editorInfo, [line, char], [line, lineText.length],
|
|
1030
|
+
replaceRange(editorInfo, [line, char], [line, lineText.length], "");
|
|
1031
|
+
const newLineText = getLineText(rep, line);
|
|
1032
|
+
moveBlockCursor(editorInfo, line, clampChar(char, newLineText));
|
|
1033
|
+
return true;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (key === "C") {
|
|
1037
|
+
setRegister(lineText.slice(char));
|
|
1038
|
+
replaceRange(editorInfo, [line, char], [line, lineText.length], "");
|
|
1014
1039
|
moveCursor(editorInfo, line, char);
|
|
1015
1040
|
setInsertMode(true);
|
|
1016
1041
|
return true;
|
|
1017
1042
|
}
|
|
1018
1043
|
|
|
1019
|
-
if (key ===
|
|
1044
|
+
if (key === "s") {
|
|
1020
1045
|
setRegister(lineText.slice(char, char + 1));
|
|
1021
|
-
replaceRange(
|
|
1046
|
+
replaceRange(
|
|
1047
|
+
editorInfo,
|
|
1048
|
+
[line, char],
|
|
1049
|
+
[line, Math.min(char + count, lineText.length)],
|
|
1050
|
+
"",
|
|
1051
|
+
);
|
|
1022
1052
|
moveCursor(editorInfo, line, char);
|
|
1023
1053
|
setInsertMode(true);
|
|
1024
1054
|
return true;
|
|
1025
1055
|
}
|
|
1026
1056
|
|
|
1027
|
-
if (key ===
|
|
1057
|
+
if (key === "S") {
|
|
1028
1058
|
setRegister(lineText);
|
|
1029
|
-
replaceRange(editorInfo, [line, 0], [line, lineText.length],
|
|
1059
|
+
replaceRange(editorInfo, [line, 0], [line, lineText.length], "");
|
|
1030
1060
|
moveCursor(editorInfo, line, 0);
|
|
1031
1061
|
setInsertMode(true);
|
|
1032
1062
|
return true;
|
|
1033
1063
|
}
|
|
1034
1064
|
|
|
1065
|
+
if (key === ";") {
|
|
1066
|
+
if (lastCharSearch) {
|
|
1067
|
+
const pos = charSearchPos(
|
|
1068
|
+
lastCharSearch.direction,
|
|
1069
|
+
lineText,
|
|
1070
|
+
char,
|
|
1071
|
+
lastCharSearch.target,
|
|
1072
|
+
count,
|
|
1073
|
+
);
|
|
1074
|
+
if (pos !== -1) {
|
|
1075
|
+
desiredColumn = null;
|
|
1076
|
+
moveBlockCursor(editorInfo, line, pos);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return true;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
if (key === ",") {
|
|
1083
|
+
if (lastCharSearch) {
|
|
1084
|
+
const opposite = { f: "F", F: "f", t: "T", T: "t" };
|
|
1085
|
+
const reverseDir = opposite[lastCharSearch.direction];
|
|
1086
|
+
const pos = charSearchPos(
|
|
1087
|
+
reverseDir,
|
|
1088
|
+
lineText,
|
|
1089
|
+
char,
|
|
1090
|
+
lastCharSearch.target,
|
|
1091
|
+
count,
|
|
1092
|
+
);
|
|
1093
|
+
if (pos !== -1) {
|
|
1094
|
+
desiredColumn = null;
|
|
1095
|
+
moveBlockCursor(editorInfo, line, pos);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return true;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
if (key === "}") {
|
|
1102
|
+
desiredColumn = null;
|
|
1103
|
+
const target = paragraphForward(rep, line, count);
|
|
1104
|
+
moveBlockCursor(editorInfo, target, 0);
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
if (key === "{") {
|
|
1109
|
+
desiredColumn = null;
|
|
1110
|
+
const target = paragraphBackward(rep, line, count);
|
|
1111
|
+
moveBlockCursor(editorInfo, target, 0);
|
|
1112
|
+
return true;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1035
1115
|
pendingKey = null;
|
|
1036
1116
|
|
|
1037
|
-
if (key ===
|
|
1117
|
+
if (key === "e") {
|
|
1038
1118
|
let pos = char;
|
|
1039
1119
|
for (let i = 0; i < count; i++) pos = wordEnd(lineText, pos);
|
|
1040
1120
|
moveBlockCursor(editorInfo, line, clampChar(pos, lineText));
|
|
@@ -1046,22 +1126,33 @@ const handleNormalKey = (rep, editorInfo, key) => {
|
|
|
1046
1126
|
|
|
1047
1127
|
// --- Exports ---
|
|
1048
1128
|
|
|
1049
|
-
exports.aceEditorCSS = () => [
|
|
1129
|
+
exports.aceEditorCSS = () => ["ep_vim/static/css/vim.css"];
|
|
1050
1130
|
|
|
1051
1131
|
exports.postToolbarInit = (_hookName, _args) => {
|
|
1052
|
-
const btn = document.getElementById(
|
|
1132
|
+
const btn = document.getElementById("vim-toggle-btn");
|
|
1053
1133
|
if (!btn) return;
|
|
1054
|
-
btn.classList.toggle(
|
|
1055
|
-
btn.addEventListener(
|
|
1134
|
+
btn.classList.toggle("vim-enabled", vimEnabled);
|
|
1135
|
+
btn.addEventListener("click", () => {
|
|
1056
1136
|
vimEnabled = !vimEnabled;
|
|
1057
|
-
localStorage.setItem(
|
|
1058
|
-
btn.classList.toggle(
|
|
1137
|
+
localStorage.setItem("ep_vimEnabled", vimEnabled ? "true" : "false");
|
|
1138
|
+
btn.classList.toggle("vim-enabled", vimEnabled);
|
|
1139
|
+
});
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
exports.postAceInit = (_hookName, { ace }) => {
|
|
1143
|
+
if (!vimEnabled) return;
|
|
1144
|
+
ace.callWithAce((aceTop) => {
|
|
1145
|
+
const rep = aceTop.ace_getRep();
|
|
1146
|
+
if (rep && rep.selStart) {
|
|
1147
|
+
currentRep = rep;
|
|
1148
|
+
selectRange(aceTop, rep.selStart, [rep.selStart[0], rep.selStart[1] + 1]);
|
|
1149
|
+
}
|
|
1059
1150
|
});
|
|
1060
1151
|
};
|
|
1061
1152
|
|
|
1062
|
-
exports.aceKeyEvent = (_hookName, {evt, rep, editorInfo}) => {
|
|
1153
|
+
exports.aceKeyEvent = (_hookName, { evt, rep, editorInfo }) => {
|
|
1063
1154
|
if (!vimEnabled) return false;
|
|
1064
|
-
if (evt.type !==
|
|
1155
|
+
if (evt.type !== "keydown") return false;
|
|
1065
1156
|
currentRep = rep;
|
|
1066
1157
|
if (!editorDoc) {
|
|
1067
1158
|
editorDoc = evt.target.ownerDocument;
|
|
@@ -1080,8 +1171,9 @@ exports.aceKeyEvent = (_hookName, {evt, rep, editorInfo}) => {
|
|
|
1080
1171
|
return handled || true;
|
|
1081
1172
|
}
|
|
1082
1173
|
|
|
1083
|
-
if (!insertMode && evt.key ===
|
|
1174
|
+
if (!insertMode && evt.key === "i") {
|
|
1084
1175
|
const [line, char] = rep.selStart;
|
|
1176
|
+
desiredColumn = null;
|
|
1085
1177
|
moveCursor(editorInfo, line, char);
|
|
1086
1178
|
setVisualMode(null);
|
|
1087
1179
|
setInsertMode(true);
|
|
@@ -1089,9 +1181,10 @@ exports.aceKeyEvent = (_hookName, {evt, rep, editorInfo}) => {
|
|
|
1089
1181
|
return true;
|
|
1090
1182
|
}
|
|
1091
1183
|
|
|
1092
|
-
if (!insertMode && evt.key ===
|
|
1184
|
+
if (!insertMode && evt.key === "a") {
|
|
1093
1185
|
const [line, char] = rep.selStart;
|
|
1094
1186
|
const lineText = getLineText(rep, line);
|
|
1187
|
+
desiredColumn = null;
|
|
1095
1188
|
moveCursor(editorInfo, line, Math.min(char + 1, lineText.length));
|
|
1096
1189
|
setVisualMode(null);
|
|
1097
1190
|
setInsertMode(true);
|
|
@@ -1099,9 +1192,10 @@ exports.aceKeyEvent = (_hookName, {evt, rep, editorInfo}) => {
|
|
|
1099
1192
|
return true;
|
|
1100
1193
|
}
|
|
1101
1194
|
|
|
1102
|
-
if (!insertMode && evt.key ===
|
|
1195
|
+
if (!insertMode && evt.key === "A") {
|
|
1103
1196
|
const [line] = rep.selStart;
|
|
1104
1197
|
const lineText = getLineText(rep, line);
|
|
1198
|
+
desiredColumn = null;
|
|
1105
1199
|
moveCursor(editorInfo, line, lineText.length);
|
|
1106
1200
|
setVisualMode(null);
|
|
1107
1201
|
setInsertMode(true);
|
|
@@ -1109,9 +1203,10 @@ exports.aceKeyEvent = (_hookName, {evt, rep, editorInfo}) => {
|
|
|
1109
1203
|
return true;
|
|
1110
1204
|
}
|
|
1111
1205
|
|
|
1112
|
-
if (!insertMode && evt.key ===
|
|
1206
|
+
if (!insertMode && evt.key === "I") {
|
|
1113
1207
|
const [line] = rep.selStart;
|
|
1114
1208
|
const lineText = getLineText(rep, line);
|
|
1209
|
+
desiredColumn = null;
|
|
1115
1210
|
moveCursor(editorInfo, line, firstNonBlank(lineText));
|
|
1116
1211
|
setVisualMode(null);
|
|
1117
1212
|
setInsertMode(true);
|
|
@@ -1119,7 +1214,7 @@ exports.aceKeyEvent = (_hookName, {evt, rep, editorInfo}) => {
|
|
|
1119
1214
|
return true;
|
|
1120
1215
|
}
|
|
1121
1216
|
|
|
1122
|
-
if (evt.key ===
|
|
1217
|
+
if (evt.key === "Escape") {
|
|
1123
1218
|
if (insertMode) {
|
|
1124
1219
|
setInsertMode(false);
|
|
1125
1220
|
const [line, char] = rep.selStart;
|
|
@@ -1130,28 +1225,29 @@ exports.aceKeyEvent = (_hookName, {evt, rep, editorInfo}) => {
|
|
|
1130
1225
|
setVisualMode(null);
|
|
1131
1226
|
moveBlockCursor(editorInfo, vLine, vChar);
|
|
1132
1227
|
}
|
|
1133
|
-
countBuffer =
|
|
1228
|
+
countBuffer = "";
|
|
1134
1229
|
pendingKey = null;
|
|
1135
1230
|
pendingCount = null;
|
|
1231
|
+
desiredColumn = null;
|
|
1136
1232
|
evt.preventDefault();
|
|
1137
1233
|
return true;
|
|
1138
1234
|
}
|
|
1139
1235
|
|
|
1140
|
-
if (!insertMode && visualMode === null && evt.key ===
|
|
1236
|
+
if (!insertMode && visualMode === null && evt.key === "V") {
|
|
1141
1237
|
const [line] = rep.selStart;
|
|
1142
1238
|
visualAnchor = [line, 0];
|
|
1143
1239
|
visualCursor = [line, 0];
|
|
1144
|
-
setVisualMode(
|
|
1240
|
+
setVisualMode("line");
|
|
1145
1241
|
updateVisualSelection(editorInfo, rep);
|
|
1146
1242
|
evt.preventDefault();
|
|
1147
1243
|
return true;
|
|
1148
1244
|
}
|
|
1149
1245
|
|
|
1150
|
-
if (!insertMode && visualMode === null && evt.key ===
|
|
1246
|
+
if (!insertMode && visualMode === null && evt.key === "v") {
|
|
1151
1247
|
const [line, char] = rep.selStart;
|
|
1152
1248
|
visualAnchor = [line, char];
|
|
1153
1249
|
visualCursor = [line, char];
|
|
1154
|
-
setVisualMode(
|
|
1250
|
+
setVisualMode("char");
|
|
1155
1251
|
updateVisualSelection(editorInfo, rep);
|
|
1156
1252
|
evt.preventDefault();
|
|
1157
1253
|
return true;
|