Repeat with "." a motion plugin command mapped in lua throught keyset?

113 Views Asked by At

I use coc.nvim with the proposed default configuration that set (in a lua file) :

-- Use `[g` and `]g` to navigate diagnostics
-- Use `:CocDiagnostics` to get all diagnostics of current buffer in location list
keyset("n", "[g", "<Plug>(coc-diagnostic-prev)", {silent = true})
keyset("n", "]g", "<Plug>(coc-diagnostic-next)", {silent = true})

I would like to know is there is a way to make these motion repeteable with ".".

I see that the tpope's plugin vim-repeat seems to allow it. Indeed, it says that:

Adding support to a plugin is generally as simple as the following command at the end of your map functions.

silent! call repeat#set("\<Plug>MyWonderfulMap", v:count)

But I have no idea how to actually put this line of code with the "keyset" command.

Do you have an idea of how to do it?

Thanks for reading (and helping) me!

1

There are 1 best solutions below

2
Jongwook Choi On

Cross-posted with https://github.com/tpope/vim-repeat/issues/92#issuecomment-1826910664

In your example:

vim.keymap.set("n", "[g", "<Plug>(coc-diagnostic-prev)", {silent = true})
vim.keymap.set("n", "]g", "<Plug>(coc-diagnostic-next)", {silent = true})

You can do something like:

local t = function(keycode) return vim.api.nvim_replace_termcodes(keycode, true, true, true) end

vim.keymap.set("n", "[g", function()
    vim.api.nvim_feedkeys(t '<Plug>(coc-diagnostic-prev)', "n", false)
    vim.fn["repeat#set"](t '<Plug>(coc-diagnostic-prev)')
end, {silent = true})

or

local t = function(keycode) return vim.api.nvim_replace_termcodes(keycode, true, true, true) end

vim.keymap.set("n", "[g", function()
    vim.fn["repeat#set"](t '<Plug>(coc-diagnostic-prev)')
    return '<Plug>(coc-diagnostic-prev)'
end, { silent = true, expr = true, remap = true })

and similar for ]g. You may want to make some helper function to reduce repeating similar lines.

The use of feedkeys is quite hacky and brittle here (although you can use expr to avoid feedkeys), so below I would suggest some ways to automatically create an internal wrapper mapping that makes things repeatable.

A general recipe & usage for neovim (Lua)

You can use the following wrapper to conveniently wrap a rhs to be passed to vim.keymap.set:

---Register a global internal keymap that wraps `rhs` to be repeatable.
---@param mode string|table keymap mode, see vim.keymap.set()
---@param lhs string lhs of the internal keymap to be created, should be in the form `<Plug>(...)`
---@param rhs string|function rhs of the keymap, see vim.keymap.set()
---@return string The name of a registered internal `<Plug>(name)` keymap. Make sure you use { remap = true }.
local make_repeatable_keymap = function (mode, lhs, rhs)
  vim.validate {
    mode = { mode, { 'string', 'table' } },
    rhs = { rhs, { 'string', 'function' },
    lhs = { name = 'string' } }
  }
  if not vim.startswith(lhs, "<Plug>") then
    error("`lhs` should start with `<Plug>`, given: " .. lhs)
  end
  vim.keymap.set(mode, lhs, function()
    rhs()
    vim.fn['repeat#set'](vim.api.nvim_replace_termcodes(lhs, true, true, true))
  end)
  return lhs
end

(Note: this does not implement opts for such as buffer, nowait, silent, etc., but it should be straightforward to extend)

Example: If you want to make a keymap <leader>tc mapped to a lua function (or any keymap sequence string) repeatable:

vim.keymap.set('n', '<leader>tc', make_repeatable_keymap('n', '<Plug>(ToggleComment)', function()
   -- the actual body (rhs) goes here: some complex logic that you want to repeat
end, { remap = true })

This will:

  • Create an internal keymap <Plug>(ToggleComment) that does the job to make the internal keymap repeatable.
  • Map <leader>tc to <Plug>(ToggleComment), which will execute the desired body (rhs).
  • Note: Make sure you use { remap = true }.

More detailed explanation and breakdown

So in order to make vim-repeat work, in general one should have a keymap (or a command) like:

  1. BEGIN: A keymap (or command) starts, say <Plug>(MyKeymap).
  2. (body -- what is going to be repeated; this is the body (rhs) of the keymap)
  3. END: call repeat#set("<Plug>(MyKeymap)") to mark the keymap sequence to repeat by ..
    • Note this is just implemented by feedkeys under the hood -- so need to use nvim_replace_termcodes; also an anonymous function cannot be used.

For keymap, it is very recommended (although not mandatory) to use <Plug> to create an internal mapping, because this will make key repeat much, much easier to write and understand.