Skip to content

Commit 20bb437

Browse files
Benji Fishervim-scripts
Benji Fisher
authored andcommitted
Version 0.5
I added support for for and while loops: % and g% go between the start of the block and either break or continue lines. I reorganized the code and made several minor improvements. For example, [% and ]% now accept a count, `` goes back to where you started, and g% does nothing (insteadof acting like the default %) if the cursor starts on a line that does not begin with a recognized key word.
1 parent 7401b52 commit 20bb437

File tree

1 file changed

+165
-94
lines changed

1 file changed

+165
-94
lines changed

Diff for: ftplugin/python_match.vim

+165-94
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
" Python filetype plugin for matching with % key
22
" Language: Python (ft=python)
3-
" Last Change: 2002 August 15
3+
" Last Change: Thu 02 Oct 2003 12:12:20 PM EDT
44
" Maintainer: Benji Fisher, Ph.D. <[email protected]>
5-
" Version: 0.4, for Vim 6.1
5+
" Version: 0.5, for Vim 6.1
6+
" URL: http://www.vim.org/scripts/script.php?script_id=386
67

78
" allow user to prevent loading and prevent duplicate loading
89
if exists("b:loaded_py_match") || &cp
@@ -22,10 +23,10 @@ vnoremap <buffer> <silent> g% :<C-U>call <SID>PyMatch('g%','v') <CR>m'gv``
2223
onoremap <buffer> <silent> g% v:<C-U>call <SID>PyMatch('g%','o') <CR>
2324
" Move to the start ([%) or end (]%) of the current block.
2425
nnoremap <buffer> <silent> [% :<C-U>call <SID>PyMatch('[%', 'n') <CR>
25-
vmap <buffer> [% <Esc>[%m'gv``
26+
vnoremap <buffer> <silent> [% :<C-U>call <SID>PyMatch('[%','v') <CR>m'gv``
2627
onoremap <buffer> <silent> [% v:<C-U>call <SID>PyMatch('[%', 'o') <CR>
2728
nnoremap <buffer> <silent> ]% :<C-U>call <SID>PyMatch(']%', 'n') <CR>
28-
vmap <buffer> ]% <Esc>]%m'gv``
29+
vnoremap <buffer> <silent> ]% :<C-U>call <SID>PyMatch(']%','v') <CR>m'gv``
2930
onoremap <buffer> <silent> ]% v:<C-U>call <SID>PyMatch(']%', 'o') <CR>
3031
3132
" The rest of the file needs to be :sourced only once per session.
@@ -40,132 +41,157 @@ let s:loaded_functions = 1
4041
"
4142
" Recognize try, except, finally and if, elif, else .
4243
" keywords that start a block:
43-
let s:ini = 'try\|if'
44+
let s:ini1 = 'try\|if'
45+
" These are special, because the matching words may not have the same indent:
46+
let s:ini2 = 'for\|while'
4447
" keywords that continue or end a block:
45-
let s:tail = 'except\|finally'
46-
let s:tail = s:tail . '\|elif\|else'
48+
let s:tail1 = 'except\|finally'
49+
let s:tail1 = s:tail1 . '\|elif\|else'
50+
" These go with s:ini2 :
51+
let s:tail2 = 'break\|continue'
4752
" all keywords:
48-
let s:all = s:ini . '\|' . s:tail
53+
let s:all1 = s:ini1 . '\|' . s:tail1
54+
let s:all2 = s:ini2 . '\|' . s:tail2
4955

50-
function! s:PyMatch(type, mode) range
56+
fun! s:PyMatch(type, mode) range
57+
" I have to do this before the :normal gv...
58+
let cnt = v:count1
5159
" If this function was called from Visual mode, make sure that the cursor
5260
" is at the correct end of the Visual range:
5361
if a:mode == "v"
5462
execute "normal! gv\<Esc>"
5563
endif
64+
" Use default behavior if called as % with a count.
65+
if a:type == "%" && v:count
66+
exe "normal! " . v:count . "%"
67+
return s:CleanUp('', a:mode)
68+
endif
5669

