I have a binding that I would like to make it attempt to run the command, and if the command fails to fall back to a different command.

vim.keymap.set("x", "L", '<cmd>STSSelectNextSiblingNode<cr>', opts)
vim.keymap.set("x", "H", '<cmd>STSSelectPrevSiblingNode<cr>', opts)
vim.keymap.set("x", "K", '<cmd>STSSelectParentNode<cr>', opts)
vim.keymap.set("x", "J", '<cmd>STSSelectChildNode<cr>', opts)

Here, these methods will fail if I e.g. trigger them when my cursor is not intersecting an actual tree-sitter node, e.g. on a comment, it will produce this error:

Error executing Lua callback: .../lazy/syntax-tree-surfer/lua/syntax-tree-surfer/init.lua:82: attempt to index local 'parent' (a nil value)
stack traceback:
        .../lazy/syntax-tree-surfer/lua/syntax-tree-surfer/init.lua:82: in function 'get_visual_node'
        .../lazy/syntax-tree-surfer/lua/syntax-tree-surfer/init.lua:122: in function 'surf'
        .../lazy/syntax-tree-surfer/lua/syntax-tree-surfer/init.lua:301: in function <.../lazy/syntax-tree-surfer/lua/syntax-tree-surfer/init.lua:300>
Press ENTER or type command to continue

(so long as opts doesn't contain silent = true)

Now, here's what neovim help has to say about <Cmd>:

                        *<Cmd>* *:map-cmd*
The <Cmd> pseudokey begins a "command mapping", which executes the command
directly (without changing modes).  Where you might use ":...<CR>" in the
{rhs} of a mapping, you can instead use "<Cmd>...<CR>".
    noremap x <Cmd>echo mode(1)<cr>
This is more flexible than `:<C-U>` in visual and operator-pending mode, or
`<C-O>:` in insert-mode, because the commands are executed directly in the
current mode (instead of always going to normal-mode).  Visual-mode is
preserved, so tricks with |gv| are not needed.  Commands can be invoked
directly in cmdline-mode (which would otherwise require timer hacks).

Unlike <expr> mappings, there are no special restrictions on the <Cmd>
command: it is executed as if an (unrestricted) |autocommand| was invoked
or an async event event was processed.

- Because <Cmd> avoids mode-changes (unlike ":") it does not trigger
  |CmdlineEnter| and |CmdlineLeave| events. This helps performance.
- For the same reason, |keycodes| like <C-R><C-W> are interpreted as plain,
  unmapped keys.
- The command is not echo'ed, no need for <silent>.
- The {rhs} is not subject to abbreviations nor to other mappings, even if the
  mapping is recursive.
- In Visual mode  you can use `line('v')` and `col('v')` to get one end of the
  Visual area, the cursor is at the other end.

<Cmd> commands must terminate, that is, they must be followed by <CR> in the
{rhs} of the mapping definition.  |Command-line| mode is never entered.

So I will clearly want to use this with silent = true but I need a way to detect when the error was produced so I can catch that and implement the fallback behavior (in this case move the cursor along the grid like I usually map these keys to).

It will be acceptable if I have to give up using <Cmd> and use a different way to implement this, but it's still not clear how to do that.

  • chatGPT suggested a pcall wrapper helper function so I will try that and post if it works
    – Steven Lu
    Commented Apr 25, 2023 at 3:19
  • Yeah that worked...
    – Steven Lu
    Commented Apr 27, 2023 at 22:23


Your Answer

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

Browse other questions tagged or ask your own question.