@thazhemadam/vim-state 0.1.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.
@@ -0,0 +1,1235 @@
1
+ import { assign, setup } from "xstate";
2
+ import { nounForKey } from "./editor.js";
3
+ import { initialVimMode } from "./state.js";
4
+ export const vimMachine = setup({
5
+ types: {
6
+ context: {},
7
+ events: {},
8
+ input: {},
9
+ },
10
+ actions: {
11
+ appendCount: assign({
12
+ count: ({ context, event }) => (context.count ?? 0) * 10 + Number(event.key),
13
+ }),
14
+ clearCount: assign({ count: undefined }),
15
+ setOperator: assign({
16
+ operator: ({ context }, params) => ({
17
+ name: params.name,
18
+ count: context.count,
19
+ }),
20
+ count: undefined,
21
+ }),
22
+ clearOperator: assign({ operator: undefined }),
23
+ startVisual: assign({
24
+ visual: ({ context }, params) => ({
25
+ mode: params.mode,
26
+ anchor: context.editor.getCursor(),
27
+ }),
28
+ }),
29
+ clearVisual: assign({ visual: undefined }),
30
+ swapVisualAnchor: assign({
31
+ visual: ({ context }) => {
32
+ const visual = context.visual;
33
+ if (!visual) {
34
+ return undefined;
35
+ }
36
+ const active = context.editor.getCursor();
37
+ context.editor.move(visual.anchor);
38
+ return { ...visual, anchor: active };
39
+ },
40
+ }),
41
+ placeCaretAfterCursor: ({ context }) => context.editor.placeCaretAfterCursor(),
42
+ placeCaretAtLineEnd: ({ context }) => context.editor.placeCaretAtLineEnd(),
43
+ move: ({ context }, params) => repeat(context, () => context.editor.move(params.motion)),
44
+ moveByEventNoun: ({ context, event }) => {
45
+ const noun = nounForKey(event.key);
46
+ if (!noun || noun === "line" || typeof noun === "object") {
47
+ return;
48
+ }
49
+ repeat(context, () => context.editor.move(noun));
50
+ },
51
+ moveToChar: ({ context, event }, params) => context.editor.moveToChar(params.operation, params.direction, event.key, context.count ?? 1),
52
+ applyPendingOperatorToNoun: assign({
53
+ register: ({ context }, params) => applyPendingOperator(context, params.noun),
54
+ }),
55
+ insertLineBelow: ({ context }) => context.editor.insertLineBelow(),
56
+ insertLineAbove: ({ context }) => context.editor.insertLineAbove(),
57
+ joinLines: ({ context }) => context.editor.joinLines(context.count ?? 2),
58
+ join: ({ context }, params) => context.editor.join(params.target),
59
+ goToLine: ({ context }, params) => context.editor.goToLine(context.count ?? params.line),
60
+ placeCaretAtLineStart: ({ context }) => context.editor.placeCaretAtLineStart(),
61
+ delete: assign({
62
+ register: ({ context }, params) => context.editor.delete(params.target, context.count ?? 1) ??
63
+ context.register,
64
+ }),
65
+ change: assign({
66
+ register: ({ context }, params) => context.editor.change(params.target, context.count ?? 1) ??
67
+ context.register,
68
+ }),
69
+ yank: assign({
70
+ register: ({ context }, params) => context.editor.yank(params.target, context.count ?? 1) ??
71
+ context.register,
72
+ }),
73
+ replace: assign({
74
+ register: ({ context }, params) => context.register
75
+ ? (context.editor.replace(params.target, context.register) ??
76
+ context.register)
77
+ : context.register,
78
+ }),
79
+ put: ({ context }, params) => {
80
+ const register = context.register;
81
+ if (!register) {
82
+ return;
83
+ }
84
+ repeat(context, () => context.editor.put(register, params.placement));
85
+ },
86
+ replaceCharUnderCursor: ({ context }, params) => context.editor.replaceCharUnderCursor(params.char),
87
+ transformCase: ({ context }, params) => context.editor.transformCase(params.target, params.transform),
88
+ toggleCase: ({ context }) => context.editor.toggleCase(context.count ?? 1),
89
+ undo: ({ context }) => context.editor.undo(),
90
+ redo: ({ context }) => context.editor.redo(),
91
+ },
92
+ guards: {
93
+ keyIs: ({ event }, params) => event.key === params.key,
94
+ keyIsPrintable: ({ event }) => Array.from(event.key).length === 1 && event.key >= " ",
95
+ keyIsPrintableWithOperator: ({ context, event }, params) => context.operator?.name === params.name &&
96
+ Array.from(event.key).length === 1 &&
97
+ event.key >= " ",
98
+ keyIsWithOperator: ({ context, event }, params) => context.operator?.name === params.name && event.key === params.key,
99
+ keyExtendsCount: ({ context, event }) => /^[1-9]$/.test(event.key) ||
100
+ (event.key === "0" && context.count !== undefined),
101
+ keyIsMotionNoun: ({ event }) => nounForKey(event.key) !== undefined,
102
+ keyIsOperatorNoun: ({ context, event }, params) => context.operator?.name === params.name &&
103
+ nounForKey(event.key, context.operator) !== undefined,
104
+ },
105
+ }).createMachine({
106
+ id: "vim-pi",
107
+ context: ({ input }) => ({
108
+ editor: input.editor,
109
+ count: undefined,
110
+ operator: undefined,
111
+ register: undefined,
112
+ }),
113
+ initial: initialVimMode,
114
+ states: {
115
+ insert: {
116
+ on: {
117
+ KEY: {
118
+ guard: { type: "keyIs", params: { key: "escape" } },
119
+ target: "normal",
120
+ actions: { type: "move", params: { motion: "left" } },
121
+ },
122
+ },
123
+ },
124
+ replace: {
125
+ on: {
126
+ KEY: [
127
+ {
128
+ guard: { type: "keyIs", params: { key: "escape" } },
129
+ target: "normal",
130
+ actions: { type: "move", params: { motion: "left" } },
131
+ },
132
+ {
133
+ guard: { type: "keyIsPrintable" },
134
+ actions: ({ context }) => context.editor.delete("right"),
135
+ },
136
+ ],
137
+ },
138
+ },
139
+ "replace-once": {
140
+ on: {
141
+ KEY: [
142
+ {
143
+ guard: { type: "keyIs", params: { key: "escape" } },
144
+ target: "normal",
145
+ },
146
+ {
147
+ guard: { type: "keyIsPrintable" },
148
+ target: "normal",
149
+ actions: {
150
+ type: "replaceCharUnderCursor",
151
+ params: ({ event }) => ({ char: event.key }),
152
+ },
153
+ },
154
+ { target: "normal" },
155
+ ],
156
+ },
157
+ },
158
+ "find-forward": {
159
+ on: {
160
+ KEY: [
161
+ {
162
+ guard: { type: "keyIs", params: { key: "escape" } },
163
+ target: "normal",
164
+ actions: { type: "clearCount" },
165
+ },
166
+ {
167
+ guard: { type: "keyIsPrintable" },
168
+ target: "normal",
169
+ actions: [
170
+ {
171
+ type: "moveToChar",
172
+ params: { operation: "find", direction: "forward" },
173
+ },
174
+ { type: "clearVisual" },
175
+ { type: "clearCount" },
176
+ ],
177
+ },
178
+ { target: "normal", actions: { type: "clearCount" } },
179
+ ],
180
+ },
181
+ },
182
+ "find-backward": {
183
+ on: {
184
+ KEY: [
185
+ {
186
+ guard: { type: "keyIs", params: { key: "escape" } },
187
+ target: "normal",
188
+ actions: { type: "clearCount" },
189
+ },
190
+ {
191
+ guard: { type: "keyIsPrintable" },
192
+ target: "normal",
193
+ actions: [
194
+ {
195
+ type: "moveToChar",
196
+ params: { operation: "find", direction: "backward" },
197
+ },
198
+ { type: "clearVisual" },
199
+ { type: "clearCount" },
200
+ ],
201
+ },
202
+ { target: "normal", actions: { type: "clearCount" } },
203
+ ],
204
+ },
205
+ },
206
+ "till-forward": {
207
+ on: {
208
+ KEY: [
209
+ {
210
+ guard: { type: "keyIs", params: { key: "escape" } },
211
+ target: "normal",
212
+ actions: { type: "clearCount" },
213
+ },
214
+ {
215
+ guard: { type: "keyIsPrintable" },
216
+ target: "normal",
217
+ actions: [
218
+ {
219
+ type: "moveToChar",
220
+ params: { operation: "till", direction: "forward" },
221
+ },
222
+ { type: "clearVisual" },
223
+ { type: "clearCount" },
224
+ ],
225
+ },
226
+ { target: "normal", actions: { type: "clearCount" } },
227
+ ],
228
+ },
229
+ },
230
+ "till-backward": {
231
+ on: {
232
+ KEY: [
233
+ {
234
+ guard: { type: "keyIs", params: { key: "escape" } },
235
+ target: "normal",
236
+ actions: { type: "clearCount" },
237
+ },
238
+ {
239
+ guard: { type: "keyIsPrintable" },
240
+ target: "normal",
241
+ actions: [
242
+ {
243
+ type: "moveToChar",
244
+ params: { operation: "till", direction: "backward" },
245
+ },
246
+ { type: "clearVisual" },
247
+ { type: "clearCount" },
248
+ ],
249
+ },
250
+ { target: "normal", actions: { type: "clearCount" } },
251
+ ],
252
+ },
253
+ },
254
+ "operator-find-forward": {
255
+ on: {
256
+ KEY: [
257
+ {
258
+ guard: { type: "keyIs", params: { key: "escape" } },
259
+ target: "normal",
260
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
261
+ },
262
+ {
263
+ guard: {
264
+ type: "keyIsPrintableWithOperator",
265
+ params: { name: "change" },
266
+ },
267
+ target: "insert",
268
+ actions: [
269
+ {
270
+ type: "applyPendingOperatorToNoun",
271
+ params: ({ event }) => ({
272
+ noun: {
273
+ type: "find",
274
+ operation: "find",
275
+ direction: "forward",
276
+ char: event.key,
277
+ },
278
+ }),
279
+ },
280
+ { type: "clearOperator" },
281
+ { type: "clearVisual" },
282
+ { type: "clearCount" },
283
+ ],
284
+ },
285
+ {
286
+ guard: { type: "keyIsPrintable" },
287
+ target: "normal",
288
+ actions: [
289
+ {
290
+ type: "applyPendingOperatorToNoun",
291
+ params: ({ event }) => ({
292
+ noun: {
293
+ type: "find",
294
+ operation: "find",
295
+ direction: "forward",
296
+ char: event.key,
297
+ },
298
+ }),
299
+ },
300
+ { type: "clearOperator" },
301
+ { type: "clearVisual" },
302
+ { type: "clearCount" },
303
+ ],
304
+ },
305
+ {
306
+ target: "normal",
307
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
308
+ },
309
+ ],
310
+ },
311
+ },
312
+ "operator-find-backward": {
313
+ on: {
314
+ KEY: [
315
+ {
316
+ guard: { type: "keyIs", params: { key: "escape" } },
317
+ target: "normal",
318
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
319
+ },
320
+ {
321
+ guard: {
322
+ type: "keyIsPrintableWithOperator",
323
+ params: { name: "change" },
324
+ },
325
+ target: "insert",
326
+ actions: [
327
+ {
328
+ type: "applyPendingOperatorToNoun",
329
+ params: ({ event }) => ({
330
+ noun: {
331
+ type: "find",
332
+ operation: "find",
333
+ direction: "backward",
334
+ char: event.key,
335
+ },
336
+ }),
337
+ },
338
+ { type: "clearOperator" },
339
+ { type: "clearVisual" },
340
+ { type: "clearCount" },
341
+ ],
342
+ },
343
+ {
344
+ guard: { type: "keyIsPrintable" },
345
+ target: "normal",
346
+ actions: [
347
+ {
348
+ type: "applyPendingOperatorToNoun",
349
+ params: ({ event }) => ({
350
+ noun: {
351
+ type: "find",
352
+ operation: "find",
353
+ direction: "backward",
354
+ char: event.key,
355
+ },
356
+ }),
357
+ },
358
+ { type: "clearOperator" },
359
+ { type: "clearVisual" },
360
+ { type: "clearCount" },
361
+ ],
362
+ },
363
+ {
364
+ target: "normal",
365
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
366
+ },
367
+ ],
368
+ },
369
+ },
370
+ "operator-till-forward": {
371
+ on: {
372
+ KEY: [
373
+ {
374
+ guard: { type: "keyIs", params: { key: "escape" } },
375
+ target: "normal",
376
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
377
+ },
378
+ {
379
+ guard: {
380
+ type: "keyIsPrintableWithOperator",
381
+ params: { name: "change" },
382
+ },
383
+ target: "insert",
384
+ actions: [
385
+ {
386
+ type: "applyPendingOperatorToNoun",
387
+ params: ({ event }) => ({
388
+ noun: {
389
+ type: "find",
390
+ operation: "till",
391
+ direction: "forward",
392
+ char: event.key,
393
+ },
394
+ }),
395
+ },
396
+ { type: "clearOperator" },
397
+ { type: "clearCount" },
398
+ ],
399
+ },
400
+ {
401
+ guard: { type: "keyIsPrintable" },
402
+ target: "normal",
403
+ actions: [
404
+ {
405
+ type: "applyPendingOperatorToNoun",
406
+ params: ({ event }) => ({
407
+ noun: {
408
+ type: "find",
409
+ operation: "till",
410
+ direction: "forward",
411
+ char: event.key,
412
+ },
413
+ }),
414
+ },
415
+ { type: "clearOperator" },
416
+ { type: "clearCount" },
417
+ ],
418
+ },
419
+ {
420
+ target: "normal",
421
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
422
+ },
423
+ ],
424
+ },
425
+ },
426
+ "operator-till-backward": {
427
+ on: {
428
+ KEY: [
429
+ {
430
+ guard: { type: "keyIs", params: { key: "escape" } },
431
+ target: "normal",
432
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
433
+ },
434
+ {
435
+ guard: {
436
+ type: "keyIsPrintableWithOperator",
437
+ params: { name: "change" },
438
+ },
439
+ target: "insert",
440
+ actions: [
441
+ {
442
+ type: "applyPendingOperatorToNoun",
443
+ params: ({ event }) => ({
444
+ noun: {
445
+ type: "find",
446
+ operation: "till",
447
+ direction: "backward",
448
+ char: event.key,
449
+ },
450
+ }),
451
+ },
452
+ { type: "clearOperator" },
453
+ { type: "clearCount" },
454
+ ],
455
+ },
456
+ {
457
+ guard: { type: "keyIsPrintable" },
458
+ target: "normal",
459
+ actions: [
460
+ {
461
+ type: "applyPendingOperatorToNoun",
462
+ params: ({ event }) => ({
463
+ noun: {
464
+ type: "find",
465
+ operation: "till",
466
+ direction: "backward",
467
+ char: event.key,
468
+ },
469
+ }),
470
+ },
471
+ { type: "clearOperator" },
472
+ { type: "clearCount" },
473
+ ],
474
+ },
475
+ {
476
+ target: "normal",
477
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
478
+ },
479
+ ],
480
+ },
481
+ },
482
+ "operator-inner-object": {
483
+ on: {
484
+ KEY: [
485
+ {
486
+ guard: { type: "keyIs", params: { key: "escape" } },
487
+ target: "normal",
488
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
489
+ },
490
+ {
491
+ guard: {
492
+ type: "keyIsWithOperator",
493
+ params: { name: "change", key: "w" },
494
+ },
495
+ target: "insert",
496
+ actions: [
497
+ {
498
+ type: "applyPendingOperatorToNoun",
499
+ params: {
500
+ noun: {
501
+ type: "textObject",
502
+ kind: "inner",
503
+ object: "word",
504
+ },
505
+ },
506
+ },
507
+ { type: "clearOperator" },
508
+ { type: "clearCount" },
509
+ ],
510
+ },
511
+ {
512
+ guard: { type: "keyIs", params: { key: "w" } },
513
+ target: "normal",
514
+ actions: [
515
+ {
516
+ type: "applyPendingOperatorToNoun",
517
+ params: {
518
+ noun: {
519
+ type: "textObject",
520
+ kind: "inner",
521
+ object: "word",
522
+ },
523
+ },
524
+ },
525
+ { type: "clearOperator" },
526
+ { type: "clearCount" },
527
+ ],
528
+ },
529
+ {
530
+ target: "normal",
531
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
532
+ },
533
+ ],
534
+ },
535
+ },
536
+ "operator-around-object": {
537
+ on: {
538
+ KEY: [
539
+ {
540
+ guard: { type: "keyIs", params: { key: "escape" } },
541
+ target: "normal",
542
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
543
+ },
544
+ {
545
+ guard: {
546
+ type: "keyIsWithOperator",
547
+ params: { name: "change", key: "w" },
548
+ },
549
+ target: "insert",
550
+ actions: [
551
+ {
552
+ type: "applyPendingOperatorToNoun",
553
+ params: {
554
+ noun: {
555
+ type: "textObject",
556
+ kind: "around",
557
+ object: "word",
558
+ },
559
+ },
560
+ },
561
+ { type: "clearOperator" },
562
+ { type: "clearCount" },
563
+ ],
564
+ },
565
+ {
566
+ guard: { type: "keyIs", params: { key: "w" } },
567
+ target: "normal",
568
+ actions: [
569
+ {
570
+ type: "applyPendingOperatorToNoun",
571
+ params: {
572
+ noun: {
573
+ type: "textObject",
574
+ kind: "around",
575
+ object: "word",
576
+ },
577
+ },
578
+ },
579
+ { type: "clearOperator" },
580
+ { type: "clearCount" },
581
+ ],
582
+ },
583
+ {
584
+ target: "normal",
585
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
586
+ },
587
+ ],
588
+ },
589
+ },
590
+ "g-prefix": {
591
+ on: {
592
+ KEY: [
593
+ {
594
+ guard: { type: "keyIs", params: { key: "escape" } },
595
+ target: "normal",
596
+ actions: { type: "clearCount" },
597
+ },
598
+ {
599
+ guard: { type: "keyIs", params: { key: "g" } },
600
+ target: "normal",
601
+ actions: [
602
+ { type: "goToLine", params: { line: "first" } },
603
+ { type: "clearCount" },
604
+ ],
605
+ },
606
+ { target: "normal", actions: { type: "clearCount" } },
607
+ ],
608
+ },
609
+ },
610
+ "operator-pending": {
611
+ on: {
612
+ KEY: [
613
+ {
614
+ guard: { type: "keyIs", params: { key: "escape" } },
615
+ target: "normal",
616
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
617
+ },
618
+ {
619
+ guard: { type: "keyExtendsCount" },
620
+ actions: { type: "appendCount" },
621
+ },
622
+ {
623
+ guard: { type: "keyIs", params: { key: "f" } },
624
+ target: "operator-find-forward",
625
+ },
626
+ {
627
+ guard: { type: "keyIs", params: { key: "F" } },
628
+ target: "operator-find-backward",
629
+ },
630
+ {
631
+ guard: { type: "keyIs", params: { key: "t" } },
632
+ target: "operator-till-forward",
633
+ },
634
+ {
635
+ guard: { type: "keyIs", params: { key: "T" } },
636
+ target: "operator-till-backward",
637
+ },
638
+ {
639
+ guard: { type: "keyIs", params: { key: "i" } },
640
+ target: "operator-inner-object",
641
+ },
642
+ {
643
+ guard: { type: "keyIs", params: { key: "a" } },
644
+ target: "operator-around-object",
645
+ },
646
+ {
647
+ guard: { type: "keyIsOperatorNoun", params: { name: "change" } },
648
+ target: "insert",
649
+ actions: [
650
+ {
651
+ type: "applyPendingOperatorToNoun",
652
+ params: ({ context, event }) => ({
653
+ noun: nounForKey(event.key, context.operator),
654
+ }),
655
+ },
656
+ { type: "clearOperator" },
657
+ { type: "clearCount" },
658
+ ],
659
+ },
660
+ {
661
+ guard: { type: "keyIsOperatorNoun", params: { name: "delete" } },
662
+ target: "normal",
663
+ actions: [
664
+ {
665
+ type: "applyPendingOperatorToNoun",
666
+ params: ({ context, event }) => ({
667
+ noun: nounForKey(event.key, context.operator),
668
+ }),
669
+ },
670
+ { type: "clearOperator" },
671
+ { type: "clearCount" },
672
+ ],
673
+ },
674
+ {
675
+ guard: { type: "keyIsOperatorNoun", params: { name: "yank" } },
676
+ target: "normal",
677
+ actions: [
678
+ {
679
+ type: "applyPendingOperatorToNoun",
680
+ params: ({ context, event }) => ({
681
+ noun: nounForKey(event.key, context.operator),
682
+ }),
683
+ },
684
+ { type: "clearOperator" },
685
+ { type: "clearCount" },
686
+ ],
687
+ },
688
+ {
689
+ target: "normal",
690
+ actions: [{ type: "clearOperator" }, { type: "clearCount" }],
691
+ },
692
+ ],
693
+ },
694
+ },
695
+ "visual-char": {
696
+ entry: { type: "startVisual", params: { mode: "charwise" } },
697
+ // visual entry sets `context.visual` before these actions; leaving actions
698
+ // consume it before clearVisual runs.
699
+ on: {
700
+ KEY: [
701
+ {
702
+ guard: { type: "keyIs", params: { key: "escape" } },
703
+ target: "normal",
704
+ actions: [{ type: "clearVisual" }, { type: "clearCount" }],
705
+ },
706
+ {
707
+ guard: { type: "keyExtendsCount" },
708
+ actions: { type: "appendCount" },
709
+ },
710
+ {
711
+ guard: { type: "keyIs", params: { key: "o" } },
712
+ actions: [{ type: "swapVisualAnchor" }, { type: "clearCount" }],
713
+ },
714
+ {
715
+ guard: { type: "keyIs", params: { key: "O" } },
716
+ actions: [{ type: "swapVisualAnchor" }, { type: "clearCount" }],
717
+ },
718
+ {
719
+ guard: { type: "keyIs", params: { key: "y" } },
720
+ target: "normal",
721
+ actions: [
722
+ {
723
+ type: "yank",
724
+ params: ({ context }) => ({ target: context.visual }),
725
+ },
726
+ { type: "clearVisual" },
727
+ { type: "clearCount" },
728
+ ],
729
+ },
730
+ {
731
+ guard: { type: "keyIs", params: { key: "d" } },
732
+ target: "normal",
733
+ actions: [
734
+ {
735
+ type: "delete",
736
+ params: ({ context }) => ({ target: context.visual }),
737
+ },
738
+ { type: "clearVisual" },
739
+ { type: "clearCount" },
740
+ ],
741
+ },
742
+ {
743
+ guard: { type: "keyIs", params: { key: "x" } },
744
+ target: "normal",
745
+ actions: [
746
+ {
747
+ type: "delete",
748
+ params: ({ context }) => ({ target: context.visual }),
749
+ },
750
+ { type: "clearVisual" },
751
+ { type: "clearCount" },
752
+ ],
753
+ },
754
+ {
755
+ guard: { type: "keyIs", params: { key: "c" } },
756
+ target: "insert",
757
+ actions: [
758
+ {
759
+ type: "change",
760
+ params: ({ context }) => ({ target: context.visual }),
761
+ },
762
+ { type: "clearVisual" },
763
+ { type: "clearCount" },
764
+ ],
765
+ },
766
+ {
767
+ guard: { type: "keyIs", params: { key: "p" } },
768
+ target: "normal",
769
+ actions: [
770
+ {
771
+ type: "replace",
772
+ params: ({ context }) => ({ target: context.visual }),
773
+ },
774
+ { type: "clearVisual" },
775
+ { type: "clearCount" },
776
+ ],
777
+ },
778
+ {
779
+ guard: { type: "keyIs", params: { key: "P" } },
780
+ target: "normal",
781
+ actions: [
782
+ {
783
+ type: "replace",
784
+ params: ({ context }) => ({ target: context.visual }),
785
+ },
786
+ { type: "clearVisual" },
787
+ { type: "clearCount" },
788
+ ],
789
+ },
790
+ {
791
+ guard: { type: "keyIs", params: { key: "~" } },
792
+ target: "normal",
793
+ actions: [
794
+ {
795
+ type: "transformCase",
796
+ params: ({ context }) => ({
797
+ target: context.visual,
798
+ transform: "toggle",
799
+ }),
800
+ },
801
+ { type: "clearVisual" },
802
+ { type: "clearCount" },
803
+ ],
804
+ },
805
+ {
806
+ guard: { type: "keyIs", params: { key: "u" } },
807
+ target: "normal",
808
+ actions: [
809
+ {
810
+ type: "transformCase",
811
+ params: ({ context }) => ({
812
+ target: context.visual,
813
+ transform: "lower",
814
+ }),
815
+ },
816
+ { type: "clearVisual" },
817
+ { type: "clearCount" },
818
+ ],
819
+ },
820
+ {
821
+ guard: { type: "keyIs", params: { key: "U" } },
822
+ target: "normal",
823
+ actions: [
824
+ {
825
+ type: "transformCase",
826
+ params: ({ context }) => ({
827
+ target: context.visual,
828
+ transform: "upper",
829
+ }),
830
+ },
831
+ { type: "clearVisual" },
832
+ { type: "clearCount" },
833
+ ],
834
+ },
835
+ {
836
+ guard: { type: "keyIs", params: { key: "J" } },
837
+ target: "normal",
838
+ actions: [
839
+ {
840
+ type: "join",
841
+ params: ({ context }) => ({ target: context.visual }),
842
+ },
843
+ { type: "clearVisual" },
844
+ { type: "clearCount" },
845
+ ],
846
+ },
847
+ {
848
+ guard: { type: "keyIs", params: { key: "G" } },
849
+ actions: [
850
+ { type: "goToLine", params: { line: "last" } },
851
+ { type: "clearCount" },
852
+ ],
853
+ },
854
+ {
855
+ guard: { type: "keyIsMotionNoun" },
856
+ actions: [{ type: "moveByEventNoun" }, { type: "clearCount" }],
857
+ },
858
+ { actions: { type: "clearCount" } },
859
+ ],
860
+ },
861
+ },
862
+ "visual-line": {
863
+ // visual entry sets `context.visual` before these actions; leaving actions
864
+ // consume it before clearVisual runs.
865
+ entry: { type: "startVisual", params: { mode: "linewise" } },
866
+ on: {
867
+ KEY: [
868
+ {
869
+ guard: { type: "keyIs", params: { key: "escape" } },
870
+ target: "normal",
871
+ actions: [{ type: "clearVisual" }, { type: "clearCount" }],
872
+ },
873
+ {
874
+ guard: { type: "keyExtendsCount" },
875
+ actions: { type: "appendCount" },
876
+ },
877
+ {
878
+ guard: { type: "keyIs", params: { key: "o" } },
879
+ actions: [{ type: "swapVisualAnchor" }, { type: "clearCount" }],
880
+ },
881
+ {
882
+ guard: { type: "keyIs", params: { key: "O" } },
883
+ actions: [{ type: "swapVisualAnchor" }, { type: "clearCount" }],
884
+ },
885
+ {
886
+ guard: { type: "keyIs", params: { key: "y" } },
887
+ target: "normal",
888
+ actions: [
889
+ {
890
+ type: "yank",
891
+ params: ({ context }) => ({ target: context.visual }),
892
+ },
893
+ { type: "clearVisual" },
894
+ { type: "clearCount" },
895
+ ],
896
+ },
897
+ {
898
+ guard: { type: "keyIs", params: { key: "d" } },
899
+ target: "normal",
900
+ actions: [
901
+ {
902
+ type: "delete",
903
+ params: ({ context }) => ({ target: context.visual }),
904
+ },
905
+ { type: "clearVisual" },
906
+ { type: "clearCount" },
907
+ ],
908
+ },
909
+ {
910
+ guard: { type: "keyIs", params: { key: "x" } },
911
+ target: "normal",
912
+ actions: [
913
+ {
914
+ type: "delete",
915
+ params: ({ context }) => ({ target: context.visual }),
916
+ },
917
+ { type: "clearVisual" },
918
+ { type: "clearCount" },
919
+ ],
920
+ },
921
+ {
922
+ guard: { type: "keyIs", params: { key: "c" } },
923
+ target: "insert",
924
+ actions: [
925
+ {
926
+ type: "change",
927
+ params: ({ context }) => ({ target: context.visual }),
928
+ },
929
+ { type: "clearVisual" },
930
+ { type: "clearCount" },
931
+ ],
932
+ },
933
+ {
934
+ guard: { type: "keyIs", params: { key: "p" } },
935
+ target: "normal",
936
+ actions: [
937
+ {
938
+ type: "replace",
939
+ params: ({ context }) => ({ target: context.visual }),
940
+ },
941
+ { type: "clearVisual" },
942
+ { type: "clearCount" },
943
+ ],
944
+ },
945
+ {
946
+ guard: { type: "keyIs", params: { key: "P" } },
947
+ target: "normal",
948
+ actions: [
949
+ {
950
+ type: "replace",
951
+ params: ({ context }) => ({ target: context.visual }),
952
+ },
953
+ { type: "clearVisual" },
954
+ { type: "clearCount" },
955
+ ],
956
+ },
957
+ {
958
+ guard: { type: "keyIs", params: { key: "~" } },
959
+ target: "normal",
960
+ actions: [
961
+ {
962
+ type: "transformCase",
963
+ params: ({ context }) => ({
964
+ target: context.visual,
965
+ transform: "toggle",
966
+ }),
967
+ },
968
+ { type: "clearVisual" },
969
+ { type: "clearCount" },
970
+ ],
971
+ },
972
+ {
973
+ guard: { type: "keyIs", params: { key: "u" } },
974
+ target: "normal",
975
+ actions: [
976
+ {
977
+ type: "transformCase",
978
+ params: ({ context }) => ({
979
+ target: context.visual,
980
+ transform: "lower",
981
+ }),
982
+ },
983
+ { type: "clearVisual" },
984
+ { type: "clearCount" },
985
+ ],
986
+ },
987
+ {
988
+ guard: { type: "keyIs", params: { key: "U" } },
989
+ target: "normal",
990
+ actions: [
991
+ {
992
+ type: "transformCase",
993
+ params: ({ context }) => ({
994
+ target: context.visual,
995
+ transform: "upper",
996
+ }),
997
+ },
998
+ { type: "clearVisual" },
999
+ { type: "clearCount" },
1000
+ ],
1001
+ },
1002
+ {
1003
+ guard: { type: "keyIs", params: { key: "J" } },
1004
+ target: "normal",
1005
+ actions: [
1006
+ {
1007
+ type: "join",
1008
+ params: ({ context }) => ({ target: context.visual }),
1009
+ },
1010
+ { type: "clearVisual" },
1011
+ { type: "clearCount" },
1012
+ ],
1013
+ },
1014
+ {
1015
+ guard: { type: "keyIs", params: { key: "G" } },
1016
+ actions: [
1017
+ { type: "goToLine", params: { line: "last" } },
1018
+ { type: "clearCount" },
1019
+ ],
1020
+ },
1021
+ {
1022
+ guard: { type: "keyIsMotionNoun" },
1023
+ actions: [{ type: "moveByEventNoun" }, { type: "clearCount" }],
1024
+ },
1025
+ { actions: { type: "clearCount" } },
1026
+ ],
1027
+ },
1028
+ },
1029
+ normal: {
1030
+ on: {
1031
+ KEY: [
1032
+ {
1033
+ guard: { type: "keyExtendsCount" },
1034
+ actions: { type: "appendCount" },
1035
+ },
1036
+ {
1037
+ guard: { type: "keyIs", params: { key: "i" } },
1038
+ target: "insert",
1039
+ actions: { type: "clearCount" },
1040
+ },
1041
+ {
1042
+ guard: { type: "keyIs", params: { key: "a" } },
1043
+ target: "insert",
1044
+ actions: [
1045
+ { type: "placeCaretAfterCursor" },
1046
+ { type: "clearCount" },
1047
+ ],
1048
+ },
1049
+ {
1050
+ guard: { type: "keyIs", params: { key: "A" } },
1051
+ target: "insert",
1052
+ actions: [{ type: "placeCaretAtLineEnd" }, { type: "clearCount" }],
1053
+ },
1054
+ {
1055
+ guard: { type: "keyIs", params: { key: "I" } },
1056
+ target: "insert",
1057
+ actions: [
1058
+ { type: "move", params: { motion: "firstNonBlank" } },
1059
+ { type: "clearCount" },
1060
+ ],
1061
+ },
1062
+ {
1063
+ guard: { type: "keyIs", params: { key: "R" } },
1064
+ target: "replace",
1065
+ actions: { type: "clearCount" },
1066
+ },
1067
+ {
1068
+ guard: { type: "keyIs", params: { key: "r" } },
1069
+ target: "replace-once",
1070
+ actions: { type: "clearCount" },
1071
+ },
1072
+ {
1073
+ guard: { type: "keyIs", params: { key: "d" } },
1074
+ target: "operator-pending",
1075
+ actions: { type: "setOperator", params: { name: "delete" } },
1076
+ },
1077
+ {
1078
+ guard: { type: "keyIs", params: { key: "c" } },
1079
+ target: "operator-pending",
1080
+ actions: { type: "setOperator", params: { name: "change" } },
1081
+ },
1082
+ {
1083
+ guard: { type: "keyIs", params: { key: "y" } },
1084
+ target: "operator-pending",
1085
+ actions: { type: "setOperator", params: { name: "yank" } },
1086
+ },
1087
+ {
1088
+ guard: { type: "keyIs", params: { key: "v" } },
1089
+ target: "visual-char",
1090
+ actions: { type: "clearCount" },
1091
+ },
1092
+ {
1093
+ guard: { type: "keyIs", params: { key: "V" } },
1094
+ target: "visual-line",
1095
+ actions: { type: "clearCount" },
1096
+ },
1097
+ {
1098
+ guard: { type: "keyIs", params: { key: "C" } },
1099
+ target: "insert",
1100
+ actions: [
1101
+ { type: "change", params: { target: "lineEnd" } },
1102
+ { type: "placeCaretAfterCursor" },
1103
+ { type: "clearCount" },
1104
+ ],
1105
+ },
1106
+ {
1107
+ guard: { type: "keyIs", params: { key: "G" } },
1108
+ actions: [
1109
+ { type: "goToLine", params: { line: "last" } },
1110
+ { type: "clearCount" },
1111
+ ],
1112
+ },
1113
+ {
1114
+ guard: { type: "keyIs", params: { key: "f" } },
1115
+ target: "find-forward",
1116
+ },
1117
+ {
1118
+ guard: { type: "keyIs", params: { key: "F" } },
1119
+ target: "find-backward",
1120
+ },
1121
+ {
1122
+ guard: { type: "keyIs", params: { key: "t" } },
1123
+ target: "till-forward",
1124
+ },
1125
+ {
1126
+ guard: { type: "keyIs", params: { key: "T" } },
1127
+ target: "till-backward",
1128
+ },
1129
+ {
1130
+ guard: { type: "keyIs", params: { key: "g" } },
1131
+ target: "g-prefix",
1132
+ },
1133
+ {
1134
+ guard: { type: "keyIs", params: { key: "u" } },
1135
+ actions: [{ type: "undo" }, { type: "clearCount" }],
1136
+ },
1137
+ {
1138
+ guard: { type: "keyIs", params: { key: "ctrl+r" } },
1139
+ actions: [{ type: "redo" }, { type: "clearCount" }],
1140
+ },
1141
+ {
1142
+ guard: { type: "keyIs", params: { key: "D" } },
1143
+ actions: [
1144
+ { type: "delete", params: { target: "lineEnd" } },
1145
+ { type: "clearCount" },
1146
+ ],
1147
+ },
1148
+ {
1149
+ guard: { type: "keyIs", params: { key: "J" } },
1150
+ actions: [{ type: "joinLines" }, { type: "clearCount" }],
1151
+ },
1152
+ {
1153
+ guard: { type: "keyIs", params: { key: "o" } },
1154
+ target: "insert",
1155
+ actions: [
1156
+ { type: "insertLineBelow" },
1157
+ { type: "placeCaretAtLineStart" },
1158
+ { type: "clearCount" },
1159
+ ],
1160
+ },
1161
+ {
1162
+ guard: { type: "keyIs", params: { key: "O" } },
1163
+ target: "insert",
1164
+ actions: [
1165
+ { type: "insertLineAbove" },
1166
+ { type: "placeCaretAtLineStart" },
1167
+ { type: "clearCount" },
1168
+ ],
1169
+ },
1170
+ {
1171
+ guard: { type: "keyIsMotionNoun" },
1172
+ actions: [{ type: "moveByEventNoun" }, { type: "clearCount" }],
1173
+ },
1174
+ {
1175
+ guard: { type: "keyIs", params: { key: "x" } },
1176
+ actions: [
1177
+ { type: "delete", params: { target: "right" } },
1178
+ { type: "clearCount" },
1179
+ ],
1180
+ },
1181
+ {
1182
+ guard: { type: "keyIs", params: { key: "X" } },
1183
+ actions: [
1184
+ { type: "delete", params: { target: "left" } },
1185
+ { type: "clearCount" },
1186
+ ],
1187
+ },
1188
+ {
1189
+ guard: { type: "keyIs", params: { key: "~" } },
1190
+ actions: [{ type: "toggleCase" }, { type: "clearCount" }],
1191
+ },
1192
+ {
1193
+ guard: { type: "keyIs", params: { key: "p" } },
1194
+ actions: [
1195
+ { type: "put", params: { placement: "after" } },
1196
+ { type: "clearCount" },
1197
+ ],
1198
+ },
1199
+ {
1200
+ guard: { type: "keyIs", params: { key: "P" } },
1201
+ actions: [
1202
+ { type: "put", params: { placement: "before" } },
1203
+ { type: "clearCount" },
1204
+ ],
1205
+ },
1206
+ { actions: { type: "clearCount" } },
1207
+ ],
1208
+ },
1209
+ },
1210
+ },
1211
+ });
1212
+ /** Run an action according to the current Normal-mode count. */
1213
+ function repeat(context, action) {
1214
+ for (let i = 0; i < (context.count ?? 1); ++i) {
1215
+ action();
1216
+ }
1217
+ }
1218
+ /** Apply the pending operator to an already-resolved noun. */
1219
+ function applyPendingOperator(context, noun) {
1220
+ if (!context.operator) {
1221
+ return context.register;
1222
+ }
1223
+ const count = (context.operator.count ?? 1) * (context.count ?? 1);
1224
+ const register = context.operator.name === "change"
1225
+ ? context.editor.change(noun, count)
1226
+ : context.operator.name === "delete"
1227
+ ? context.editor.delete(noun, count)
1228
+ : context.editor.yank(noun, count);
1229
+ // Change enters Insert mode; after c$/C, place the caret after the
1230
+ // remaining last character instead of before it.
1231
+ if (context.operator.name === "change" && noun === "lineEnd") {
1232
+ context.editor.placeCaretAfterCursor();
1233
+ }
1234
+ return register ?? context.register;
1235
+ }