That's a neat idea. I just wrote this up, so it hasn't been tested a
lot, but it seems to work. The way I implemented this requires that you
have python support compiled into VIM. It's probably possible to
reimplement this as a pure-VIM script, but the python version is
probably orders of magnitude faster (not to mention being easier to
write). :-)
(I had once implemented the C-style [{ ]} motion keys as vim-scripts
before I realized that VIM already had built-in support for that. They
were quite slow.)
First, make a file called "vimMotion.py":
## begin vimMotion.py ##
import vim
import string
import re
# returns a tuple containing the indentation, and the line number where
we got
# that indentation. That can be different than lineNo if lineNo refers
to an
# empty (ie: only whitespace) line.
def getIndent(lineNo):
for line in vim.current.buffer[lineNo:]:
result = getIndentQuick(line)
if result>=0:
return (result,lineNo)
lineNo = lineNo + 1
return (0,len(vim.current.buffer)-1)
# grabs the initial whitespace and the stuff after it into two groups
indentRE = re.compile( r"([\t ]*)(.*)" )
# returns the indentation for a line, or -1 if it can't be determined
(ie:
# only whitespace)
def getIndentQuick(line):
match = indentRE.match(line)
if len(match.group(2)) == 0:
# if the line is just whitespace, we don't know the indentation
return -1
else:
# make sure we expand tabs like python does...
return len(string.expandtabs(match.group(1)))
# if dir is positive, goes down, negative, goes up. Other than that, the
value
# is irrelevant
def blockMotion( dir ):
lineNo0 = vim.current.window.cursor[0]-1
indent0,lineNo = getIndent(lineNo0)
newLine = lineNo0
if indent0 == 0:
# this is an optimization
if dir>0:
newLine = len(vim.current.buffer)-1
else:
newLine = 0
elif dir>0:
indent = indent0
numLines = len(vim.current.buffer)
while lineNo<numLines and indent >= indent0:
lineNo = lineNo + 1
indent,lineNo = getIndent(lineNo)
newLine = lineNo
else:
indent = indent0
lineNo = lineNo0
while lineNo>0 and indent >= indent0:
lineNo = lineNo - 1
qIndent = getIndentQuick(vim.current.buffer[lineNo])
if qIndent>=0:
indent = qIndent
newLine = lineNo
# move the cursor
vim.current.window.cursor = (newLine+1,0)
## end vimMotion.py ##
Then add the following three lines to your .vimrc:
pyfile vimMotion.py
nmap [{ :python blockMotion(-1)<CR>^
nmap ]} :python blockMotion(1)<CR>^
You should probably just execute these when editting a python file.
Note that python is a little bit funny as to where blocks can end,
because multiple blocks can all end at exactly the same position. That
means that doing ']}' can potentially (and often does) take you out of
several blocks. In a language with braces, ]} will always take you out
of one block at a time.
The other tricky part was figuring out the indentation of a line. The
two things that make this tricky are tabs (but string.expandtabs made
that pretty easy), and the fact that python seems to ignore
empty/whitespace-only lines when figuring out block levels. The "right"
thing to do seems to be to scan forward until we find a line with some
non-whitespace on it.
Let me know if you have any problems.
(note: this was posted to both comp.lang.python and comp.editors, since
people in both those groups might be interested in this)
--
C. Laurence Gonsalves "Any sufficiently advanced
clgo...@kami.com technology is indistinguishable
http://www.cryogen.com/clgonsal/ from magic" -- Arthur C. Clarke
Being a faithful Emacs-ite, I've never used vim. But since you can
script vim with Python, it might be possible to use the tokenizer in
the standard library (module tokenize) to handle this cleanly. Likely
you'd need to extend it a bit to handle the syntax errors that show up
in half-written code gracefully, but taking vim that one step past
syntax-awareness and into structure-awareness would be a very slick
hack.
Neel
## begin vimMotion.py ##
## end vimMotion.py ##
--
--
Being a faithful Emacs-ite, I've never used vim. But since you can