AndrewRadev/switch.vim

Find match on cursor line

felixSchl opened this issue · 7 comments

This is strictly speaking not an issue but just and idea. Currently the cursor needs to be on the word, but often times there is only one relevant word in a line that needs to be toggled, at least in my use cases. It would be nice if it worked like vim's <C-A> and <C-X> (add and subtract) feature and just find the first relevant match on the line and operates on that.

If I get time and there is general interest and no one wants to work on it, I will do it.

Seems like it would already behave like that if it was not for line 79 in mapping.vim, so I presume this behavior is unlikely to be changed. Maybe we could add an option to ignore that restriction or work out a more sophisticated solution?

Consider:

[_]VerboseTypeName verboseVariableName = new VerboseTypeName(true);

[_] being the cursor, it would be much faster to just use :Switch than to move to the word first and then toggle it.

Ideas to handle multiple matches on one line would be:

  • Some mechanism to pick one and toggle
  • Do nothing
  • Move to first match, provide key-mapping to jump to next and toggle
  • Change all (cannot see how that's useful)

The problem is not just multiple matches on the same line, it's overlapping matches. Consider:

if foo(bar: true)
  baz
end

If the cursor is on true, then the plugin should flip it to false, but it's still within the limits of the if pattern as well. If the cursor moves slightly away from the true, the bar: true match or the if-clause match would take effect.

Picking one would be an option, maybe with an inputlist() prompt, so you would get something like:

Switch what?

1. true
2. bar: true
3. if foo(bar: true)

Personally, I'd find this annoying. One of Vim's biggest benefits to me is not needing feedback to perform predictable results. Being asked for what exactly to choose (and it couldn't be an unconscious choice anyway, since it depends on the actual patterns on the line, wouldn't be a repeatable number) would make me unhappy. Still, I can imagine this may be useful depending on other people's workflow, so I'm not discarding this as an idea. It could be activatable with an option.

Moving to the closest match could work only if there is no other larger match that obscures this one. This may still be pretty reasonable for most cases, especially if you prune the definitions, only putting those you care for. What I'm worried about is performing a switch and getting unexpected results because you haven't realized there's another matching definition on the line. Sure, there's always undo, but it's a cache miss, it would actually take you more time and effort to hit this bump, undo, and then do the jumping necessary to get there and do the right thing.

This is why I opted to require you to be on the spot, because it's predictable and once you learn which patterns have what range, you can get consistent results. Even so, I understand it could be pretty annoying, especially since you're likely using switch as a micro-optimization to minor changes.

I'm open to figuring something out here, possibly controlled by options. The first step in any of the solutions we're discussing, is to change the algorithm so it finds all viable matches, even those not under the cursor. I guess removing the condition you mentioned could fix this, though I'd keep the same behaviour to begin with (we could just move the cursor check outside of that function, possibly add a match.IsAroundCursor() method or something) and then think about how to provide a different one. As I currently see it, would be something like:

  • By default, require precise match under cursor, choose "smallest" one
  • Maybe, if there's no match under cursor, it's safe to do the closest one
  • If particular option is turned on, provide a choice of switches to perform on the entire line, ordering them on how close to the cursor and "small" they are

I'm going to try to devote some time on it this weekend, though if you do feel like working on it and have a general idea of how to tackle it, I wouldn't really mind :).

Maybe a bad idea, well ... wouldn't it be possible to think it 'the other way around'? I mean, the user could write regexps that take the whole line into account in order to change something on it: something - false - something else becomes something - true - something else and so on. Such rules would duplicate the regular ones (false -> true) and would be appended to the list, in what would become a sort of priority rule list: if there is no false/true in the whole line, the next rule gets evaluated.

As a quick hack I added two options that allow overriding default algorithm by first matching word on cursor as it is, if no match found - try matching from cursor to the right (like / does when searching) and if that fails, try to match form beginning of line. Here is the diff master...randomize:master

Anyway agree that this requires more thinking to avoid weird behaviour in complex cases with overlaps. Will give a try to this new alg is my daily work setup - time will tell

And big thanks to Andrew for putting this together, plugin is a real time saver

mg979 commented

I've been trying this out:

nnoremap <silent> <Plug>(SwitchInLine) :<C-u>call SwitchLine(v:count1)<cr>
nmap g<C-a> <Plug>(SwitchInLine)

fun! SwitchLine(cnt)
    let tick = b:changedtick
    let start = getcurpos()
    for n in range(a:cnt)
        Switch
    endfor
    if b:changedtick != tick
        return
    endif
    while v:true
        let pos = getcurpos()
        normal! w
        if pos[1] != getcurpos()[1] || pos == getcurpos()
            break
        endif
        for n in range(a:cnt)
            Switch
        endfor
        if b:changedtick != tick
            return
        endif
    endwhile
    call setpos('.', start)
endfun

It does normal! w and :Switch on the line until the buffer changes (meaning it worked), or stopping at line change or end of file.

@mg979 That does seem like it could work. What do you think about adding it to the wiki as something that could be built on top of switch? https://github.com/AndrewRadev/switch.vim/wiki