tpope / vim-repeat

repeat.vim: enable repeating supported plugin maps with "."
http://www.vim.org/scripts/script.php?script_id=2136
2.58k stars 81 forks source link

Working example for neovim (custom text object with repeat) #92

Open JoseConseco opened 2 years ago

JoseConseco commented 2 years ago

I had trouble to make vim-repeat work in neovim with lua, but finally here it is:

--  tpope/vim-repeat - allows . repeat
vim.api.nvim_create_user_command( 'BigInnerWord',
    function() get_big_word('i') end,
    {}
)
vim.fn['repeat#set'](":BigInnerWord"..t'<CR>')  -- the vim-repeat magic
vim.keymap.set('o', 'W', ':BigInnerWord'..t'<CR>') 

t() function is just nvim helper:

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

All you have to do is to write your custom : get_big_word('i') - function - in my case I wanted to write custom big WORD text object. I guess it should work for any custom lua functions, not just o . Hopefully people will find this useful.

dongdongbh commented 1 year ago

Great, how to do this with custom key maping?

wookayin commented 8 months ago

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:

To see a working example in the wild, please see my dotfiles: https://github.com/wookayin/dotfiles/commit/3fe6e68563d13c7f40a5a5870f087c99692e3c13. See how complicated it was to write things in vimscript with dirty string manipulation; in Lua, functional programming makes config more concise!

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.