I would like to jump through specific syntax elements in a file. For example, quickly navigate to the next string element. In a .vim file strings are defined as vimString syntax elements. For clarification, the syntax element under the cursor can be found with the following command:

synIDattr(synID(line('.'), col('.'), 1), "name")

If I wanted to jump through all the strings or comments in a file, it would be useful to be able to get a list of line,col arrays of all syntax elements of a certain type. Or at least a list of line,col arrays of syntax elements of the file or current line.

  • This is not possible Sep 27 at 6:36
  • To correct @ChristianBrabandt, this is only possible if one iterates through all char positions and parses all synstacks. I'm pretty sure I've even seen a plugin implementing this thing but... it'so-o ugly...
    – Matt
    Sep 27 at 6:55
  • Going character by character through the buffer is fairly efficient (as opposed to random access)
    – Mass
    Sep 28 at 13:44
  • I am pretty sure this is also possible with searchpos('.', ..., skip=) (allowing skip expression is relatively new)
    – Mass
    Sep 28 at 14:08

In recent versions of vim (around patch 8.2.0915), search takes a skip argument which can be used to create a move forward/backward to syntax group motion. Here is a rudimentary implementation:

function! SynMatches(syn) abort
  return synIDattr(synID(line('.'), col('.'), 1), "name") =~# a:syn

function! SynMove(syn, dir) abort
  let l:flags = a:dir ? 'W' : 'Wb'
  if SynMatches(a:syn)
    call search('.', l:flags, 0, 0, 'SynMatches(a:syn)')
  call search('.', l:flags, 0, 0, '!SynMatches(a:syn)')

nnoremap <left> <cmd>call SynMove('vimString', 0)<cr>
nnoremap <right> <cmd>call SynMove('vimString', 1)<cr>

Here is a quick starter implementation which simply moves forward until the syntax is matched. synID is generally quite slow, but fortunately, vim accelerates it in the forward direction using caching.

nnoremap <plug>(forward) <space>

function! s:forward_to(syn) abort
  let l:save_view = winsaveview()
  let [l:ol, l:oc] = [line('.'), col('.')]
  while 1
    execute "normal \<plug>(forward)"
    if line('.') == l:ol && col('.') == l:oc
    let l:syn = synIDattr(synID(line('.'), col('.'), 1), "name")
    if l:syn ==# a:syn
    let [l:ol, l:oc] = [line('.'), col('.')]
  call winrestview(l:save_view)

nnoremap <space> <cmd>call <sid>forward_to('vimString')<cr>

With Neovim, you can use nvim-treesitter for parsing the syntax together with nvim-treesitter-textobjects to achieve this. Treesitter provides a parser rather than using regular expressions to find the objects, and can be used to replace Vim's native highlighting as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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