2

I want to define insert mode mappings that will jump outside the scope of any delimiter I'm in (forward/backward). Where "delimiter" is in the broad sense of LaTeX delimiters, so not only {},[],() but also $$, \{ \}, \left( \right) etc. Preferably I'd like to use <tab>/<S-tab> for this.

How can I do that using vimtex?

Example ( marks cursor place, each line corresponding to a use of the intended "forward" keymap)

$\left[ f_{k(▒x)}+1 \right]-2$
$\left[ f_{k(x)▒}+1 \right]-2$
$\left[ f_{k(x)}▒+1 \right]-2$
$\left[ f_{k(x)}+1 \right]▒-2$
$\left[ f_{k(x)}+1 \right]-2$▒

P.S I'm using neovim with LazyVim distro, so can utilize treesitter and lsp if needed, but my guess is that vimtex should suffice.

4
  • 1
    After more digging I found the following issue github.com/lervag/vimtex/issues/2257 where there's a solution using the plugin match-up. I'll give it a try.
    – Ur Ya'ar
    Apr 16 at 10:57
  • 1
    If using match-up will be your solution, consider self-answering for the benefit of future readers. Thank you.
    – Friedrich
    Apr 18 at 6:45
  • @Friedrich haven't tried it yet and the solution that suggested by Lervåg works, so won't try it as of now (it also overlaps some other plugins I'm already using so I don't want to start messing with it atm...)
    – Ur Ya'ar
    Apr 18 at 11:13
  • if <my comment above> else: don't write an answer :-)
    – Friedrich
    Apr 18 at 11:35

2 Answers 2

2

You can use VimTeX for this, but it does require that we use some of the api's that users generally don't know. Here's a Lua implementation that seems to work well:

vim.keymap.set("i", "<tab>", function()
  local delims = vim.fn["vimtex#delim#get_surrounding"]("all")
  local close = delims[2]

  if not vim.tbl_isempty(close) then
    vim.api.nvim_win_set_cursor(0, {
      close.lnum,
      close.cnum + vim.fn.strlen(close.match) - 1
    })
  end
end
{ desc = "jump forward out of delimiter scope" }
)

 vim.keymap.set("i", "<S-tab>", function()
        vim.cmd("norm! h")
        local delims = vim.fn["vimtex#delim#get_surrounding"]("all")
        local open = delims[1]

        if not vim.tbl_isempty(close) then
          vim.api.nvim_win_set_cursor(0, {
            open.lnum,
            open.cnum - 1,
          })
        end
      end, 
{ desc = "jump backward out of delimiter scope" }
) 
6
  • Wow thanks! Your code works perfectly! I tried to do the backward variant, and something is weird - it works unless my cursor has a delimiter to its right (which means that at most one consecutive press of the map works). Any idea why this might happen? I'll put the code in the next comment
    – Ur Ya'ar
    Apr 18 at 7:44
  • vim.keymap.set("i", "<S-tab>", function() local delims = vim.fn["vimtex#delim#get_surrounding"]("all") local open = delims[1] local close = delims[2] if not vim.tbl_isempty(open) then vim.api.nvim_win_set_cursor(0, { open.lnum, open.cnum - 1, }) end end, { desc = "jump backward out of delimiter" })
    – Ur Ya'ar
    Apr 18 at 7:45
  • BTW I realized this use of <TAB> conflicts with expanding Ultisnippets with <TAB>, so I'll have to change one of them...
    – Ur Ya'ar
    Apr 18 at 7:46
  • 1
    Glad to hear it works! One possible reason this does not work when you have a delimiter just to the right is because the actual cursor column is "too high". One possible solution to that is to adjust the cursor position one to the left before running the get_surrounding function. Apr 18 at 10:38
  • Yes, not sure what you mean by "actual cursor column is "too high" ", but I recalled that although my interface shows the cursor as a | which is between two characters, vim actually thinks of the cursor as being "on" a character (if I understand correctly). And yes your suggestion works :) If you don't mind I'm adding the code to your answer, so others won't have trouble with this. Thanks again!
    – Ur Ya'ar
    Apr 18 at 11:03
1

I would do:

inoremap <Tab> <Esc>/[\])}$]<CR>a

This mapping:

  • Switch to Normal mode <Esc>
  • Search for a closing brace (), }, ]) or a $ /[\])}$]
  • Switch back to insert mode a

But it is not exactly the same of going one level up in nesting.

If you activate the builtin matchit plugin (runtime macros/matchit.vim) you could do:

imap <Tab> <Esc>]%a

It uses the ]% which is the equivalent of ]) but for all the 'braces' considered by matchit.

Remark: It doesn't support $ because for $ the open and closing brace characters are the same which make determining the level of nesting much more complex.

6
  • well vimtex has a notion of "what is a delimiter" (see e.g. :h g:vimtex_delim_list) which I think can be used, but I don't really know how to use it...
    – Ur Ya'ar
    Apr 16 at 10:30
  • By default it seems that g:vimtex_delim_list is not set. You can enrich the regular expression proposed to cope with more delimiter or enrich b:match_words if your prefer the second solution (that only works for the pairs that are not the same) :help b:match_words. Apr 16 at 10:41
  • It would be helpful if you could precise what is your problem with the solution (which pairs is not supported the way you would like). We could then propose a way to improve the solution :-) Apr 16 at 10:43
  • LaTeX has many forms of delimiters. For example \left(, \big(', \Big(` (along with countarparts for [, \{, \lange \rangle) etc. I wouldn't want to try to implement this myself. I now see that the plugin github.com/andymass/vim-matchup is supposed to support this, so I'll give it a try. Thanks for your help!
    – Ur Ya'ar
    Apr 16 at 11:37
  • You already have a fairly good support with the builtin matchit but indeed vim-matchup is an improvement. Apr 16 at 11:46

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.