ep_vim 0.13.0 → 1.0.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 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
- The following are not planned, but PRs are welcome.
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.13.0",
3
+ "version": "1.0.0",
4
4
  "description": "Vim-mode plugin for Etherpad with modal editing, motions, and operators",
5
5
  "author": {
6
6
  "name": "Seth Rothschild",
@@ -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
  };