ep_vim 0.13.0 → 1.0.1
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 +10 -1
- package/package.json +2 -2
- package/static/js/index.js +85 -0
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ A vim-mode plugin for [Etherpad](https://etherpad.org/). Adds modal editing with
|
|
|
20
20
|
- **Registers** — `"a`–`"z` named registers for yank/delete/put, `"_` blackhole register
|
|
21
21
|
- **Put** — `p` / `P` with linewise and characterwise register handling
|
|
22
22
|
- **Editing** — `i` `a` `A` `I` (insert/append), `x`, `X`, `r`, `R` (replace mode), `s`, `S`, `C`, `o`, `O`, `~` (toggle case)
|
|
23
|
+
- **Case operators** — `gu{motion}`/`gU{motion}` lowercase/uppercase over motion or text object (e.g. `guiw`, `gUap`); `u`/`U` in visual mode lowercase/uppercase selection
|
|
23
24
|
- **Marks** — `m{a-z}` to set, `'{a-z}` / `` `{a-z} `` to jump
|
|
24
25
|
- **Search** — `/` and `?` forward/backward, `n`/`N` repeat, `*`/`#` search word under cursor
|
|
25
26
|
- **Scrolling** — `zz`/`zt`/`zb` center/top/bottom, `Ctrl+d`/`Ctrl+u` half-page, `Ctrl+f`/`Ctrl+b` full-page (requires ctrl keys enabled)
|
|
@@ -30,13 +31,21 @@ A vim-mode plugin for [Etherpad](https://etherpad.org/). Adds modal editing with
|
|
|
30
31
|
- **Toggle** — toolbar button to enable/disable vim mode, persisted in localStorage; settings panel for system clipboard and ctrl key behavior
|
|
31
32
|
|
|
32
33
|
## Differences from vi
|
|
33
|
-
|
|
34
|
+
No further features are planned, but PRs are welcome. Notable exclusions in the current implementation are:
|
|
34
35
|
|
|
35
36
|
- **No command line, macros, or globals**
|
|
36
37
|
- **No visual block mode**
|
|
37
38
|
- **No indentation operators** — `>>`, `<<`, and `>` / `<` in visual mode
|
|
38
39
|
- **No increment/decrement** — `Ctrl+a` and `Ctrl+x`
|
|
39
40
|
|
|
41
|
+
## Rate limiting
|
|
42
|
+
|
|
43
|
+
If you encounter rate limit errors, Etherpad's commit rate limiter (default: 10 per second) can be adjusted:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
COMMIT_RATE_LIMIT_POINTS=100
|
|
47
|
+
```
|
|
48
|
+
|
|
40
49
|
## Installation
|
|
41
50
|
|
|
42
51
|
From your Etherpad directory run
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ep_vim",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Vim-mode plugin for Etherpad with modal editing, motions, and operators",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Seth Rothschild",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"engines": {
|
|
32
32
|
"node": ">=20.0.0"
|
|
33
33
|
},
|
|
34
|
-
"
|
|
34
|
+
"devDependencies": {
|
|
35
35
|
"prettier": "^3.8.1"
|
|
36
36
|
}
|
|
37
37
|
}
|
package/static/js/index.js
CHANGED
|
@@ -227,6 +227,17 @@ const applyOperator = (op, start, end, ctx) => {
|
|
|
227
227
|
}
|
|
228
228
|
};
|
|
229
229
|
|
|
230
|
+
const applyCaseOp = (op, start, end, ctx) => {
|
|
231
|
+
const { editorInfo, rep } = ctx;
|
|
232
|
+
const before =
|
|
233
|
+
start[0] < end[0] || (start[0] === end[0] && start[1] <= end[1]);
|
|
234
|
+
const [s, e] = before ? [start, end] : [end, start];
|
|
235
|
+
const text = getTextInRange(rep, s, e);
|
|
236
|
+
const transformed = op === "gu" ? text.toLowerCase() : text.toUpperCase();
|
|
237
|
+
replaceRange(editorInfo, s, e, transformed);
|
|
238
|
+
moveBlockCursor(editorInfo, s[0], s[1]);
|
|
239
|
+
};
|
|
240
|
+
|
|
230
241
|
// --- Command tables ---
|
|
231
242
|
const commands = {
|
|
232
243
|
normal: {},
|
|
@@ -236,6 +247,7 @@ const commands = {
|
|
|
236
247
|
|
|
237
248
|
// --- Registration helpers ---
|
|
238
249
|
const OPERATORS = ["d", "c", "y"];
|
|
250
|
+
const CASE_OPERATORS = ["gu", "gU"];
|
|
239
251
|
|
|
240
252
|
const resolveTextObject = (key, type, line, lineText, char, rep) => {
|
|
241
253
|
if (key === "p") {
|
|
@@ -302,6 +314,17 @@ const registerMotion = (
|
|
|
302
314
|
}
|
|
303
315
|
};
|
|
304
316
|
}
|
|
317
|
+
for (const op of CASE_OPERATORS) {
|
|
318
|
+
commands.normal[op + key] = (ctx) => {
|
|
319
|
+
state.desiredColumn = null;
|
|
320
|
+
const pos = getEndPos(ctx);
|
|
321
|
+
if (pos) {
|
|
322
|
+
const endChar = inclusive ? pos.char + 1 : pos.char;
|
|
323
|
+
applyCaseOp(op, [ctx.line, ctx.char], [pos.line, endChar], ctx);
|
|
324
|
+
recordCommand(op + key, ctx.count);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
305
328
|
};
|
|
306
329
|
|
|
307
330
|
const parameterized = {};
|
|
@@ -360,6 +383,14 @@ const registerTextObject = (obj, getRange) => {
|
|
|
360
383
|
};
|
|
361
384
|
}
|
|
362
385
|
}
|
|
386
|
+
for (const op of CASE_OPERATORS) {
|
|
387
|
+
for (const type of ["i", "a"]) {
|
|
388
|
+
commands.normal[`${op}${type}${obj}`] = (ctx) => {
|
|
389
|
+
const range = getRange(ctx, type);
|
|
390
|
+
if (range) applyCaseOp(op, range.start, range.end, ctx);
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
363
394
|
};
|
|
364
395
|
|
|
365
396
|
const getVisibleLineRange = (rep) => {
|
|
@@ -770,6 +801,60 @@ commands["visual-line"]["~"] = (ctx) => {
|
|
|
770
801
|
moveBlockCursor(ctx.editorInfo, start[0], start[1]);
|
|
771
802
|
};
|
|
772
803
|
|
|
804
|
+
commands["visual-char"]["u"] = (ctx) => {
|
|
805
|
+
const [start, end] = getVisualSelection(
|
|
806
|
+
"char",
|
|
807
|
+
state.visualAnchor,
|
|
808
|
+
state.visualCursor,
|
|
809
|
+
ctx.rep,
|
|
810
|
+
);
|
|
811
|
+
const adjustedEnd = [end[0], end[1] + 1];
|
|
812
|
+
const text = getTextInRange(ctx.rep, start, adjustedEnd);
|
|
813
|
+
replaceRange(ctx.editorInfo, start, adjustedEnd, text.toLowerCase());
|
|
814
|
+
state.mode = "normal";
|
|
815
|
+
moveBlockCursor(ctx.editorInfo, start[0], start[1]);
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
commands["visual-char"]["U"] = (ctx) => {
|
|
819
|
+
const [start, end] = getVisualSelection(
|
|
820
|
+
"char",
|
|
821
|
+
state.visualAnchor,
|
|
822
|
+
state.visualCursor,
|
|
823
|
+
ctx.rep,
|
|
824
|
+
);
|
|
825
|
+
const adjustedEnd = [end[0], end[1] + 1];
|
|
826
|
+
const text = getTextInRange(ctx.rep, start, adjustedEnd);
|
|
827
|
+
replaceRange(ctx.editorInfo, start, adjustedEnd, text.toUpperCase());
|
|
828
|
+
state.mode = "normal";
|
|
829
|
+
moveBlockCursor(ctx.editorInfo, start[0], start[1]);
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
commands["visual-line"]["u"] = (ctx) => {
|
|
833
|
+
const [start, end] = getVisualSelection(
|
|
834
|
+
"line",
|
|
835
|
+
state.visualAnchor,
|
|
836
|
+
state.visualCursor,
|
|
837
|
+
ctx.rep,
|
|
838
|
+
);
|
|
839
|
+
const text = getTextInRange(ctx.rep, start, end);
|
|
840
|
+
replaceRange(ctx.editorInfo, start, end, text.toLowerCase());
|
|
841
|
+
state.mode = "normal";
|
|
842
|
+
moveBlockCursor(ctx.editorInfo, start[0], start[1]);
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
commands["visual-line"]["U"] = (ctx) => {
|
|
846
|
+
const [start, end] = getVisualSelection(
|
|
847
|
+
"line",
|
|
848
|
+
state.visualAnchor,
|
|
849
|
+
state.visualCursor,
|
|
850
|
+
ctx.rep,
|
|
851
|
+
);
|
|
852
|
+
const text = getTextInRange(ctx.rep, start, end);
|
|
853
|
+
replaceRange(ctx.editorInfo, start, end, text.toUpperCase());
|
|
854
|
+
state.mode = "normal";
|
|
855
|
+
moveBlockCursor(ctx.editorInfo, start[0], start[1]);
|
|
856
|
+
};
|
|
857
|
+
|
|
773
858
|
commands["visual-char"]["i"] = () => {
|
|
774
859
|
state.pendingKey = "i";
|
|
775
860
|
};
|