summaryrefslogtreecommitdiffstats
path: root/modules/neovim/init/mappings.nix
blob: b9fe9225c913bb69144b29bcb4060f2fc248296b (plain)
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
{
  config,
  lib,
  ...
}: let
  inherit (lib.modules) mkMerge;
  inherit (lib.options) mkOption literalMD;
  inherit (lib.types) either str listOf attrsOf nullOr submodule;
  inherit (lib.attrsets) mapAttrsToList;
  inherit (lib.lists) flatten;
  inherit (lib.trivial) pipe;
  inherit (lib.options) mkEnableOption;
  inherit (lib.nvim.config) mkBool;

  mapConfigOptions = {
    desc = mkOption {
      type = nullOr str;
      default = null;
      description = ''
        Description for the keybind, to be shown in which-key, if you have enabled
        in the module system.
      '';
    };

    action = mkOption {
      type = str;
      description = "The command to execute.";
    };

    lua = mkBool false ''
      If true, `action` is considered to be lua code.
      Thus, it will not be wrapped in `""`.
    '';

    silent = mkBool true "Whether this mapping should be silent. Equivalent to adding <silent> to a map.";
    nowait = mkBool false "Whether to wait for extra input on ambiguous mappings. Equivalent to adding <nowait> to a map.";
    script = mkBool false "Equivalent to adding <script> to a map.";
    expr = mkBool false "Means that the action is actually an expression. Equivalent to adding <expr> to a map.";
    unique = mkBool false "Whether to fail if the map is already defined. Equivalent to adding <unique> to a map.";
    noremap = mkBool true "Whether to use the 'noremap' variant of the command, ignoring any custom mappings on the defined action. It is highly advised to keep this on, which is the default.";
  };

  mapType = submodule {
    options =
      mapConfigOptions
      // {
        key = mkOption {
          type = nullOr str;
          description = "The key that triggers this keybind.";
        };
        mode = mkOption {
          type = either str (listOf str);
          description = ''
            The short-name of the mode to set the keymapping for. Passing an empty string is the equivalent of `:map`.

            See `:help map-modes` for a list of modes.
          '';
          example = literalMD ''`["n" "v" "c"]` for normal, visual and command mode'';
        };
      };
  };

  legacyMapOption = mode:
    mkOption {
      description = "Mappings for ${mode} mode";
      visible = false;
      type = attrsOf (submodule {
        options = mapConfigOptions;
      });
    };

  legacyMapModes = {
    normal = ["n"];
    insert = ["i"];
    select = ["s"];
    visual = ["v"];
    terminal = ["t"];
    normalVisualOp = ["n" "v" "o"];
    visualOnly = ["n" "x"];
    operator = ["o"];
    insertCommand = ["i" "c"];
    lang = ["l"];
    command = ["c"];
  };

  cfg = config.vim;
in {
  options.vim = {
    vendoredKeymaps.enable =
      mkEnableOption "this project's vendored keymaps by default"
      // {
        default = true;
        example = false;
      };

    keymaps = mkOption {
      type = listOf mapType;
      description = "Custom keybindings.";
      example = ''
        vim.keymaps = [
          {
            key = "<leader>m";
            mode = "n";
            silent = true;
            action = ":make<CR>";
          }
          {
            key = "<leader>l";
            mode = ["n" "x"];
            silent = true;
            action = "<cmd>cnext<CR>";
          }
        ];
      '';
      default = {};
    };

    maps = {
      normal = legacyMapOption "normal";
      insert = legacyMapOption "insert";
      select = legacyMapOption "select";
      visual = legacyMapOption "visual and select";
      terminal = legacyMapOption "terminal";
      normalVisualOp = legacyMapOption "normal, visual, select and operator-pending (same as plain 'map')";

      visualOnly = legacyMapOption "visual only";
      operator = legacyMapOption "operator-pending";
      insertCommand = legacyMapOption "insert and command-line";
      lang = legacyMapOption "insert, command-line and lang-arg";
      command = legacyMapOption "command-line";
    };
  };

  config = {
    vim.keymaps = mkMerge [
      (
        pipe cfg.maps
        [
          (mapAttrsToList (
            oldMode: keybinds:
              mapAttrsToList (
                key: bind:
                  bind
                  // {
                    inherit key;
                    mode = legacyMapModes.${oldMode};
                  }
              )
              keybinds
          ))
          flatten
        ]
      )
    ];
  };
}