57-
let startline = line(".") " Do not change these: needed for s:CleanUp()
58-
let startcol = col(".")
70+
" Do not change these: needed for s:CleanUp()
71+
let s:startline = line(".")
72+
let s:startcol = col(".")
5973
" In case we start on a comment line, ...
60-
if a:type[0] =~ '[][]'
61-
let currline = s:NonComment(+1, startline-1)
74+
if a:type == '[%' || a:type == ']%'
75+
let currline = s:NonComment(+1, s:startline-1)
6276
else
63-
let currline = startline
77+
let currline = s:startline
6478
endif
6579
let startindent = indent(currline)
80+
" Set a mark before jumping.
81+
normal! m'
6682

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)
83+
" If called as [%, find the start of the current block.
84+
" If called as ]%, find the end of the current block.
85+
if a:type == '[%' || a:type == ']%'
86+
while cnt > 0
87+
let currline = (a:type == '[%') ?
88+
\ s:StartOfBlock(currline) : s:EndOfBlock(currline)
89+
let cnt = cnt - 1
90+
endwhile
91+
execute currline
92+
return s:CleanUp('', a:mode, '$')
7193
endif
7294

7395
" If called as % or g%, decide whether to bail out.
7496
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)
97+
let text = getline(currline)
98+
if strpart(text, 0, col(".")) =~ '\S\s'
99+
\ || text !~ '^\s*\%(' . s:all1 . '\|' . s:all2 . '\)'
100+
" cursor not on the first WORD or no keyword so bail out
101+
if a:type == '%'
102+
normal! %
103+
endif
104+
return s:CleanUp('', a:mode)
105+
endif
106+
" If it matches s:all2, we need to find the "for" or "while".
107+
if text =~ '^\s*\%(' . s:all2 . '\)'
108+
let topline = currline
109+
while getline(topline) !~ '^\s*\%(' . s:ini2 . '\)'
110+
let temp = s:StartOfBlock(topline)
111+
if temp == topline " there is no enclosing block.
112+
return s:CleanUp('', a:mode)
113+
endif
114+
let topline = temp
115+
endwhile
116+
let topindent = indent(topline)
80117
endif
81118
endif
82119

83120
" If called as %, look down for "elif" or "else" or up for "if".
84-
if a:type == '%'
121+
if a:type == '%' && text =~ '^\s*\%('. s:all1 .'\)'
85122
let next = s:NonComment(+1, currline)
86-
while next != 0 && indent(next) > startindent
123+
while next > 0 && indent(next) > startindent
87124
let next = s:NonComment(+1, next)
88125
endwhile
89-
if indent(next) == startindent && getline(next) =~ '^\s*\%('.s:tail.'\)'
90-
execute next
91-
return s:CleanUp('', a:mode, startline, startcol, '$')
126+
if next == 0 || indent(next) < startindent
127+
\ || getline(next) !~ '^\s*\%(' . s:tail1 . '\)'
128+
" There are no "tail1" keywords below startline in this block. Go to
129+
" the start of the block.
130+
let next = (text =~ '^\s*\%(' . s:ini1 . '\)') ?
131+
\ currline : s:StartOfBlock(currline)
92132
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, '$')
133+
execute next
134+
return s:CleanUp('', a:mode, '$')
135+
endif
136+
137+
" If called as %, look down for "break" or "continue" or up for
138+
" "for" or "while".
139+
if a:type == '%' && text =~ '^\s*\%(' . s:all2 . '\)'
140+
let next = s:NonComment(+1, currline)
141+
while next > 0 && indent(next) > topindent
142+
\ && getline(next) !~ '^\s*\%(' . s:tail2 . '\)'
143+
" Skip over nested "for" or "while" blocks:
144+
if getline(next) =~ '^\s*\%(' . s:ini2 . '\)'
145+
let next = s:EndOfBlock(next)
100146
endif
101-
let next = s:NonComment(-1, next)
147+
let next = s:NonComment(+1, next)
102148
endwhile
103-
" If we are still here, there is an error in the file. Let's do nothing.
149+
if indent(next) > topindent && getline(next) =~ '^\s*\%(' . s:tail2 . '\)'
150+
execute next
151+
else " There are no "tail2" keywords below v:startline, so go to topline.
152+
execute topline
153+
endif
154+
return s:CleanUp('', a:mode, '$')
104155
endif
105156

