summaryrefslogtreecommitdiff
path: root/lib/command.ml
blob: 7dedc6c2719cd2d47ee0f7f5e26c5ac4ab704f01 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
open Base
open Key

type register = char option
type count = int option
type operation = Noop | Yank | Paste | Delete | Change
type scope = Line | To_bol | To_eol | Down | Left | Right | Up

type command =
  | Type of char
  | Simple of Key.t
  | Partial of Key.t
  | Shortcut of register * count * operation * scope
  | Chord of register * count * operation * count * scope

type t = command

let shortcut ?r ?n c s = Shortcut (r, n, c, s)
let chord ?r ?n1 c ?n2 m = Chord (r, n1, c, n2, m)

let i_stream =
  let step s k =
    let open Sequence.Step in
    match (s, k) with
    | `start, Key c -> Yield { value = Type c; state = `start }
    | `start, _ -> Yield { value = Simple k; state = `start }
    | _, _ -> Skip { state = `start }
  in
  Sequence.unfold_with ~init:`start ~f:step Key.stream

let simple_movements =
  [
    Key 'h';
    Key 'j';
    Key 'k';
    Key 'l';
    Key ' ';
    Arrow_up;
    Arrow_down;
    Arrow_left;
    Arrow_right;
    Backspace;
  ]

let to_scope = function
  | Key 'j' | Arrow_down -> Down
  | Key 'h' | Arrow_left | Backspace -> Left
  | Key 'l' | Key ' ' | Arrow_right -> Right
  | Key 'k' | Arrow_up -> Up
  | _ -> failwith "Invalid motion."

let n_stream =
  let step s k =
    let open Sequence.Step in
    let is_chord_op c = String.contains "ydc" (Char.lowercase c) in
    let is_simple_movement k = List.mem ~equal:Poly.equal simple_movements k in
    let to_op c =
      match Char.lowercase c with
      | 'y' -> Yank
      | 'p' -> Paste
      | 'd' -> Delete
      | 'c' -> Change
      | _ -> failwith "Invalid operation in chord."
    in
    match (s, k) with
    | `start, Key '"' -> Yield { value = Partial k; state = `chord_reg_pre }
    | `chord_reg_pre, Key c -> Yield { value = Partial k; state = `chord_reg c }
    | `chord_reg r, Key n when Char.('1' <= n && n <= '9') ->
        let n = Char.to_int n - 48 in
        Yield { value = Partial k; state = `chord_n (Some r, n) }
    | `start, Key n when Char.('1' <= n && n <= '9') ->
        let n = Char.to_int n - 48 in
        Yield { value = Partial k; state = `chord_n (None, n) }
    | `chord_n (r, m), Key n when Char.('0' <= n && n <= '9') ->
        let n = (10 * m) + Char.to_int n - 48 in
        Yield { value = Partial k; state = `chord_n (r, n) }
    | `start, Key c when is_chord_op c ->
        if Char.is_uppercase c then
          Yield { value = shortcut (to_op c) To_eol; state = `start }
        else
          Yield { value = Partial k; state = `chord_cmd (None, None, to_op c) }
    | `chord_reg r, Key c when is_chord_op c ->
        if Char.is_uppercase c then
          Yield { value = shortcut ~r (to_op c) To_eol; state = `start }
        else
          Yield
            { value = Partial k; state = `chord_cmd (Some r, None, to_op c) }
    | `chord_n (r, n), Key c when is_chord_op c ->
        if Char.is_uppercase c then
          Yield { value = shortcut ?r ~n (to_op c) To_eol; state = `start }
        else
          Yield { value = Partial k; state = `chord_cmd (r, Some n, to_op c) }
    | `chord_cmd (r, n, c), Key ch when is_chord_op ch && Poly.(c = to_op ch) ->
        if Char.is_uppercase ch then
          Yield { value = shortcut ?r ?n c To_bol; state = `start }
        else Yield { value = shortcut ?r ?n c Line; state = `start }
    | (`start | `chord_reg _), k when is_simple_movement k ->
        Yield { value = chord Noop (to_scope k); state = `start }
    | `chord_n (_, n), k when is_simple_movement k ->
        Yield { value = chord ~n1:n Noop (to_scope k); state = `start }
    | `chord_cmd (r, n, c), k when is_simple_movement k ->
        Yield { value = chord ?r ?n1:n c (to_scope k); state = `start }
    | `chord_cmd (r, n1, c), Key n when Char.('1' <= n && n <= '9') ->
        let n = Char.to_int n - 48 in
        Yield { value = Partial k; state = `chord_mv_n (r, n1, c, n) }
    | `chord_mv_n (r, n1, c, n2), Key n when Char.('0' <= n && n <= '9') ->
        let n2 = (10 * n2) + Char.to_int n - 48 in
        Yield { value = Partial k; state = `chord_mv_n (r, n1, c, n2) }
    | `chord_mv_n (r, n1, c, n2), k when is_simple_movement k ->
        Yield { value = chord ?r ?n1 c ~n2 (to_scope k); state = `start }
    (* Catch-all rules *)
    | `start, _ -> Yield { value = Simple k; state = `start }
    | _, _ -> Skip { state = `start }
  in
  Sequence.unfold_with ~init:`start ~f:step Key.stream