andymass / vim-matchup

vim match-up: even better % :facepunch: navigate and highlight matching words :facepunch: modern matchit and matchparen. Supports both vim and neovim + tree-sitter.
https://www.vim.org/scripts/script.php?script_id=5624
MIT License
1.72k stars 73 forks source link

% jumps backwards #49

Closed mwgkgk closed 5 years ago

mwgkgk commented 5 years ago

From docs:

% - Go forwards to next matching word. If at a close word, cycle back to the corresponding open word.

However for me, with this minimal vimrc,

set nocompatible

call plug#begin('~/.vim/bundle/')

Plug 'andymass/vim-matchup'

call plug#end()

In the following examples, jumps (and motion-s) backwards:

# `%` jumps to if
if test -z ""; then
    echo "cursor_position"
fi

# `%` jumps to while
while true; do
    echo "cursor_position"
done
; `%` jumps to (
(format t "cursor_position")

Effectively acting the same as g%. Been a long time, last tested at commit 62b3db9684ce7e9626b0a631d9e7fbf53eb09780. Seems like I'm doing something wrong. Will appreciate any pointers / possible workarounds. ]% works correctly but further jumps lead to next closing matches, so it can't be substituted for %.

Does work correctly in some cases:

# `%` jumps to )
def cursor_position(name: str) -> str:
    return "Hello, " + name

# Seems that when jumping from outside of the pair, all the examples above also 
# jump to the closing "bracket".

Jumps over else directly to fi, in this example.

# `%` jumps to fi
if test -z ""; then
    echo "cursor_position"
else
    true
fi

Seems like it could be solved with matchup_hotfix. However the issue does not look to be filetype-specific. Will appreciate any pointers.

andymass commented 5 years ago

Try this example with vim --clean or vim -Nu NONE

; `%` jumps to (
   (format t "cursor_position")

Does it work the same way as with match-up? The goal is that match-up will emulate the behavior of vim whenever possible.

I think the match-up documentation is very unclear. This is how it is described in the vim help:

                            *%*
%           Find the next item in this line after or under the
            cursor and jump to its match. |inclusive| motion.

So the precise behavior is: 1) search forward for an active word or symbol and 2) go to its match.

The if/else/fi example should work similarly, except with three instead of two.

Perhaps the behavior of g% should be changed?

mwgkgk commented 5 years ago

Indeed, the naked vim's % does jump backward, I didn't think to check that. Seems like Vim's stance on this is to first, find the next matching item, and then jump to it's match. Which is exactly what you said. That explains different behaviors from outside and inside the brackets.

It does seem like g% acts as a duplicate of %, both outside and inside of brackets. In matchup#motion#find_matching_pair, the a:down switch seems to be used in 3 different places, so the difference is probably something I couldn't easily observe.

Doesn't seem like making % / g% behave consistently from outside and inside the brackets, and keeping consistent with Vim default behavior, can be reconciled. Probably the motions that I really want are indeed ]% and [%, it's just that the repeated keypresses will lead further out, instead of iterating between both. A separate key for iteration (%) solves that.

(Edit: With the exception that ]% and [% skip over the inner groups like if-else-fi).

If you don't mind explaining the difference between current (or planned?) behavior of % and g%, I would appreciate that :) Otherwise, this issue can be closed. Thank you for your time :)

andymass commented 5 years ago

Can you perhaps clarify which exact motion you are looking for, or expect? If I think it is sensible and simple to implement it could maybe be added as another (optional) map.

Your suspicion is totally correct; for matches with only two components (such as parentheses) % and g% behave identically. The difference is only for higher-order matches like if-else-endif, where % goes forwards and g% goes backwards. So my idea is that in cases where % and g% would be identical, make g% do the opposite of what % would do.

cursor  (                )
        ^                ^
        g%               %

        (   cursor       )
        ^                ^
        %                g%

(proposed behavior, not current). However, this might be even more confusing than the current situation, which is consistent although g% is useless for parentheses.

mwgkgk commented 5 years ago

