DEV Community

Heiker
Heiker

Posted on • Edited on

Neovim: creating keymaps in lua

If you've read old tutorials about Neovim configuration you've find the way they create keymaps is a little bit different from what people do now. And if you check other people's configuration sometimes you'll find they create their keymaps using lazy.nvim. This is causing confusion among Neovim users.

Here I will explain why each method for creating keymaps exists and tell you when they are useful.

I will not explain in detail every option and possibility of each method. I just want to give you enough information to decide which one you should use in what situation.

nvim_set_keymap

This function was introduced in Neovim v0.5 and it's part of the Nvim API. This means nvim_set_keymap is accesible from lua, vimscript and also remote plugins.

For example, you could use nvim_set_keymap in command mode. This is a valid.

:call nvim_set_keymap('n', '<F2>', ':echo "hello"<cr>', {'noremap': v:true})
Enter fullscreen mode Exit fullscreen mode

If you wanted to do the same in lua it'll be like this

vim.api.nvim_set_keymap('n', '<F2>', ':echo "hello"<cr>', {noremap = true})
Enter fullscreen mode Exit fullscreen mode

And what about remote plugins. What's that? Those are plugins that communicate with external processes. They can be written in any programming language that can send messages through RPC.

In a python remote plugin using pynvim, you could write something like this.

vim.api.set_keymap('n', '<F2>', ':echo "hello"<cr>', {'noremap': True})
Enter fullscreen mode Exit fullscreen mode

Note: I have never used pynvim. I just assume this is a valid method based on the documentation.

When to use it?

The only good reason to use nvim_set_keymap in your own personal configuration is if you can't upgrade to Neovim v0.7 or greater.

vim.keymap.set

This function was introduced in Neovim v0.7 and its part of the lua API. This means its only accesible in lua.

But why?

nvim_set_keymap exists and can be used in lua. What's the purpose of vim.keymap.set?

Short answer: to have better defaults.

Long answer: nvim_set_keymap behaves like the vimscript command :map. Now here's the thing, most people prefer the command :noremap. And there's another issue, you can't assign a lua function in the third argument. You have to use the options in the fourth argument.

If you want to use a lua function in nvim_set_keymap you end up with something like this.

vim.api.nvim_set_keymap(
  'n',
  '<F2>',
  '',
  {
    noremap = true,
    callback = function()
      print('hello')
    end,
  }
)
Enter fullscreen mode Exit fullscreen mode

Almost every configuration I saw online had some sort wrapper around nvim_set_keymap to modify the defaults settings. And binding lua functions is very verbose. Because of details like these vim.keymap.set was created.

What's the difference?

  • vim.keymap.set is not recursive by default. You don't have to specify noremap = true.

  • Did you know noremap stands for "no recursive mapping"? Having that negative prefix is kind of confusing. So in vim.keymap.set if you want a recursive mapping you say remap = true.

  • You can use a lua function in the third argument.

  • Lua functions in combination with the option expr = true handles keycodes automatically.

  • vim.keymap.set can define buffer local keymaps.

So our previous example can be written like this.

vim.keymap.set('n', '<F2>', function()
  print('hello')
end)
Enter fullscreen mode Exit fullscreen mode

When to use it?

Whenever possible. This is the recommended way of creating keymaps in your personal configuration.

lazy.nvim's "keys" property

And then there were three...

lazy.nvim offers a bunch of features. Among those there is one that allows users to create keymaps, that is the keys property in the plugin configuration.

Why?

One of the selling points of lazy.nvim is its ability to load plugins on demand using a (relatively) simple configuration. You can delay loading a plugin until you really, really need it.

Consider this example

{
  'nvim-telescope/telescope.nvim',
  branch = '0.1.x',
  dependencies = {'nvim-lua/plenary.nvim'},
  cmd = 'Telescope',
},
Enter fullscreen mode Exit fullscreen mode

Here we have a configuration for telescope.nvim, a very popular fuzzy finder.

With this in your lazy.nvim setup it will install telescope.nvim and also plenary.nvim. But they will not be loaded until you use the command Telescope. If you never use that command Neovim is not going to execute any files in the source code of telescope.nvim.

Now, cmd is not the only way to load a plugin on demand. You can also tell lazy.nvim to do it when you press a keymap. Like this.

{
  'nvim-telescope/telescope.nvim',
  branch = '0.1.x',
  dependencies = {'nvim-lua/plenary.nvim'},
  keys = {
    {'<F2>', '<cmd>Telescope find_files<cr>'},
  },
},
Enter fullscreen mode Exit fullscreen mode

In this example telescope.nvim will only be loaded after we press the <F2> key.

Consider the side effects

When a plugin is not loaded is like it doesn't even exists.

In the previous example we had a keys property. But notice it didn't have a cmd property. So if the plugin is not loaded and you try to execute the command :Telescope find_files manually in command mode, that would fail. If you try to use the command :help telescope.nvim to read the help page, that would also fail. That is because Neovim doesn't even know where telescope.nvim is installed. It can't give you access to anything.

Is worth mention some plugins do need to execute some commands or functions during Neovim's initialization process. So when you delay loading a plugin it may not work as expected.

When to use it?

I don't think there is ever a situation were you NEED to use this method. Is purely optional. You would use this when you want to delay loading a plugin.

I would use this feature of lazy.nvim in a plugin that I rarely need. Something like neogit for example. I can spend hours coding without using it. In that particular case I think is nice to have the option to load it when I actually call it.

Conclusion

I recommend using vim.keymap.set() whenever possible.

lazy.nvim's keys property does more than just create a keymap. It's one way the user can tell lazy.nvim to delay loading a plugin.


Thank you for your time. If you find this article useful and want to support my efforts, consider leaving a tip in ko-fi.com/vonheikemen.

buy me a coffee

Top comments (3)

Collapse
 
gokayburuc profile image
gokayburuc.dev

i have an issue with regex command as keymaps. i want to create a key to convert comma seperated words into lists. Bu i also want to use this in virtual-line mode in Neovim. My goal is seleting the words and execute this convert operations:

%s/,/\r/g
Enter fullscreen mode Exit fullscreen mode

i tried with vim.set.keymap() but somehow not worked properly:

vim.set.keymap('x','<leader>fc',':%s/,/\r/g')

-- second variant
vim.set.keymap('x','<leader>fc'," '<,'>s/,/\r/g")

Enter fullscreen mode Exit fullscreen mode

but didn't tried nnoremap method yet. So could you help me with this problem if you have a suggestion. I want to solve this with vim.set.keymap() method.

Thnx in advance.

Collapse
 
vonheikemen profile image
Heiker • Edited

Maybe the backslash is not getting throught the command. Use \\r.

vim.keymap.set('x', '<leader>fc', ':%s/,\\r/g')
Enter fullscreen mode Exit fullscreen mode

Another option would be using [[ ]] to create the string, that way you don't have to escape the backlash.

vim.keymap.set('x', '<leader>fc', [[:%s/,\r/g]])
Enter fullscreen mode Exit fullscreen mode
Collapse
 
gokayburuc profile image
gokayburuc.dev

i saw two different format with square brackets and without it. i will try both and give the feedback.

local map = vim.keymap.set
map("x", "<leader>fp", [[:s/,/\r/g]])
Enter fullscreen mode Exit fullscreen mode

this is working for me right now. thanks for your help.