diff --git a/after/ftplugin/ruby_matchup.vim b/after/ftplugin/ruby_matchup.vim new file mode 100644 index 0000000..e9f7956 --- /dev/null +++ b/after/ftplugin/ruby_matchup.vim @@ -0,0 +1,17 @@ +if !exists('b:did_ftplugin') + finish +endif + +let s:save_cpo = &cpo +set cpo&vim + +call matchup#util#patch_match_words('retry', 'retry\|return') + +let b:match_midmap = [ + \ ['rubyRepeat', 'next'], + \ ['rubyDefine', 'return'], + \] +let b:undo_ftplugin .= '| unlet! b:match_midmap' + +let &cpo = s:save_cpo + diff --git a/autoload/matchup/delim.vim b/autoload/matchup/delim.vim index e994d98..a5baf6e 100644 --- a/autoload/matchup/delim.vim +++ b/autoload/matchup/delim.vim @@ -34,6 +34,7 @@ function! matchup#delim#get_current(type, side, ...) " {{{1 endfunction " }}}1 + function! matchup#delim#get_matching(delim, ...) " {{{1 if empty(a:delim) || !has_key(a:delim, 'lnum') | return {} | endif @@ -112,7 +113,7 @@ function! matchup#delim#get_matching(delim, ...) " {{{1 return l:matching_list else " old syntax: open->close, close->open - if !len(l:matching_list) | return {} | endif + if len(l:matching_list) < 2 | return {} | endif return a:delim.side ==# 'open' ? l:matching_list[-1] \ : l:matching_list[0] endif @@ -120,6 +121,7 @@ function! matchup#delim#get_matching(delim, ...) " {{{1 endfunction " }}}1 + function! matchup#delim#get_surrounding(type, ...) " {{{1 call matchup#perf#tic('delim#get_surrounding') @@ -615,6 +617,7 @@ function! s:parser_delim_new(lnum, cnum, opts) " {{{1 \ 'get_matching' : s:basetypes['delim_tex'].get_matching, \ 'regexone' : l:thisre, \ 'regextwo' : l:thisrebr, + \ 'midmap' : get(l:list, 'midmap', {}), \ 'rematch' : l:re, \ 'highlighting' : get(a:opts, 'highlighting', 0), \} @@ -667,6 +670,27 @@ function! s:get_matching_delims(down, stopline) dict " {{{1 let l:skip = 'matchup#delim#skip0()' endif + " disambiguate matches for languages like julia, matlab, ruby, etc + if !empty(self.midmap) + let l:midmap = self.midmap.elements + if self.side ==# 'mid' + let l:idx = filter(range(len(l:midmap)), + \ 'self.match =~# l:midmap[v:val][1]') + else + let l:syn = synIDattr(synID(self.lnum, self.cnum, 0), 'name') + let l:idx = filter(range(len(l:midmap)), + \ 'l:syn =~# l:midmap[v:val][0]') + endif + if len(l:idx) + let l:valid = l:midmap[l:idx[0]] + let l:skip = printf("matchup#delim#skip1(%s, %s)", + \ string(l:midmap[l:idx[0]]), string(l:skip)) + else + let l:skip = printf("matchup#delim#skip2(%s, %s)", + \ string(self.midmap.strike), string(l:skip)) + endif + endif + if matchup#perf#timeout_check() | return [['', 0, 0]] | endif " improves perceptual performance in insert mode @@ -746,7 +770,7 @@ function! s:get_matching_delims(down, stopline) dict " {{{1 if matchup#perf#timeout_check() | break | endif let [l:lnum, l:cnum] = searchpairpos(l:open, l:mids, l:close, - \ l:flags, l:skip, l:lnum_corr, matchup#perf#timeout()) + \ l:flags, l:skip, l:lnum_corr, matchup#perf#timeout()) if l:lnum <= 0 | break | endif if a:down @@ -759,6 +783,10 @@ function! s:get_matching_delims(down, stopline) dict " {{{1 let l:re_anchored = s:anchor_regex(l:re, l:cnum, l:has_zs) let l:matches = matchlist(getline(l:lnum), l:re_anchored) + if empty(l:matches) + " this should never happen + continue + endif let l:match = l:matches[0] call add(l:list, [l:match, l:lnum, l:cnum]) @@ -802,6 +830,26 @@ function! matchup#delim#skip0() execute 'return' (s:invert_skip ? '!(' : '(') b:matchup_delim_skip ')' endfunction +"" +" advanced mid/syntax skip when found in midmap +" {val} is a 2 element array of allowed [syntax, words] +" {def} is the fallback skip expression +function! matchup#delim#skip1(val, def) + if getline('.')[col('.')-1:] =~# '^'.a:val[1] + return eval(a:def) + endif + let l:s = synIDattr(synID(line('.'),col('.'), 0), 'name') + return l:s !~# a:val[0] || eval(a:def) +endfunction + +"" +" advanced mid/syntax skip when word is not in midmap +" {strike} pattern of disallowed mid words +" {def} is the fallback skip expression +function! matchup#delim#skip2(strike, def) + return getline('.')[col('.')-1:] =~# '^'.a:strike || eval(a:def) +endfunction + let s:invert_skip = 0 let s:eff_curpos = [1, 1] diff --git a/autoload/matchup/loader.vim b/autoload/matchup/loader.vim index 0e88730..7fcaf3c 100644 --- a/autoload/matchup/loader.vim +++ b/autoload/matchup/loader.vim @@ -84,7 +84,8 @@ let s:match_word_cache = {} " }}}1 function! s:init_delim_lists(...) abort " {{{1 - let l:lists = { 'delim_tex': { 'regex': [], 'regex_backref': [] } } + let l:lists = { 'delim_tex': { 'regex': [], 'regex_backref': [], + \ 'midmap': {} } } " very tricky examples: " good: let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1' @@ -434,10 +435,19 @@ function! s:init_delim_lists(...) abort " {{{1 \}) endfor + if exists('b:match_midmap') && type(b:match_midmap) == type([]) + let l:elems = deepcopy(b:match_midmap) + let l:lists.delim_tex.midmap = { + \ 'elements': l:elems, + \ 'strike': join(map(range(len(l:elems)), + \ '"\\(".l:elems[v:val][1]."\\)"'), '\|') + \} + endif + " generate combined lists let l:lists.delim_all = {} let l:lists.all = {} - for l:k in ['regex', 'regex_backref'] + for l:k in ['regex', 'regex_backref', 'midmap'] let l:lists.delim_all[l:k] = l:lists.delim_tex[l:k] let l:lists.all[l:k] = l:lists.delim_all[l:k] endfor diff --git a/test/lang/ruby/next.rb b/test/lang/ruby/next.rb new file mode 100644 index 0000000..e08dd68 --- /dev/null +++ b/test/lang/ruby/next.rb @@ -0,0 +1,8 @@ +for i in 0..5 + if i < 2 then + next + else + end + puts "Value of local variable is #{i}" +end + diff --git a/test/vader/ruby.vader b/test/vader/ruby.vader new file mode 100644 index 0000000..fd50fd9 --- /dev/null +++ b/test/vader/ruby.vader @@ -0,0 +1,15 @@ +Given ruby (Ruby for if): + for i in 0..5 + if i < 2 then + next + end + puts "Value of local variable is #{i}" + end + +Do (Motion %): + go + % + +Then (Verify): + AssertEqual 'next', expand('') +