8

matchit's %/g% are simple enough to use, but I'm having trouble understanding the other mappings, which sound potentially useful but don't seem to work in any understandable way.

]%  Go to [count] next unmatched group, as specified by
    |b:match_words|.  Similar to |]}|.

I would expect this maybe to allow me to jump to endif, endfunction, etc. Take the following vimscript example, using the built-in vim ftplugin (b:match_words appears to be set.)

function! SuperTab()
  let l:part = strpart(getline('.'),col('.')-2,1)
  if (l:part=~'^\W\?$')
      return "\<Tab>"
  else
      return "\<C-n>"
  endif
endfunction

[% most of the time jumps to the function!, occasionally (inconsistently depending on cursor position) jumps to the if, and totally ignores (s. ]% jumps to )s and nothing else. va% behaves identically to [% in this example and does not select anything. Is this normal behavior? Is there some example where these mappings make sense?

6
  • 1
    Good question! I thought I'd enable it and come up with an answer but I'm as stumped as you. As far as I can see ]% does almost nothing. I'm going to have to take a look at the code.
    – B Layer
    Commented Aug 20, 2017 at 20:02
  • I think it might just be buggy. As far as I can tell from the documentation and comments in the code, it should work as you suggested. Possibly someone (@BLayer? Or maybe me!) will take a look at the actual code and tell you why/how it goes wrong.
    – Rich
    Commented Sep 15, 2017 at 10:59
  • I'll try to respond this weekend. I did a little poking some time back and am sure it's badly bugged but I didn't debug it in any depth.
    – B Layer
    Commented Sep 15, 2017 at 12:41
  • I just tried neovim's matchit in vim (surprisingly is different than vim's) and it seems to work.. weird github.com/neovim/neovim/blob/master/runtime/plugin/matchit.vim
    – Mass
    Commented Sep 15, 2017 at 13:56
  • 1
    However, neovim's va% ((( ))) does not "expand" after repeated a%. I'm not sure if vim's ever did, but imo it should.just like a(.
    – Mass
    Commented Sep 15, 2017 at 14:17

1 Answer 1

7
+50

I'm going to assume that you use the latest matchit plugin version 1.13.3 which I could find at the moment on Vim's github repository.

[% most of the time jumps to the function!, occasionally (inconsistently depending on cursor position) jumps to the if, and totally ignores (s.

When you hit [%, the s:MultiMatch() function is invoked. At the end of the latter, there's a while loop whose number of iterations is v:count1. This variable stores the count for the last normal command. Here, it should be 1, because you didn't hit any number before [%. But in the middle of the function, some :normal commands are executed to save the screen and cursor position. These normal commands alter the value of v:count1.

You can check this like so:

nno cd :<c-u>call Func()<cr>
fu! Func()
    echom v:count1
endfu

Source this code, and press cd, read your messages (:messages): you should see 1, because you didn't hit any number before cd. Now, source this code:

nno cd :<c-u>call Func()<cr>
fu! Func()
    norm! 3G
    echom v:count1
endfu

And do the same experiment, press cd in a file with more than 3 lines. This time, in the messages, you should see 3, not 1.

Because of this, the searchpair() function inside the loop is invoked too many times, which, I suspect, explains the behavior you described. At least, it does on my machine (Linux, Vim 8.0 patches 1-134).

To fix this, you could delete the line 729:

let level = v:count1

And move it at the beginning of the function (before any other commands, including :normal commands, has the chance to alter v:count1):

fun! s:MultiMatch(spflag, mode)
  if !exists("b:match_words") || b:match_words == ""
    return ""
  end
  let level = v:count1

enter image description here


I just tried neovim's matchit in vim (surprisingly is different than vim's) and it seems to work.. weird

This is probably because of the pull request #5124 that neovim merged a year ago. According to the commit message, its purpose was to prevent matchit to add an undesired entry in the jumplist. To solve this, the commit changed how the screen and cursor positions were saved. It doesn't use :normal commands anymore, but invokes the function winsaveview() and winrestview(). By getting rid of :normal, for a different purpose, they've also fixed the issue you described earlier, because v:count1 isn't modified anymore. Although it could be in the future if some commands are added in the middle of the function.


]% jumps to )s and nothing else.

Towards the end of the s:MultiMatch() function, line 722, the pattern describing the end part of a group of tokens is defined like this:

let closepat = substitute(closepat, ',', '\\|', 'g')

The purpose of the substitution is to replace each comma, which separates 2 consecutive groups of tokens, with a double escaped bar which will be interpreted by Vim's regex engine as an alternation (separation between 2 branches). I think that the substitution should also replace the colons : which separate the tokens inside each group. So, you could rewrite the substitution like this:

let closepat = substitute(closepat, '[,:]', '\\|', 'g')

With this change, ]% should move the cursor on the various tokens described in b:match_words and &l:matchpairs, not just ). At least, it does on my machine.

enter image description here


va% behaves identically to [% in this example and does not select anything.

With the previous changes, in particular the 2nd one, va% should select the text between the 2 surrounding tokens. Although, the plugin seems to treat a middle part of a group of tokens (like else in if|else|endif) as an end part (it's passed to searchpair() through its 3rd argument, not its 2nd). So, what it considers to be the surrounding tokens may sometimes surprise you.


However, neovim's va% ((( ))) does not "expand" after repeated a%. I'm not sure if vim's ever did, but imo it should. just like a(.

Currently, the visual mapping a% is defined on line 71 like this:

vmap a% <Esc>[%v]%

When I add o at the beginning of the {rhs}, to move the cursor to the beginning of the selection (:h v_o), I get the behavior that you want:

vmap a% o<Esc>[%v]%

enter image description here


Here's a version of the matchit plugin, with the 3 small changes described so far.
And here's another one where I tried to also include the PR #5124 from Neovim.

If you want to test the code on your machine, but you don't have the rights to change the default matchit plugin (or you don't want to), you can create the file ~/.vim/plugin/matchit.vim, and write your experimental matchit plugin there.

Since in the runtimepath, ~/.vim comes before $VIMRUNTIME, Vim should source your custom version before the default one. And since, the default plugin has the guard:

if exists("loaded_matchit") || &cp
  finish
endif
let loaded_matchit = 1

... only your version should be fully sourced.

3
  • please open issues for the various problems you have fixed at github.com/chrisbra/matchit Commented Sep 16, 2017 at 19:36
  • @ChristianBrabandt Ok, I've opened 2 issues to discuss the ]% and v_a% mappings which could be changed a little.
    – user852573
    Commented Sep 16, 2017 at 21:26
  • Amazing answer! I am a bit surprised at how apparently few people use these features, but I'm glad we can understand what's going on now.
    – Mass
    Commented Sep 19, 2017 at 2:38

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.