106157
" 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
158+
if a:type == 'g%' && text =~ '^\s*\%('. s:all1 .'\)'
159+
" If we started at the top of the block, go down to the end of the block.
160+
if text =~ '^\s*\(' . s:ini1 . '\)'
161+
let next = s:EndOfBlock(currline)
117162
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, '$')
163+
let next = s:NonComment(-1, currline)
133164
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)
165+
while next > 0 && indent(next) > startindent
166+
let next = s:NonComment(-1, next)
149167
endwhile
168+
if indent(next) == startindent && getline(next) =~ '^\s*\%('.s:all1.'\)'
169+
execute next
170+
endif
171+
return s:CleanUp('', a:mode, '$')
150172
endif
151173

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
174+
" If called as g%, look up for "for" or "while" or down for any.
175+
if a:type == 'g%' && text =~ '^\s*\%(' . s:all2 . '\)'
176+
" Start at topline . If we started on a "for" or "while" then topline is
177+
" the same as currline, and we want the last "break" or "continue" in the
178+
" block. Otherwise, we want the last one before currline.
179+
let botline = (topline == currline) ? line("$") + 1 : currline
180+
let currline = topline
181+
let next = s:NonComment(+1, currline)
182+
while next < botline && indent(next) > topindent
183+
if getline(next) =~ '^\s*\%(' . s:tail2 . '\)'
184+
let currline = next
185+
elseif getline(next) =~ '^\s*\%(' . s:ini2 . '\)'
186+
" Skip over nested "for" or "while" blocks:
187+
let next = s:EndOfBlock(next)
161188
endif
162-
let currline = nextline
163-
let nextline = s:NonComment(+1, currline)
189+
let next = s:NonComment(+1, next)
164190
endwhile
165-
" nextline is in the next block or after EOF, so go to currline:
166191
execute currline
167-
return s:CleanUp('', a:mode, startline, startcol, '$')
192+
return s:CleanUp('', a:mode, '$')
168193
endif
194+
169195
endfun
170196

171197
" Return the line number of the next non-comment, or 0 if there is none.
@@ -187,9 +213,54 @@ fun! s:NonComment(inc, ...)
187213
return 0 " If the while loop finishes, we fell off the end of the file.
188214
endfun
189215

216+
" Return the line number of the top of the block containing Line a:start .
217+
" For most lines, this is the first previous line with smaller indent.
218+
" For lines starting with "except", "finally", "elif", or "else", this is the
219+
" first previous line starting with "try" or "if".
220+
fun! s:StartOfBlock(start)
221+
let startindent = indent(a:start)
222+
let tailflag = (getline(a:start) =~ '^\s*\(' . s:tail1 . '\)')
223+
let prevline = s:NonComment(-1, a:start)
224+
while prevline > 0
225+
if indent(prevline) < startindent ||
226+
\ tailflag && indent(prevline) == startindent &&
227+
\ getline(prevline) =~ '^\s*\(' . s:ini1 . '\)'
228+
" Found the start of block!
229+
return prevline
230+
endif
231+
let prevline = s:NonComment(-1, prevline)
232+
endwhile
233+
" If the loop completes, then s:NonComment() returned 0, so we are at the
234+
" top.
235+
return a:start
236+
endfun
237+
238+
" Return the line number of the end of the block containing Line a:start .
239+
" For most lines, this is the line before the next line with smaller indent.
240+
" For lines that begin a block, go to the end of that block, with special
241+
" treatment for "if" and "try" blocks.
242+
fun! s:EndOfBlock(start)
243+
let startindent = indent(a:start)
244+
let currline = a:start
245+
let nextline = s:NonComment(+1, currline)
246+
let startofblock = (indent(nextline) > startindent) ||
247+
\ getline(currline) =~ '^\s*\(' . s:ini1 . '\)'
248+
while nextline > 0
249+
if indent(nextline) < startindent ||
250+
\ startofblock && indent(nextline) == startindent &&
251+
\ getline(nextline) !~ '^\s*\(' . s:tail1 . '\)'
252+
break
253+
endif
254+
let currline = nextline
255+
let nextline = s:NonComment(+1, currline)
256+
endwhile
257+
" nextline is in the next block or after EOF, so return currline:
258+
return currline
259+
endfun
260+
190261
" Restore options and do some special handling for Operator-pending mode.
191262
" The optional argument is the tail of the matching group.
192-
fun! s:CleanUp(options, mode, startline, startcol, ...)
263+
fun! s:CleanUp(options, mode, ...)
193264
if strlen(a:options)
194265
execute "set" a:options
195266
endif
@@ -201,13 +272,13 @@ fun! s:CleanUp(options, mode, startline, startcol, ...)
201272
" In Operator-pending mode, we want to include the whole match
202273
" (for example, d%).
203274
" 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(".")
275+
elseif s:startline < line(".") ||
276+
\ s:startline == line(".") && s:startcol < col(".")
206277
if a:0
207278
" If we want to include the whole line then a:1 should be '$' .
208279
silent! call search(a:1)
209280
endif
210-
endif " a:mode != "o" && etc.
281+
endif " a:mode != "o"
211282
return 0
212283
endfun
213284

0 commit comments

Comments
 (0)