Vim has the concept of actions, which can act on text objects via motions. For example diw will delete (the action) the inner word (the motion). I want to create a function that can be applied/executed with those motions. For example, if my function appends to certain register, I can append whatever I want by doing myFunction + motion for what I want to add.

  • Have you read :h operator, :h 'operatorfunc' and :h :map-operator. I think there is all the information you need there. If not you should try to implement what you want to do and edit your question with actual issues you're encountering.
    – statox
    May 10, 2022 at 13:32
  • Thanks for the pointers. I will read it and will try to update my question, but the main problem seems to be that it is vimscript specific and that you can only have one global function
    – Danielo515
    May 11, 2022 at 7:41

2 Answers 2

  1. Scripting language does not matter. The help for mappings is in :h map.txt. Specific topic is :h :map-operator.

  2. There is g@ normal operator and opfunc global string option.

  3. In order to perform "custom action" we must

    1. Make sure opfunc is set to the name of appropriate VimScript callable object;

    2. Execute g@ in normal mode (that triggers the transition into operator-pending mode);

    3. Make sure Vim is left hanging in operator-pending mode;

  4. After operator-pending mode is done g@ saves the selected range to the "brackets" bookmark and invokes opfunc value passing an extra string parameter.

  5. The quick and dumb test is set opfunc=v:lua.print and then type g@aw (prints "char"), g@as (prints "line"), g@^VG (prints "block") and so on.

  6. As we probably want more than one user-defined operator, we should pack all of that 1-2-3 into a new mapping.

  7. Implement actual opfunc function to do something useful.

BTW. appending text to a register is a builtin. Like "Ayiw etc.

UPD. Some example implementation for 1-2-3:

By :h :map-expression

function! Gat(method) abort
    let &opfunc = a:method
    return "g@"

nnoremap <expr><f12> Gat("v:lua.print")

By :h :map-cmd

nnoremap <f12> <cmd>set opfunc=v:lua.print<CR>g@
  • Seems that some logic to setup a global lua function and also restore the previous opfunc is required. Can you also probide an example of 6? A mapping that performs the correct sequence to call opfunc?
    – Danielo515
    May 11, 2022 at 7:45
  • @Danielo515 You don't need to restore anything as long as you don't use g@ directly. As for the mapping, either :map-expression or :map-cmd wil do. A basic example is under :h :map-operator mentioned above. If you want to explore somewhat advanced use case check this plugin.
    – Matt
    May 11, 2022 at 8:39
  • Does you new answer require the lua function to be globally available?
    – Danielo515
    May 12, 2022 at 17:15
  • @Danielo515 I don't quite understand. Can you elaborate?
    – Matt
    May 12, 2022 at 18:11
  • because you are just referencing a globally available lua function (print) I think your solution requires the function to use to be global
    – Danielo515
    May 13, 2022 at 13:14

As the other answer from Matt has pointed out, it is required to modify the opfunc to point to a global callable object and then call the g@ + motion keymap. This is even suggested by the vim docs, where they create a mapping where both the opfunc is set and then the g@ motion is called on the same command. This seems to be the closer you can get using lua until they provide a better lua integration

function format_range_operator()
  local old_func = vim.go.operatorfunc -- backup previous reference
  -- set a globally callable object/function
  _G.op_func_formatting = function()
    -- the content covered by the motion is between the [ and ] marks, so get those
    local start = vim.api.nvim_buf_get_mark(0, '[')
    local finish = vim.api.nvim_buf_get_mark(0, ']')
    vim.lsp.buf.range_formatting({}, start, finish)
    vim.go.operatorfunc = old_func -- restore previous opfunc
    _G.op_func_formatting = nil -- deletes itself from global namespace
  vim.go.operatorfunc = 'v:lua.op_func_formatting'
  vim.api.nvim_feedkeys('g@', 'n', false)
vim.api.nvim_set_keymap("n", "gm", "<cmd>lua format_range_operator()<CR>", {noremap = true})

Picked from: https://github.com/neovim/nvim-lspconfig/wiki/User-contributed-tips#range-formatting-with-a-motion

  • Don't use feedkeys. This is too dirty and (almost) never needed. Also, creating two functions instead of one and then deleting one of them... This makes no sense.
    – Matt
    May 11, 2022 at 8:48
  • 1
    Can you explain why it is dirty to use feedkeys? what is the alternative? The creation of two functions is required so you call the normal function from a command and that function does all the environment setup before calling the required mappings. I will be happy to see a cleaner implementation if you know one
    – Danielo515
    May 11, 2022 at 9:22
  • I had updated my answer already.
    – Matt
    May 11, 2022 at 9:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.