public
Last active

Smart Selection for VIM

  • Download Gist
gistfile1.vim
VimL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
" About:
"
" How often do you forget which keys you should use to select/modify strings
" in the ' or " or in other pairs? I often use viw/ciw instead of vi'/vi" for
" the first time because it easier for my fingers (but after that I remember
" about vi'). This script allows you always use the same shortcut for all
" cases. When you want to select string in the ' use viv. Do you want to
" select all in the '()'? Use viv. All in the '[]'? Use viv.
"
" How it works:
"
" Script searches first unpair symbol from the left of the current cursor
" position and than runs target command with this symbol. You can use i or a
" modifiers for commands too.
"
" Available commands:
"
" vi* -> viv
" va* -> vav
" ci* -> civ
" ca* -> cav
" di* -> div
" da* -> dav
" ya* -> yiv
" ya* -> yav
" Where * is in <, >, ", ', `, (, ), [, ], {, } or t as tag
"
" NOTE: After v* commands you also can press v again and script extends selection
" to the next pairs.
"
" Author: @gorkunov (alex.g@cloudcastlegroup.com)
"
"
let s:pairs = { '<' : '>', '"': '"', "'": "'", '`': '`', '(': ')', '[': ']', '{': '}' }
function! s:SmartPairs(type, mod, ...)
let all = keys(s:pairs) + values(s:pairs)
if a:0 > 0
let str = getline(a:1)
let cur = len(str)
else
let str = getline('.')
let cur = col('.') - 1
let s:line = line('.')
let s:type = a:type
let s:mod = a:mod
let s:stops = []
endif
while cur >= 0
let cur = cur - 1
let ch = str[cur]
if index(all, ch) < 0
continue
endif
 
if len(s:stops) && get(s:pairs, ch, '') == s:stops[-1].symbol
call remove(s:stops, -1)
" tags workaround
if ch == '<'
" closed tag
if str[cur + 1] == '/'
call add(s:stops, { 'symbol': 'c', 'position': [s:line, cur + 1] })
else
if len(s:stops) && s:stops[-1].symbol == 'c'
call remove(s:stops, -1)
else
call add(s:stops, { 'symbol': 't', 'position': [s:line, cur + 1] })
endif
endif
endif
elseif str[cur - 1] != '\'
call add(s:stops, { 'symbol': ch, 'position': [s:line, cur + 1] })
endif
endwhile
call s:ApplyPairs()
endfunction
 
function! s:ApplyPairs()
let stop = get(s:stops, 0)
 
if type(stop) == type({}) && (has_key(s:pairs, stop.symbol) || stop.symbol == 't')
call remove(s:stops, 0)
execute "normal! " . stop.position[0] . "G" . stop.position[1] . "|"
execute "normal! " . s:type . s:mod . stop.symbol
elseif s:line > 1
let s:line = s:line - 1
call s:SmartPairs(s:type, s:mod, s:line)
endif
endfunction
 
function! s:NextPairs()
call s:ApplyPairs()
endfunction
 
nnoremap <silent> viv :call <SID>SmartPairs('v', 'i')<CR>
nnoremap <silent> vav :call <SID>SmartPairs('v', 'a')<CR>
nnoremap <silent> div :call <SID>SmartPairs('d', 'i')<CR>
nnoremap <silent> dav :call <SID>SmartPairs('d', 'a')<CR>
nnoremap <silent> civ :call <SID>SmartPairs('c', 'i')<CR>a
nnoremap <silent> cav :call <SID>SmartPairs('c', 'a')<CR>a
nnoremap <silent> yiv :call <SID>SmartPairs('y', 'i')<CR>
nnoremap <silent> yav :call <SID>SmartPairs('y', 'a')<CR>
vnoremap <silent> v <ESC>:call <SID>NextPairs()<CR>

e.g.

 _ - cursor position
 _...._ - selection

{ [ test_, 'blabla', cron ] } -> viv -> { [_test, 'blabla', cron_] }
{ [ test, 'bla_bla', cron ] } -> viv -> { [ test, '_blabla_', cron] }   
{ [ test, 'blabla', cron ]_}  -> viv -> {_[ test, 'blabla', cron]_}   

I like this idea a lot, if I understand correctly it selects the nearest matching pair. I use di" or ci" mostly and rarely vi" but it looks like SmartSelect could easily be generalised to take another parameter which would be the first letter in the command, e.g SmartSelect('c', 'i'), of course the name of the function would need to change as well.

@krisleech thanks for suggestions :) I added deleting and changing to the script and also fixed some bugs.

Added extending selection for 'v' commands

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.