I'm a relatively new neovim user. I use nvim-autopairs to always induce matching (, [, {, ', " character pairs, and automatically placing my cursor inside them. As an example, in insert mode, if I type (, I get ($), where $ represents my active cursor insert mode position.

I want to have a keybinding in insert mode to allow me to <Tab> out of the ending pair, and remain in insert mode, ready to keep going. So I would like to type in insert mode, (x, y<Tab> to obtain (x, y) $, where again $ represents my cursor in insert mode ready to continue typing. Note the requirement of the extra space between ) and $ above.

I found this reddit thread which apparently does this keybinding, with a more advanced implementation in the same thread. I tried to implement the advanced implementation as follows in my init.lua file:

-- tab out of `(['` etc in insert mode
vim.keymap.set('i', '<Tab>', getline('.')[col('.')-1] =~? '[]>)}]' || getline('.')[col('.')-1] =~? '[''"`]' && synIDattr(synID(line("."), col(".")+1, 1), "name") !~? 'string', {silent = true, expr = true, remap =false})

But I get a syntax error when restarting nvim. Could anyone please help me get this working and help ensure that the keymap is ported over to lua correctly? The latter is for my future configuration learning purposes.

  • As shared, the problem is that the mapping is Vimscript but you're using Lua. Either translate to Lua, or use one of the NeoVim functions that can execute VimScript. Note that it's probably also an <expr> mapping and I think it's missing some code.
    – D. Ben Knoble
    Jan 31, 2023 at 15:06
  • (confirmed: you missed the ? '<Right>' : '<Tab>' part of the ternary)
    – D. Ben Knoble
    Jan 31, 2023 at 15:07
  • @D.BenKnoble thanks - I understand the expression is vimscript, hence my request to ask experts to help port it over to pure lua. I'm new to vimscript, so needed help with the porting. Also, could you please write down the full expression you are stating? It would make it easier to see what I missed in copying over the long expression. Thanks again. Jan 31, 2023 at 16:01
  • getline('.')[col('.')-1] =~? '[]>)}]' || getline('.')[col('.')-1] =~? '[''"]' && synIDattr(synID(line("."), col(".")+1, 1), "name") !~? 'string' ? '<Right>' : '<Tab>' (I took the backtick out of the second getline match for formatting; don't forget it if you want it). This is typical of an <expr> mapping: check a condition and return different keys based on the result. It gives a single mapping dynamic behavior.
    – D. Ben Knoble
    Jan 31, 2023 at 16:23

1 Answer 1


First of all: Welcome to your (neo-)vim adventure and welcome to vi.stackexchange!

The problem

The problem here is, that this code:

getline('.')[col('.')-1] =~? '[]>)}]' || getline('.')[col('.')-1] =~? '[''"`]' && synIDattr(synID(line("."), col(".")+1, 1), "name") !~? 'string'

is actually vimscript and not lua! Sadly I'd just rewrite that into lua but that would take probably some time (for me)... take a look into my alternative section below. But if I remember correctly, you can execute vimscript by wrapping the vimscript code within [[ and ]] like this:

vim.keymap.set("n", "a", [[echo 'hello']], {})

this inserts hello into my text, but I can't explain you why, sadly. I rather expected to see it in the messages (see :h messages) but I guess that neovim takes the output of the vimscript part as an input to insert it into the given file.


There's tabout.nvim which does exactly that what you're trying to do :) Maybe you want to give it a try.


The reason why you get 1 if you just wrap the whole vimscript code with [[ and ]] is that it gets evaluated as vimscript code and if you look into the vimscript code, you can see that there are logical and's (&&) and logical or's (||).

I tried to adjust it into a mix of lua and vimscript but I couldn't get the cursor movement working:

vim.keymap.set("i", "a", function()
    local vimscript_code = [[getline('.')[col('.')-1] =~? '[]>)}]' || getline('.')[col('.')-1] =~? '[''"`]' && synIDattr(synID(line("."), col(".")+1, 1), "name") !~? 'string' ]]

    if vim.api.nvim_eval(vimscript_code) == 1 then
        -- add the cursor movement here
end, {})

but I think the easiest solution would be, if you just write that part in vimscript if you really want to have this mapping:

vim.api.nvim_eval([[inoremap <expr> <Tab> getline('.')[col('.')-1] =~? '[]>)}]' || getline('.')[col('.')-1] =~? '[''"`]' && synIDattr(synID(line("."), col(".")+1, 1), "name") !~? 'string' ? '<Right>' : '<Tab>']])

But be aware that this only works, if your cursor is in front of the ending bracket (or ending " and ')!

  • @Tornax07 thanks for the warm welcome and helpful reply. I tried your first suggestion, i.e. wrapping in the vimscript in [[ ]]. It now no longer gives a syntax error, however when I try to tab inside ($), it keeps inserting a 1 symbol. Any ideas what may be happening? Jan 31, 2023 at 1:46
  • @Tornax07 I do know about tabout.nvim, thanks for the reminder though. It is nice if the basic (core) functionality can be achieved with a keymap, rather than an entire package. I'll wait to see if this basic solution can at least be made to work, by users here, before going down that route. Jan 31, 2023 at 1:47
  • @user4687531 I edited my answer
    – TornaxO7
    Jan 31, 2023 at 13:11

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.