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