neovim-flake/docs/manual/hacking/keybinds.md

5.9 KiB

Keybinds

As of 0.4, there exists an API for writing your own keybinds and a couple of useful utility functions are available in the extended standard library. The following section contains a general overview to how you may utilize said functions.

Custom Key Mappings Support for a Plugin

To set a mapping, you should define it in vim.maps.<<mode>>. The available modes are:

  • normal
  • insert
  • select
  • visual
  • terminal
  • normalVisualOp
  • visualOnly
  • operator
  • insertCommand
  • lang
  • command

An example, simple keybinding, can look like this:

{
  vim.maps.normal = {
    "<leader>wq" = {
      action = ":wq<CR>";
      silent = true;
      desc = "Save file and quit";
    };
  };
}

There are many settings available in the options. Please refer to the documentation to see a list of them.

neovim-flake provides a list of helper commands, so that you don't have to write the mapping attribute sets every time:

  • mkBinding = key: action: desc: - makes a basic binding, with silent set to true.
  • mkExprBinding = key: action: desc: - makes an expression binding, with lua, silent, and expr set to true.
  • mkLuaBinding = key: action: desc: - makes an expression binding, with lua, and silent set to true.

Note that the Lua in these bindings is actual Lua, not pasted into a :lua command. Therefore, you either pass in a function like require('someplugin').some_function, without actually calling it, or you define your own function, like function() require('someplugin').some_function() end.

Additionally, to not have to repeat the descriptions, there's another utility function with its own set of functions:

Utility function that takes two attrsets:

  • { someKey = "some_value" }
  • { someKey = { description = "Some Description"; }; }

and merges them into { someKey = { value = "some_value"; description = "Some Description"; }; }

addDescriptionsToMappings = actualMappings: mappingDefinitions:

This function can be used in combination with the same mkBinding functions as above, except they only take two arguments - binding and action, and have different names:

  • mkSetBinding = binding: action: - makes a basic binding, with silent set to true.
  • mkSetExprBinding = binding: action: - makes an expression binding, with lua, silent, and expr set to true.
  • mkSetLuaBinding = binding: action: - makes an expression binding, with lua, and silent set to true.

You can read the source code of some modules to see them in action, but their usage should look something like this:


# plugindefinition.nix
{lib, ...}: with lib; {
  options.vim.plugin = {
    enable = mkEnableOption "Enable plugin";

    # Mappings should always be inside an attrset called mappings
    mappings = {
      # mkMappingOption is a helper function from lib,
      # that takes a description (which will also appear in which-key),
      # and a default mapping (which can be null)
      toggleCurrentLine = mkMappingOption "Toggle current line comment" "gcc";
      toggleCurrentBlock = mkMappingOption "Toggle current block comment" "gbc";

      toggleOpLeaderLine = mkMappingOption "Toggle line comment" "gc";
      toggleOpLeaderBlock = mkMappingOption "Toggle block comment" "gb";

      toggleSelectedLine = mkMappingOption "Toggle selected comment" "gc";
      toggleSelectedBlock = mkMappingOption "Toggle selected block" "gb";
    };

  };
}


# config.nix
{
  pkgs,
  config,
  lib,
  ...
}:
  with lib;
  with builtins; let
    cfg = config.vim.plugin;
    self = import ./plugindefinition.nix {inherit lib;};
    mappingDefinitions = self.options.vim.plugin;

    # addDescriptionsToMappings is a helper function from lib,
    # that merges mapping values and their descriptions
    # into one nice attribute set
    mappings = addDescriptionsToMappings cfg.mappings mappingDefinitions;
in {
  config = mkIf (cfg.enable) {
    # ...
    vim.maps.normal = mkMerge [
      # mkSetBinding is another helper function from lib,
      # that actually adds the mapping with a description.
      (mkSetBinding mappings.findFiles "<cmd> Telescope find_files<CR>")
      (mkSetBinding mappings.liveGrep "<cmd> Telescope live_grep<CR>")
      (mkSetBinding mappings.buffers "<cmd> Telescope buffers<CR>")
      (mkSetBinding mappings.helpTags "<cmd> Telescope help_tags<CR>")
      (mkSetBinding mappings.open "<cmd> Telescope<CR>")

      (mkSetBinding mappings.gitCommits "<cmd> Telescope git_commits<CR>")
      (mkSetBinding mappings.gitBufferCommits "<cmd> Telescope git_bcommits<CR>")
      (mkSetBinding mappings.gitBranches "<cmd> Telescope git_branches<CR>")
      (mkSetBinding mappings.gitStatus "<cmd> Telescope git_status<CR>")
      (mkSetBinding mappings.gitStash "<cmd> Telescope git_stash<CR>")

      (mkIf config.vim.lsp.enable (mkMerge [
        (mkSetBinding mappings.lspDocumentSymbols "<cmd> Telescope lsp_document_symbols<CR>")
        (mkSetBinding mappings.lspWorkspaceSymbols "<cmd> Telescope lsp_workspace_symbols<CR>")

        (mkSetBinding mappings.lspReferences "<cmd> Telescope lsp_references<CR>")
        (mkSetBinding mappings.lspImplementations "<cmd> Telescope lsp_implementations<CR>")
        (mkSetBinding mappings.lspDefinitions "<cmd> Telescope lsp_definitions<CR>")
        (mkSetBinding mappings.lspTypeDefinitions "<cmd> Telescope lsp_type_definitions<CR>")
        (mkSetBinding mappings.diagnostics "<cmd> Telescope diagnostics<CR>")
      ]))

      (
        mkIf config.vim.treesitter.enable
        (mkSetBinding mappings.treesitter "<cmd> Telescope treesitter<CR>")
      )
    ];
    # ...
  };
}

:::{.note}

If you have come across a plugin that has an API that doesn't seem to easily allow custom keybindings, don't be scared to implement a draft PR. We'll help you get it done.

:::