Oh, wow :) Yeah, the proposed reverse g% is indeed confusing, while making sense at the same time.

Thank you for explaining. I now understand the need for g% in higher-order-matches.

What I originally had in mind for %/g% was to have a consistent motion to kill/move to the right side or left side of the nearest pair, and, in normal mode, cycle down/up when on top of a match. This would involve not skipping over inner matches in if-else-fi, being able to consistently jump up/down from inside else-fi to else or fi, treating the match exclusively when used with motions (currently we kill till the right-matching bracket on ]%, but including on [% and %); Also, prioritizing outer matches over nearest-not-entered-pairs (currently % will jump to a nearest bracketed call inside of if-else-fi, rather than if-else-fi itself. ]% and [% however do act on the "outer" matches).

So something like a ]% / [% that does not skip higher-order matches, iterates when on top of a match (like %/g%), is consistent up/down with regards to killing/yanking only text and not the match itself.

I'm not sure if this fits into the vim-matchup strategy, being more of a replacement / combo, as well as having like 20 pages of specification to explain what it does :) Feels more towards the territorial waters of machakann/vim-patternjump or something like jeetsukumaran/vim-indentwise or even fcpg/vim-shore, in terms of implementing a general-feel sort of thing rather than building up on established Vim conventions.

From what I can see it leans outside of sensible/simple-to-implement range, but in any case I appreciate your time spent on clearing up my confusion.

andymass commented 5 years ago

This is implemented as an example "custom" motion. The intent is to provide an API to allow users to define their own motions which use match-up's matching features. The actual matching API isn't really specified yet, but the example motion should still be useable. Since this isn't the mainstream motion, no documentation and not much support is provided. Interested users can see matchup#custom#example_motion which is fairly simple and constructed from basic building-blocks (API subject to change).

To use:

call matchup#custom#define_motion('nox', '%',
      \ 'matchup#custom#example_motion', { 'down': 1 })
call matchup#custom#define_motion('nox', 'g%',
      \ 'matchup#custom#example_motion', { 'down': 0 })
mwgkgk commented 5 years ago

Works and feels great! Some questions:

1 - custom.vim has to be sourced explicitly to work.

Plug 'andymass/vim-matchup' " {{{

source ~/.vim/bundle/vim-matchup/autoload/matchup/custom.vim

call matchup#custom#define_motion('nox', 'q',
      \ 'matchup#custom#example_motion', { 'down': 1 })
call matchup#custom#define_motion('nox', 'gq',
      \ 'matchup#custom#example_motion', { 'down': 0 })
" }}}

A setup like this triggers an error for not having access to motion_sid(), because the source has to go after call plug#end() to work like this. So should the calls to define_motion, because they require custom.vim. Is there a better way for right now?

2 - % and g% when used as motions now consistently grab the match in both directions. What I would prefer as a default is leave the match as is, only selecting the text until the match. I couldn't easily modify the source to achieve this, and v to trigger the inclusiveness of a motion (e.g. dv%) has no effect.

Edit: The problem with source-order is honestly just me using the plugin manager wrong. It does work for most cases to couple the config like that, but I really should try something like minpac which should play better with runtimepath-related things.

andymass commented 5 years ago
  1. Yeah, I think you are using the plugin manager in an unexpected way. The paths are only added to the runtimepath after call plug#end(), so you'd need to use the matchup#custom#define_motion function after the plug#end(), not interspersed with the Plugs. Alternatively, I don't see why something like this wouldn't work:
Plug 'andymass/vim-matchup'
augroup matchup_custom
    au!
    autocmd! VimEnter * 
      \     call matchup#custom#define_motion('nox', '%',
      \       'matchup#custom#example_motion', { 'down': 1 })
      \ \|  call matchup#custom#define_motion('nox', 'g%',
      \       'matchup#custom#example_motion', { 'down': 0 })
augroup END
  1. I think this is possible within the current framework, and has to be handled within matchup#custom#example_motion. Please see the recent commit.