|
| 1 | +" Python filetype plugin for matching with % key |
| 2 | +" Language: Python (ft=python) |
| 3 | +" Last Change: 2002 August 15 |
| 4 | +" Maintainer: Benji Fisher, Ph.D. <[email protected]> |
| 5 | +" Version: 0.4, for Vim 6.1 |
| 6 | + |
| 7 | +" allow user to prevent loading and prevent duplicate loading |
| 8 | +if exists("b:loaded_py_match") || &cp |
| 9 | + finish |
| 10 | +endif |
| 11 | +let b:loaded_py_match = 1 |
| 12 | + |
| 13 | +let s:save_cpo = &cpo |
| 14 | +set cpo&vim |
| 15 | + |
| 16 | +" % for if -> elif -> else -> if, g% for else -> elif -> if -> else |
| 17 | +nnoremap <buffer> <silent> % :<C-U>call <SID>PyMatch('%','n') <CR> |
| 18 | +vnoremap <buffer> <silent> % :<C-U>call <SID>PyMatch('%','v') <CR>m'gv`` |
| 19 | +onoremap <buffer> <silent> % v:<C-U>call <SID>PyMatch('%','o') <CR> |
| 20 | +nnoremap <buffer> <silent> g% :<C-U>call <SID>PyMatch('g%','n') <CR> |
| 21 | +vnoremap <buffer> <silent> g% :<C-U>call <SID>PyMatch('g%','v') <CR>m'gv`` |
| 22 | +onoremap <buffer> <silent> g% v:<C-U>call <SID>PyMatch('g%','o') <CR> |
| 23 | +" Move to the start ([%) or end (]%) of the current block. |
| 24 | +nnoremap <buffer> <silent> [% :<C-U>call <SID>PyMatch('[%', 'n') <CR> |
| 25 | +vmap <buffer> [% <Esc>[%m'gv`` |
| 26 | +onoremap <buffer> <silent> [% v:<C-U>call <SID>PyMatch('[%', 'o') <CR> |
| 27 | +nnoremap <buffer> <silent> ]% :<C-U>call <SID>PyMatch(']%', 'n') <CR> |
| 28 | +vmap <buffer> ]% <Esc>]%m'gv`` |
| 29 | +onoremap <buffer> <silent> ]% v:<C-U>call <SID>PyMatch(']%', 'o') <CR> |
| 30 | +
|
| 31 | +" The rest of the file needs to be :sourced only once per session. |
| 32 | +if exists("s:loaded_functions") || &cp |
| 33 | + finish |
| 34 | +endif |
| 35 | +let s:loaded_functions = 1 |
| 36 | + |
| 37 | +" One problem with matching in Python is that so many parts are optional. |
| 38 | +" I deal with this by matching on any known key words at the start of the |
| 39 | +" line, if they have the same indent. |
| 40 | +" |
| 41 | +" Recognize try, except, finally and if, elif, else . |
| 42 | +" keywords that start a block: |
| 43 | +let s:ini = 'try\|if' |
| 44 | +" keywords that continue or end a block: |
| 45 | +let s:tail = 'except\|finally' |
| 46 | +let s:tail = s:tail . '\|elif\|else' |
| 47 | +" all keywords: |
| 48 | +let s:all = s:ini . '\|' . s:tail |
| 49 | + |
| 50 | +function! s:PyMatch(type, mode) range |
| 51 | + " If this function was called from Visual mode, make sure that the cursor |
| 52 | + " is at the correct end of the Visual range: |
| 53 | + if a:mode == "v" |
| 54 | + execute "normal! gv\<Esc>" |
| 55 | + endif |
| 56 | + |
| 57 | + let startline = line(".") " Do not change these: needed for s:CleanUp() |
| 58 | + let startcol = col(".") |
| 59 | + " In case we start on a comment line, ... |
| 60 | + if a:type[0] =~ '[][]' |
| 61 | + let currline = s:NonComment(+1, startline-1) |
| 62 | + else |
| 63 | + let currline = startline |
| 64 | + endif |
| 65 | + let startindent = indent(currline) |
| 66 | + |
| 67 | + " Use default behavior if called as % with a count. |
| 68 | + if a:type == "%" && v:count |
| 69 | + exe "normal! " . v:count . "%" |
| 70 | + return s:CleanUp('', a:mode, startline, startcol) |
| 71 | + endif |
| 72 | + |
| 73 | + " If called as % or g%, decide whether to bail out. |
| 74 | + if a:type == '%' || a:type == 'g%' |
| 75 | + let text = getline(".") |
| 76 | + if strpart(text, 0, col(".")) =~ '\S\s' || text !~ '^\s*\%('. s:all .'\)' |
| 77 | + " cursor not on the first WORD or no keyword so bail out |
| 78 | + normal! % |
| 79 | + return s:CleanUp('', a:mode, startline, startcol) |
| 80 | + endif |
| 81 | + endif |
| 82 | + |
| 83 | + " If called as %, look down for "elif" or "else" or up for "if". |
| 84 | + if a:type == '%' |
| 85 | + let next = s:NonComment(+1, currline) |
| 86 | + while next != 0 && indent(next) > startindent |
| 87 | + let next = s:NonComment(+1, next) |
| 88 | + endwhile |
| 89 | + if indent(next) == startindent && getline(next) =~ '^\s*\%('.s:tail.'\)' |
| 90 | + execute next |
| 91 | + return s:CleanUp('', a:mode, startline, startcol, '$') |
| 92 | + endif |
| 93 | + " If we are still here, then there are no "tail" keywords below us in this |
| 94 | + " block. Search upwards for the start of the block. |
| 95 | + let next = currline |
| 96 | + while next != 0 && indent(next) >= startindent |
| 97 | + if indent(next) == startindent && getline(next) =~ '^\s*\%('.s:ini.'\)' |
| 98 | + execute next |
| 99 | + return s:CleanUp('', a:mode, startline, startcol, '$') |
| 100 | + endif |
| 101 | + let next = s:NonComment(-1, next) |
| 102 | + endwhile |
| 103 | + " If we are still here, there is an error in the file. Let's do nothing. |
| 104 | + endif |
| 105 | + |
| 106 | + " If called as g%, look up for "if" or "elif" or "else" or down for any. |
| 107 | + if a:type == 'g%' |
| 108 | + if getline(currline) =~ '^\s*\(' . s:tail . '\)' |
| 109 | + let next = s:NonComment(-1, currline) |
| 110 | + while next != 0 && indent(next) > startindent |
| 111 | + let next = s:NonComment(-1, next) |
| 112 | + endwhile |
| 113 | + if indent(next) == startindent && getline(next) =~ '^\s*\%('.s:all.'\)' |
| 114 | + execute next |
| 115 | + return s:CleanUp('', a:mode, startline, startcol, '$') |
| 116 | + endif |
| 117 | + else |
| 118 | + " We started at the top of the block. |
| 119 | + " Search down for the end of the block. |
| 120 | + let next = s:NonComment(+1, currline) |
| 121 | + while next != 0 && indent(next) >= startindent |
| 122 | + if indent(next) == startindent |
| 123 | + if getline(next) =~ '^\s*\('.s:tail.'\)' |
| 124 | + let currline = next |
| 125 | + else |
| 126 | + break |
| 127 | + endif |
| 128 | + endif |
| 129 | + let next = s:NonComment(+1, next) |
| 130 | + endwhile |
| 131 | + execute currline |
| 132 | + return s:CleanUp('', a:mode, startline, startcol, '$') |
| 133 | + endif |
| 134 | + endif |
| 135 | + |
| 136 | + " If called as [%, find the start of the current block. |
| 137 | + if a:type == '[%' |
| 138 | + let tailflag = (getline(currline) =~ '^\s*\(' . s:tail . '\)') |
| 139 | + let prevline = s:NonComment(-1, currline) |
| 140 | + while prevline > 0 |
| 141 | + if indent(prevline) < startindent || |
| 142 | + \ tailflag && indent(prevline) == startindent && |
| 143 | + \ getline(prevline) =~ '^\s*\(' . s:ini . '\)' |
| 144 | + " Found the start of block, so go there! |
| 145 | + execute prevline |
| 146 | + return s:CleanUp('', a:mode, startline, startcol, '$') |
| 147 | + endif |
| 148 | + let prevline = s:NonComment(-1, prevline) |
| 149 | + endwhile |
| 150 | + endif |
| 151 | + |
| 152 | + " If called as ]%, find the end of the current block. |
| 153 | + if a:type == ']%' |
| 154 | + let nextline = s:NonComment(+1, currline) |
| 155 | + let startofblock = (indent(nextline) > startindent) |
| 156 | + while nextline > 0 |
| 157 | + if indent(nextline) < startindent || |
| 158 | + \ startofblock && indent(nextline) == startindent && |
| 159 | + \ getline(nextline) !~ '^\s*\(' . s:tail . '\)' |
| 160 | + break |
| 161 | + endif |
| 162 | + let currline = nextline |
| 163 | + let nextline = s:NonComment(+1, currline) |
| 164 | + endwhile |
| 165 | + " nextline is in the next block or after EOF, so go to currline: |
| 166 | + execute currline |
| 167 | + return s:CleanUp('', a:mode, startline, startcol, '$') |
| 168 | + endif |
| 169 | +endfun |
| 170 | + |
| 171 | +" Return the line number of the next non-comment, or 0 if there is none. |
| 172 | +" Start at the current line unless the optional second argument is given. |
| 173 | +" The direction is specified by a:inc (normally +1 or -1 ; |
| 174 | +" no test for a:inc == 0, which may lead to an infinite loop). |
| 175 | +fun! s:NonComment(inc, ...) |
| 176 | + if a:0 > 0 |
| 177 | + let next = a:1 + a:inc |
| 178 | + else |
| 179 | + let next = line(".") + a:inc |
| 180 | + endif |
| 181 | + while 0 < next && next <= line("$") |
| 182 | + if getline(next) !~ '^\s*\(#\|$\)' |
| 183 | + return next |
| 184 | + endif |
| 185 | + let next = next + a:inc |
| 186 | + endwhile |
| 187 | + return 0 " If the while loop finishes, we fell off the end of the file. |
| 188 | +endfun |
| 189 | + |
| 190 | +" Restore options and do some special handling for Operator-pending mode. |
| 191 | +" The optional argument is the tail of the matching group. |
| 192 | +fun! s:CleanUp(options, mode, startline, startcol, ...) |
| 193 | + if strlen(a:options) |
| 194 | + execute "set" a:options |
| 195 | + endif |
| 196 | + " Open folds, if appropriate. |
| 197 | + if a:mode != "o" |
| 198 | + if &foldopen =~ "percent" |
| 199 | + normal! zv |
| 200 | + endif |
| 201 | + " In Operator-pending mode, we want to include the whole match |
| 202 | + " (for example, d%). |
| 203 | + " This is only a problem if we end up moving in the forward direction. |
| 204 | + elseif a:startline < line(".") || |
| 205 | + \ a:startline == line(".") && a:startcol < col(".") |
| 206 | + if a:0 |
| 207 | + " If we want to include the whole line then a:1 should be '$' . |
| 208 | + silent! call search(a:1) |
| 209 | + endif |
| 210 | + endif " a:mode != "o" && etc. |
| 211 | + return 0 |
| 212 | +endfun |
| 213 | + |
| 214 | +let &cpo = s:save_cpo |
| 215 | + |
| 216 | +" vim:sts=2:sw=2:ff=unix: |
0 commit comments