| " matchit.vim: (global plugin) Extended "%" matching |
| " Last Change: Fri Jan 25 10:00 AM 2008 EST |
| " Maintainer: Benji Fisher PhD <benji@member.AMS.org> |
| " Version: 1.13.2, for Vim 6.3+ |
| " URL: http://www.vim.org/script.php?script_id=39 |
| |
| " Documentation: |
| " The documentation is in a separate file, matchit.txt . |
| |
| " Credits: |
| " Vim editor by Bram Moolenaar (Thanks, Bram!) |
| " Original script and design by Raul Segura Acevedo |
| " Support for comments by Douglas Potts |
| " Support for back references and other improvements by Benji Fisher |
| " Support for many languages by Johannes Zellner |
| " Suggestions for improvement, bug reports, and support for additional |
| " languages by Jordi-Albert Batalla, Neil Bird, Servatius Brandt, Mark |
| " Collett, Stephen Wall, Dany St-Amant, Yuheng Xie, and Johannes Zellner. |
| |
| " Debugging: |
| " If you'd like to try the built-in debugging commands... |
| " :MatchDebug to activate debugging for the current buffer |
| " This saves the values of several key script variables as buffer-local |
| " variables. See the MatchDebug() function, below, for details. |
| |
| " TODO: I should think about multi-line patterns for b:match_words. |
| " This would require an option: how many lines to scan (default 1). |
| " This would be useful for Python, maybe also for *ML. |
| " TODO: Maybe I should add a menu so that people will actually use some of |
| " the features that I have implemented. |
| " TODO: Eliminate the MultiMatch function. Add yet another argument to |
| " Match_wrapper() instead. |
| " TODO: Allow :let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1' |
| " TODO: Make backrefs safer by using '\V' (very no-magic). |
| " TODO: Add a level of indirection, so that custom % scripts can use my |
| " work but extend it. |
| |
| " allow user to prevent loading |
| " and prevent duplicate loading |
| if exists("loaded_matchit") || &cp |
| finish |
| endif |
| let loaded_matchit = 1 |
| let s:last_mps = "" |
| let s:last_words = ":" |
| |
| let s:save_cpo = &cpo |
| set cpo&vim |
| |
| nnoremap <silent> % :<C-U>call <SID>Match_wrapper('',1,'n') <CR> |
| nnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'n') <CR> |
| vnoremap <silent> % :<C-U>call <SID>Match_wrapper('',1,'v') <CR>m'gv`` |
| vnoremap <silent> g% :<C-U>call <SID>Match_wrapper('',0,'v') <CR>m'gv`` |
| onoremap <silent> % v:<C-U>call <SID>Match_wrapper('',1,'o') <CR> |
| onoremap <silent> g% v:<C-U>call <SID>Match_wrapper('',0,'o') <CR> |
| |
| " Analogues of [{ and ]} using matching patterns: |
| nnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "n") <CR> |
| nnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W", "n") <CR> |
| vmap [% <Esc>[%m'gv`` |
| vmap ]% <Esc>]%m'gv`` |
| " vnoremap <silent> [% :<C-U>call <SID>MultiMatch("bW", "v") <CR>m'gv`` |
| " vnoremap <silent> ]% :<C-U>call <SID>MultiMatch("W", "v") <CR>m'gv`` |
| onoremap <silent> [% v:<C-U>call <SID>MultiMatch("bW", "o") <CR> |
| onoremap <silent> ]% v:<C-U>call <SID>MultiMatch("W", "o") <CR> |
| |
| " text object: |
| vmap a% <Esc>[%v]% |
| |
| " Auto-complete mappings: (not yet "ready for prime time") |
| " TODO Read :help write-plugin for the "right" way to let the user |
| " specify a key binding. |
| " let g:match_auto = '<C-]>' |
| " let g:match_autoCR = '<C-CR>' |
| " if exists("g:match_auto") |
| " execute "inoremap " . g:match_auto . ' x<Esc>"=<SID>Autocomplete()<CR>Pls' |
| " endif |
| " if exists("g:match_autoCR") |
| " execute "inoremap " . g:match_autoCR . ' <CR><C-R>=<SID>Autocomplete()<CR>' |
| " endif |
| " if exists("g:match_gthhoh") |
| " execute "inoremap " . g:match_gthhoh . ' <C-O>:call <SID>Gthhoh()<CR>' |
| " endif " gthhoh = "Get the heck out of here!" |
| |
| let s:notslash = '\\\@<!\%(\\\\\)*' |
| |
| function! s:Match_wrapper(word, forward, mode) range |
| " In s:CleanUp(), :execute "set" restore_options . |
| let restore_options = (&ic ? " " : " no") . "ignorecase" |
| if exists("b:match_ignorecase") |
| let &ignorecase = b:match_ignorecase |
| endif |
| let restore_options = " ve=" . &ve . restore_options |
| set ve= |
| " If this function was called from Visual mode, make sure that the cursor |
| " is at the correct end of the Visual range: |
| if a:mode == "v" |
| execute "normal! gv\<Esc>" |
| endif |
| " In s:CleanUp(), we may need to check whether the cursor moved forward. |
| let startline = line(".") |
| let startcol = col(".") |
| " Use default behavior if called with a count. |
| if v:count |
| exe "normal! " . v:count . "%" |
| return s:CleanUp(restore_options, a:mode, startline, startcol) |
| end |
| |
| " First step: if not already done, set the script variables |
| " s:do_BR flag for whether there are backrefs |
| " s:pat parsed version of b:match_words |
| " s:all regexp based on s:pat and the default groups |
| " |
| if !exists("b:match_words") || b:match_words == "" |
| let match_words = "" |
| " Allow b:match_words = "GetVimMatchWords()" . |
| elseif b:match_words =~ ":" |
| let match_words = b:match_words |
| else |
| execute "let match_words =" b:match_words |
| endif |
| " Thanks to Preben "Peppe" Guldberg and Bram Moolenaar for this suggestion! |
| if (match_words != s:last_words) || (&mps != s:last_mps) || |
| \ exists("b:match_debug") |
| let s:last_words = match_words |
| let s:last_mps = &mps |
| " The next several lines were here before |
| " BF started messing with this script. |
| " quote the special chars in 'matchpairs', replace [,:] with \| and then |
| " append the builtin pairs (/*, */, #if, #ifdef, #else, #elif, #endif) |
| " let default = substitute(escape(&mps, '[$^.*~\\/?]'), '[,:]\+', |
| " \ '\\|', 'g').'\|\/\*\|\*\/\|#if\>\|#ifdef\>\|#else\>\|#elif\>\|#endif\>' |
| let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") . |
| \ '\/\*:\*\/,#\s*if\%(def\)\=:#\s*else\>:#\s*elif\>:#\s*endif\>' |
| " s:all = pattern with all the keywords |
| let match_words = match_words . (strlen(match_words) ? "," : "") . default |
| if match_words !~ s:notslash . '\\\d' |
| let s:do_BR = 0 |
| let s:pat = match_words |
| else |
| let s:do_BR = 1 |
| let s:pat = s:ParseWords(match_words) |
| endif |
| let s:all = substitute(s:pat, s:notslash . '\zs[,:]\+', '\\|', 'g') |
| let s:all = '\%(' . s:all . '\)' |
| " let s:all = '\%(' . substitute(s:all, '\\\ze[,:]', '', 'g') . '\)' |
| if exists("b:match_debug") |
| let b:match_pat = s:pat |
| endif |
| endif |
| |
| " Second step: set the following local variables: |
| " matchline = line on which the cursor started |
| " curcol = number of characters before match |
| " prefix = regexp for start of line to start of match |
| " suffix = regexp for end of match to end of line |
| " Require match to end on or after the cursor and prefer it to |
| " start on or before the cursor. |
| let matchline = getline(startline) |
| if a:word != '' |
| " word given |
| if a:word !~ s:all |
| echohl WarningMsg|echo 'Missing rule for word:"'.a:word.'"'|echohl NONE |
| return s:CleanUp(restore_options, a:mode, startline, startcol) |
| endif |
| let matchline = a:word |
| let curcol = 0 |
| let prefix = '^\%(' |
| let suffix = '\)$' |
| " Now the case when "word" is not given |
| else " Find the match that ends on or after the cursor and set curcol. |
| let regexp = s:Wholematch(matchline, s:all, startcol-1) |
| let curcol = match(matchline, regexp) |
| " If there is no match, give up. |
| if curcol == -1 |
| return s:CleanUp(restore_options, a:mode, startline, startcol) |
| endif |
| let endcol = matchend(matchline, regexp) |
| let suf = strlen(matchline) - endcol |
| let prefix = (curcol ? '^.*\%' . (curcol + 1) . 'c\%(' : '^\%(') |
| let suffix = (suf ? '\)\%' . (endcol + 1) . 'c.*$' : '\)$') |
| endif |
| if exists("b:match_debug") |
| let b:match_match = matchstr(matchline, regexp) |
| let b:match_col = curcol+1 |
| endif |
| |
| " Third step: Find the group and single word that match, and the original |
| " (backref) versions of these. Then, resolve the backrefs. |
| " Set the following local variable: |
| " group = colon-separated list of patterns, one of which matches |
| " = ini:mid:fin or ini:fin |
| " |
| " Reconstruct the version with unresolved backrefs. |
| let patBR = substitute(match_words.',', |
| \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g') |
| let patBR = substitute(patBR, s:notslash.'\zs:\{2,}', ':', 'g') |
| " Now, set group and groupBR to the matching group: 'if:endif' or |
| " 'while:endwhile' or whatever. A bit of a kluge: s:Choose() returns |
| " group . "," . groupBR, and we pick it apart. |
| let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR) |
| let i = matchend(group, s:notslash . ",") |
| let groupBR = strpart(group, i) |
| let group = strpart(group, 0, i-1) |
| " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix |
| if s:do_BR " Do the hard part: resolve those backrefs! |
| let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline) |
| endif |
| if exists("b:match_debug") |
| let b:match_wholeBR = groupBR |
| let i = matchend(groupBR, s:notslash . ":") |
| let b:match_iniBR = strpart(groupBR, 0, i-1) |
| endif |
| |
| " Fourth step: Set the arguments for searchpair(). |
| let i = matchend(group, s:notslash . ":") |
| let j = matchend(group, '.*' . s:notslash . ":") |
| let ini = strpart(group, 0, i-1) |
| let mid = substitute(strpart(group, i,j-i-1), s:notslash.'\zs:', '\\|', 'g') |
| let fin = strpart(group, j) |
| "Un-escape the remaining , and : characters. |
| let ini = substitute(ini, s:notslash . '\zs\\\(:\|,\)', '\1', 'g') |
| let mid = substitute(mid, s:notslash . '\zs\\\(:\|,\)', '\1', 'g') |
| let fin = substitute(fin, s:notslash . '\zs\\\(:\|,\)', '\1', 'g') |
| " searchpair() requires that these patterns avoid \(\) groups. |
| let ini = substitute(ini, s:notslash . '\zs\\(', '\\%(', 'g') |
| let mid = substitute(mid, s:notslash . '\zs\\(', '\\%(', 'g') |
| let fin = substitute(fin, s:notslash . '\zs\\(', '\\%(', 'g') |
| " Set mid. This is optimized for readability, not micro-efficiency! |
| if a:forward && matchline =~ prefix . fin . suffix |
| \ || !a:forward && matchline =~ prefix . ini . suffix |
| let mid = "" |
| endif |
| " Set flag. This is optimized for readability, not micro-efficiency! |
| if a:forward && matchline =~ prefix . fin . suffix |
| \ || !a:forward && matchline !~ prefix . ini . suffix |
| let flag = "bW" |
| else |
| let flag = "W" |
| endif |
| " Set skip. |
| if exists("b:match_skip") |
| let skip = b:match_skip |
| elseif exists("b:match_comment") " backwards compatibility and testing! |
| let skip = "r:" . b:match_comment |
| else |
| let skip = 's:comment\|string' |
| endif |
| let skip = s:ParseSkip(skip) |
| if exists("b:match_debug") |
| let b:match_ini = ini |
| let b:match_tail = (strlen(mid) ? mid.'\|' : '') . fin |
| endif |
| |
| " Fifth step: actually start moving the cursor and call searchpair(). |
| " Later, :execute restore_cursor to get to the original screen. |
| let restore_cursor = virtcol(".") . "|" |
| normal! g0 |
| let restore_cursor = line(".") . "G" . virtcol(".") . "|zs" . restore_cursor |
| normal! H |
| let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor |
| execute restore_cursor |
| call cursor(0, curcol + 1) |
| " normal! 0 |
| " if curcol |
| " execute "normal!" . curcol . "l" |
| " endif |
| if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on")) |
| let skip = "0" |
| else |
| execute "if " . skip . "| let skip = '0' | endif" |
| endif |
| let sp_return = searchpair(ini, mid, fin, flag, skip) |
| let final_position = "call cursor(" . line(".") . "," . col(".") . ")" |
| " Restore cursor position and original screen. |
| execute restore_cursor |
| normal! m' |
| if sp_return > 0 |
| execute final_position |
| endif |
| return s:CleanUp(restore_options, a:mode, startline, startcol, mid.'\|'.fin) |
| endfun |
| |
| " Restore options and do some special handling for Operator-pending mode. |
| " The optional argument is the tail of the matching group. |
| fun! s:CleanUp(options, mode, startline, startcol, ...) |
| execute "set" a:options |
| " Open folds, if appropriate. |
| if a:mode != "o" |
| if &foldopen =~ "percent" |
| normal! zv |
| endif |
| " In Operator-pending mode, we want to include the whole match |
| " (for example, d%). |
| " This is only a problem if we end up moving in the forward direction. |
| elseif (a:startline < line(".")) || |
| \ (a:startline == line(".") && a:startcol < col(".")) |
| if a:0 |
| " Check whether the match is a single character. If not, move to the |
| " end of the match. |
| let matchline = getline(".") |
| let currcol = col(".") |
| let regexp = s:Wholematch(matchline, a:1, currcol-1) |
| let endcol = matchend(matchline, regexp) |
| if endcol > currcol " This is NOT off by one! |
| call cursor(0, endcol) |
| endif |
| endif " a:0 |
| endif " a:mode != "o" && etc. |
| return 0 |
| endfun |
| |
| " Example (simplified HTML patterns): if |
| " a:groupBR = '<\(\k\+\)>:</\1>' |
| " a:prefix = '^.\{3}\(' |
| " a:group = '<\(\k\+\)>:</\(\k\+\)>' |
| " a:suffix = '\).\{2}$' |
| " a:matchline = "123<tag>12" or "123</tag>12" |
| " then extract "tag" from a:matchline and return "<tag>:</tag>" . |
| fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline) |
| if a:matchline !~ a:prefix . |
| \ substitute(a:group, s:notslash . '\zs:', '\\|', 'g') . a:suffix |
| return a:group |
| endif |
| let i = matchend(a:groupBR, s:notslash . ':') |
| let ini = strpart(a:groupBR, 0, i-1) |
| let tailBR = strpart(a:groupBR, i) |
| let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix, |
| \ a:groupBR) |
| let i = matchend(word, s:notslash . ":") |
| let wordBR = strpart(word, i) |
| let word = strpart(word, 0, i-1) |
| " Now, a:matchline =~ a:prefix . word . a:suffix |
| if wordBR != ini |
| let table = s:Resolve(ini, wordBR, "table") |
| else |
| " let table = "----------" |
| let table = "" |
| let d = 0 |
| while d < 10 |
| if tailBR =~ s:notslash . '\\' . d |
| " let table[d] = d |
| let table = table . d |
| else |
| let table = table . "-" |
| endif |
| let d = d + 1 |
| endwhile |
| endif |
| let d = 9 |
| while d |
| if table[d] != "-" |
| let backref = substitute(a:matchline, a:prefix.word.a:suffix, |
| \ '\'.table[d], "") |
| " Are there any other characters that should be escaped? |
| let backref = escape(backref, '*,:') |
| execute s:Ref(ini, d, "start", "len") |
| let ini = strpart(ini, 0, start) . backref . strpart(ini, start+len) |
| let tailBR = substitute(tailBR, s:notslash . '\zs\\' . d, |
| \ escape(backref, '\\&'), 'g') |
| endif |
| let d = d-1 |
| endwhile |
| if exists("b:match_debug") |
| if s:do_BR |
| let b:match_table = table |
| let b:match_word = word |
| else |
| let b:match_table = "" |
| let b:match_word = "" |
| endif |
| endif |
| return ini . ":" . tailBR |
| endfun |
| |
| " Input a comma-separated list of groups with backrefs, such as |
| " a:groups = '\(foo\):end\1,\(bar\):end\1' |
| " and return a comma-separated list of groups with backrefs replaced: |
| " return '\(foo\):end\(foo\),\(bar\):end\(bar\)' |
| fun! s:ParseWords(groups) |
| let groups = substitute(a:groups.",", s:notslash.'\zs[,:]*,[,:]*', ',', 'g') |
| let groups = substitute(groups, s:notslash . '\zs:\{2,}', ':', 'g') |
| let parsed = "" |
| while groups =~ '[^,:]' |
| let i = matchend(groups, s:notslash . ':') |
| let j = matchend(groups, s:notslash . ',') |
| let ini = strpart(groups, 0, i-1) |
| let tail = strpart(groups, i, j-i-1) . ":" |
| let groups = strpart(groups, j) |
| let parsed = parsed . ini |
| let i = matchend(tail, s:notslash . ':') |
| while i != -1 |
| " In 'if:else:endif', ini='if' and word='else' and then word='endif'. |
| let word = strpart(tail, 0, i-1) |
| let tail = strpart(tail, i) |
| let i = matchend(tail, s:notslash . ':') |
| let parsed = parsed . ":" . s:Resolve(ini, word, "word") |
| endwhile " Now, tail has been used up. |
| let parsed = parsed . "," |
| endwhile " groups =~ '[^,:]' |
| let parsed = substitute(parsed, ',$', '', '') |
| return parsed |
| endfun |
| |
| " TODO I think this can be simplified and/or made more efficient. |
| " TODO What should I do if a:start is out of range? |
| " Return a regexp that matches all of a:string, such that |
| " matchstr(a:string, regexp) represents the match for a:pat that starts |
| " as close to a:start as possible, before being preferred to after, and |
| " ends after a:start . |
| " Usage: |
| " let regexp = s:Wholematch(getline("."), 'foo\|bar', col(".")-1) |
| " let i = match(getline("."), regexp) |
| " let j = matchend(getline("."), regexp) |
| " let match = matchstr(getline("."), regexp) |
| fun! s:Wholematch(string, pat, start) |
| let group = '\%(' . a:pat . '\)' |
| let prefix = (a:start ? '\(^.*\%<' . (a:start + 2) . 'c\)\zs' : '^') |
| let len = strlen(a:string) |
| let suffix = (a:start+1 < len ? '\(\%>'.(a:start+1).'c.*$\)\@=' : '$') |
| if a:string !~ prefix . group . suffix |
| let prefix = '' |
| endif |
| return prefix . group . suffix |
| endfun |
| |
| " No extra arguments: s:Ref(string, d) will |
| " find the d'th occurrence of '\(' and return it, along with everything up |
| " to and including the matching '\)'. |
| " One argument: s:Ref(string, d, "start") returns the index of the start |
| " of the d'th '\(' and any other argument returns the length of the group. |
| " Two arguments: s:Ref(string, d, "foo", "bar") returns a string to be |
| " executed, having the effect of |
| " :let foo = s:Ref(string, d, "start") |
| " :let bar = s:Ref(string, d, "len") |
| fun! s:Ref(string, d, ...) |
| let len = strlen(a:string) |
| if a:d == 0 |
| let start = 0 |
| else |
| let cnt = a:d |
| let match = a:string |
| while cnt |
| let cnt = cnt - 1 |
| let index = matchend(match, s:notslash . '\\(') |
| if index == -1 |
| return "" |
| endif |
| let match = strpart(match, index) |
| endwhile |
| let start = len - strlen(match) |
| if a:0 == 1 && a:1 == "start" |
| return start - 2 |
| endif |
| let cnt = 1 |
| while cnt |
| let index = matchend(match, s:notslash . '\\(\|\\)') - 1 |
| if index == -2 |
| return "" |
| endif |
| " Increment if an open, decrement if a ')': |
| let cnt = cnt + (match[index]=="(" ? 1 : -1) " ')' |
| " let cnt = stridx('0(', match[index]) + cnt |
| let match = strpart(match, index+1) |
| endwhile |
| let start = start - 2 |
| let len = len - start - strlen(match) |
| endif |
| if a:0 == 1 |
| return len |
| elseif a:0 == 2 |
| return "let " . a:1 . "=" . start . "| let " . a:2 . "=" . len |
| else |
| return strpart(a:string, start, len) |
| endif |
| endfun |
| |
| " Count the number of disjoint copies of pattern in string. |
| " If the pattern is a literal string and contains no '0' or '1' characters |
| " then s:Count(string, pattern, '0', '1') should be faster than |
| " s:Count(string, pattern). |
| fun! s:Count(string, pattern, ...) |
| let pat = escape(a:pattern, '\\') |
| if a:0 > 1 |
| let foo = substitute(a:string, '[^'.a:pattern.']', "a:1", "g") |
| let foo = substitute(a:string, pat, a:2, "g") |
| let foo = substitute(foo, '[^' . a:2 . ']', "", "g") |
| return strlen(foo) |
| endif |
| let result = 0 |
| let foo = a:string |
| let index = matchend(foo, pat) |
| while index != -1 |
| let result = result + 1 |
| let foo = strpart(foo, index) |
| let index = matchend(foo, pat) |
| endwhile |
| return result |
| endfun |
| |
| " s:Resolve('\(a\)\(b\)', '\(c\)\2\1\1\2') should return table.word, where |
| " word = '\(c\)\(b\)\(a\)\3\2' and table = '-32-------'. That is, the first |
| " '\1' in target is replaced by '\(a\)' in word, table[1] = 3, and this |
| " indicates that all other instances of '\1' in target are to be replaced |
| " by '\3'. The hard part is dealing with nesting... |
| " Note that ":" is an illegal character for source and target, |
| " unless it is preceded by "\". |
| fun! s:Resolve(source, target, output) |
| let word = a:target |
| let i = matchend(word, s:notslash . '\\\d') - 1 |
| let table = "----------" |
| while i != -2 " There are back references to be replaced. |
| let d = word[i] |
| let backref = s:Ref(a:source, d) |
| " The idea is to replace '\d' with backref. Before we do this, |
| " replace any \(\) groups in backref with :1, :2, ... if they |
| " correspond to the first, second, ... group already inserted |
| " into backref. Later, replace :1 with \1 and so on. The group |
| " number w+b within backref corresponds to the group number |
| " s within a:source. |
| " w = number of '\(' in word before the current one |
| let w = s:Count( |
| \ substitute(strpart(word, 0, i-1), '\\\\', '', 'g'), '\(', '1') |
| let b = 1 " number of the current '\(' in backref |
| let s = d " number of the current '\(' in a:source |
| while b <= s:Count(substitute(backref, '\\\\', '', 'g'), '\(', '1') |
| \ && s < 10 |
| if table[s] == "-" |
| if w + b < 10 |
| " let table[s] = w + b |
| let table = strpart(table, 0, s) . (w+b) . strpart(table, s+1) |
| endif |
| let b = b + 1 |
| let s = s + 1 |
| else |
| execute s:Ref(backref, b, "start", "len") |
| let ref = strpart(backref, start, len) |
| let backref = strpart(backref, 0, start) . ":". table[s] |
| \ . strpart(backref, start+len) |
| let s = s + s:Count(substitute(ref, '\\\\', '', 'g'), '\(', '1') |
| endif |
| endwhile |
| let word = strpart(word, 0, i-1) . backref . strpart(word, i+1) |
| let i = matchend(word, s:notslash . '\\\d') - 1 |
| endwhile |
| let word = substitute(word, s:notslash . '\zs:', '\\', 'g') |
| if a:output == "table" |
| return table |
| elseif a:output == "word" |
| return word |
| else |
| return table . word |
| endif |
| endfun |
| |
| " Assume a:comma = ",". Then the format for a:patterns and a:1 is |
| " a:patterns = "<pat1>,<pat2>,..." |
| " a:1 = "<alt1>,<alt2>,..." |
| " If <patn> is the first pattern that matches a:string then return <patn> |
| " if no optional arguments are given; return <patn>,<altn> if a:1 is given. |
| fun! s:Choose(patterns, string, comma, branch, prefix, suffix, ...) |
| let tail = (a:patterns =~ a:comma."$" ? a:patterns : a:patterns . a:comma) |
| let i = matchend(tail, s:notslash . a:comma) |
| if a:0 |
| let alttail = (a:1 =~ a:comma."$" ? a:1 : a:1 . a:comma) |
| let j = matchend(alttail, s:notslash . a:comma) |
| endif |
| let current = strpart(tail, 0, i-1) |
| if a:branch == "" |
| let currpat = current |
| else |
| let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g') |
| endif |
| while a:string !~ a:prefix . currpat . a:suffix |
| let tail = strpart(tail, i) |
| let i = matchend(tail, s:notslash . a:comma) |
| if i == -1 |
| return -1 |
| endif |
| let current = strpart(tail, 0, i-1) |
| if a:branch == "" |
| let currpat = current |
| else |
| let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g') |
| endif |
| if a:0 |
| let alttail = strpart(alttail, j) |
| let j = matchend(alttail, s:notslash . a:comma) |
| endif |
| endwhile |
| if a:0 |
| let current = current . a:comma . strpart(alttail, 0, j-1) |
| endif |
| return current |
| endfun |
| |
| " Call this function to turn on debugging information. Every time the main |
| " script is run, buffer variables will be saved. These can be used directly |
| " or viewed using the menu items below. |
| if !exists(":MatchDebug") |
| command! -nargs=0 MatchDebug call s:Match_debug() |
| endif |
| |
| fun! s:Match_debug() |
| let b:match_debug = 1 " Save debugging information. |
| " pat = all of b:match_words with backrefs parsed |
| amenu &Matchit.&pat :echo b:match_pat<CR> |
| " match = bit of text that is recognized as a match |
| amenu &Matchit.&match :echo b:match_match<CR> |
| " curcol = cursor column of the start of the matching text |
| amenu &Matchit.&curcol :echo b:match_col<CR> |
| " wholeBR = matching group, original version |
| amenu &Matchit.wh&oleBR :echo b:match_wholeBR<CR> |
| " iniBR = 'if' piece, original version |
| amenu &Matchit.ini&BR :echo b:match_iniBR<CR> |
| " ini = 'if' piece, with all backrefs resolved from match |
| amenu &Matchit.&ini :echo b:match_ini<CR> |
| " tail = 'else\|endif' piece, with all backrefs resolved from match |
| amenu &Matchit.&tail :echo b:match_tail<CR> |
| " fin = 'endif' piece, with all backrefs resolved from match |
| amenu &Matchit.&word :echo b:match_word<CR> |
| " '\'.d in ini refers to the same thing as '\'.table[d] in word. |
| amenu &Matchit.t&able :echo '0:' . b:match_table . ':9'<CR> |
| endfun |
| |
| " Jump to the nearest unmatched "(" or "if" or "<tag>" if a:spflag == "bW" |
| " or the nearest unmatched "</tag>" or "endif" or ")" if a:spflag == "W". |
| " Return a "mark" for the original position, so that |
| " let m = MultiMatch("bW", "n") ... execute m |
| " will return to the original position. If there is a problem, do not |
| " move the cursor and return "", unless a count is given, in which case |
| " go up or down as many levels as possible and again return "". |
| " TODO This relies on the same patterns as % matching. It might be a good |
| " idea to give it its own matching patterns. |
| fun! s:MultiMatch(spflag, mode) |
| if !exists("b:match_words") || b:match_words == "" |
| return "" |
| end |
| let restore_options = (&ic ? "" : "no") . "ignorecase" |
| if exists("b:match_ignorecase") |
| let &ignorecase = b:match_ignorecase |
| endif |
| let startline = line(".") |
| let startcol = col(".") |
| |
| " First step: if not already done, set the script variables |
| " s:do_BR flag for whether there are backrefs |
| " s:pat parsed version of b:match_words |
| " s:all regexp based on s:pat and the default groups |
| " This part is copied and slightly modified from s:Match_wrapper(). |
| let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") . |
| \ '\/\*:\*\/,#\s*if\%(def\)\=:#\s*else\>:#\s*elif\>:#\s*endif\>' |
| " Allow b:match_words = "GetVimMatchWords()" . |
| if b:match_words =~ ":" |
| let match_words = b:match_words |
| else |
| execute "let match_words =" b:match_words |
| endif |
| if (match_words != s:last_words) || (&mps != s:last_mps) || |
| \ exists("b:match_debug") |
| let s:last_words = match_words |
| let s:last_mps = &mps |
| if match_words !~ s:notslash . '\\\d' |
| let s:do_BR = 0 |
| let s:pat = match_words |
| else |
| let s:do_BR = 1 |
| let s:pat = s:ParseWords(match_words) |
| endif |
| let s:all = '\%(' . substitute(s:pat . (strlen(s:pat)?",":"") . default, |
| \ '[,:]\+','\\|','g') . '\)' |
| if exists("b:match_debug") |
| let b:match_pat = s:pat |
| endif |
| endif |
| |
| " Second step: figure out the patterns for searchpair() |
| " and save the screen, cursor position, and 'ignorecase'. |
| " - TODO: A lot of this is copied from s:Match_wrapper(). |
| " - maybe even more functionality should be split off |
| " - into separate functions! |
| let cdefault = (s:pat =~ '[^,]$' ? "," : "") . default |
| let open = substitute(s:pat . cdefault, |
| \ s:notslash . '\zs:.\{-}' . s:notslash . ',', '\\),\\(', 'g') |
| let open = '\(' . substitute(open, s:notslash . '\zs:.*$', '\\)', '') |
| let close = substitute(s:pat . cdefault, |
| \ s:notslash . '\zs,.\{-}' . s:notslash . ':', '\\),\\(', 'g') |
| let close = substitute(close, '^.\{-}' . s:notslash . ':', '\\(', '') . '\)' |
| if exists("b:match_skip") |
| let skip = b:match_skip |
| elseif exists("b:match_comment") " backwards compatibility and testing! |
| let skip = "r:" . b:match_comment |
| else |
| let skip = 's:comment\|string' |
| endif |
| let skip = s:ParseSkip(skip) |
| " let restore_cursor = line(".") . "G" . virtcol(".") . "|" |
| " normal! H |
| " let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor |
| let restore_cursor = virtcol(".") . "|" |
| normal! g0 |
| let restore_cursor = line(".") . "G" . virtcol(".") . "|zs" . restore_cursor |
| normal! H |
| let restore_cursor = "normal!" . line(".") . "Gzt" . restore_cursor |
| execute restore_cursor |
| |
| " Third step: call searchpair(). |
| " Replace '\('--but not '\\('--with '\%(' and ',' with '\|'. |
| let openpat = substitute(open, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g') |
| let openpat = substitute(openpat, ',', '\\|', 'g') |
| let closepat = substitute(close, '\(\\\@<!\(\\\\\)*\)\@<=\\(', '\\%(', 'g') |
| let closepat = substitute(closepat, ',', '\\|', 'g') |
| if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on")) |
| let skip = '0' |
| else |
| execute "if " . skip . "| let skip = '0' | endif" |
| endif |
| mark ' |
| let level = v:count1 |
| while level |
| if searchpair(openpat, '', closepat, a:spflag, skip) < 1 |
| call s:CleanUp(restore_options, a:mode, startline, startcol) |
| return "" |
| endif |
| let level = level - 1 |
| endwhile |
| |
| " Restore options and return a string to restore the original position. |
| call s:CleanUp(restore_options, a:mode, startline, startcol) |
| return restore_cursor |
| endfun |
| |
| " Search backwards for "if" or "while" or "<tag>" or ... |
| " and return "endif" or "endwhile" or "</tag>" or ... . |
| " For now, this uses b:match_words and the same script variables |
| " as s:Match_wrapper() . Later, it may get its own patterns, |
| " either from a buffer variable or passed as arguments. |
| " fun! s:Autocomplete() |
| " echo "autocomplete not yet implemented :-(" |
| " if !exists("b:match_words") || b:match_words == "" |
| " return "" |
| " end |
| " let startpos = s:MultiMatch("bW") |
| " |
| " if startpos == "" |
| " return "" |
| " endif |
| " " - TODO: figure out whether 'if' or '<tag>' matched, and construct |
| " " - the appropriate closing. |
| " let matchline = getline(".") |
| " let curcol = col(".") - 1 |
| " " - TODO: Change the s:all argument if there is a new set of match pats. |
| " let regexp = s:Wholematch(matchline, s:all, curcol) |
| " let suf = strlen(matchline) - matchend(matchline, regexp) |
| " let prefix = (curcol ? '^.\{' . curcol . '}\%(' : '^\%(') |
| " let suffix = (suf ? '\).\{' . suf . '}$' : '\)$') |
| " " Reconstruct the version with unresolved backrefs. |
| " let patBR = substitute(b:match_words.',', '[,:]*,[,:]*', ',', 'g') |
| " let patBR = substitute(patBR, ':\{2,}', ':', "g") |
| " " Now, set group and groupBR to the matching group: 'if:endif' or |
| " " 'while:endwhile' or whatever. |
| " let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR) |
| " let i = matchend(group, s:notslash . ",") |
| " let groupBR = strpart(group, i) |
| " let group = strpart(group, 0, i-1) |
| " " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix |
| " if s:do_BR |
| " let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline) |
| " endif |
| " " let g:group = group |
| " |
| " " - TODO: Construct the closing from group. |
| " let fake = "end" . expand("<cword>") |
| " execute startpos |
| " return fake |
| " endfun |
| |
| " Close all open structures. "Get the heck out of here!" |
| " fun! s:Gthhoh() |
| " let close = s:Autocomplete() |
| " while strlen(close) |
| " put=close |
| " let close = s:Autocomplete() |
| " endwhile |
| " endfun |
| |
| " Parse special strings as typical skip arguments for searchpair(): |
| " s:foo becomes (current syntax item) =~ foo |
| " S:foo becomes (current syntax item) !~ foo |
| " r:foo becomes (line before cursor) =~ foo |
| " R:foo becomes (line before cursor) !~ foo |
| fun! s:ParseSkip(str) |
| let skip = a:str |
| if skip[1] == ":" |
| if skip[0] == "s" |
| let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" . |
| \ strpart(skip,2) . "'" |
| elseif skip[0] == "S" |
| let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" . |
| \ strpart(skip,2) . "'" |
| elseif skip[0] == "r" |
| let skip = "strpart(getline('.'),0,col('.'))=~'" . strpart(skip,2). "'" |
| elseif skip[0] == "R" |
| let skip = "strpart(getline('.'),0,col('.'))!~'" . strpart(skip,2). "'" |
| endif |
| endif |
| return skip |
| endfun |
| |
| let &cpo = s:save_cpo |
| unlet s:save_cpo |
| |
| " vim:sts=2:sw=2: |