" Where Am I for Python VIM Plugin.
"
" Copyright 2008 Shane Harper (http://shaneharper.net)
"
" This program is free software: you can redistribute it and/or modify
" it under the terms of the GNU General Public License as published by
" the Free Software Foundation, either version 3 of the License, or
" (at your option) any later version.
"
" This program is distributed in the hope that it will be useful,
" but WITHOUT ANY WARRANTY; without even the implied warranty of
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
" GNU General Public License for more details.
"
" You should have received a copy of the GNU General Public License
" along with this program. If not, see .
" THIS FILE WAS GENERATED BY A SCRIPT.
" Time & Date: Mon Jul 14 21:54:26 2008
" Mercurial Revision ID: 77dfff44b224
if exists("*WhereAmIPython")
finish
endif
command -nargs=0 WhereAmIPython call WhereAmIPython()
function WhereAmIPython()
python << END
import vim
#!/usr/bin/env python
# where_am_i.py
# Copyright 2008 Shane Harper (http://shaneharper.net)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import re
import sys
indent_regexp = re.compile('(\s*?\f)*(\s*)')
def indent_column(s):
"""Return the indent column number.
Indent can be made up of spaces, tabs and form-feed characters.
"""
return len(indent_regexp.match(s).group(2).expandtabs())
def logical_lines(file):
"""Generator returning the next logical line as a list of statements.
No characters are filtered out: Line indentation, blank lines and comments
are returned.
"""
logical_line = []
statement = ""
in_string = ""
def in_triple_quote_string(): return len(in_string) == 3
unmatched_brackets = ""
for l in file:
in_comment = False
i = 0
while i < len(l):
if in_string:
if l[i] == '\\':
i += 1
elif l[i:i+len(in_string)] == in_string:
i += len(in_string) - 1
in_string = ""
else:
if l[i:i+3] == '"""' or l[i:i+3] == "'''":
in_string = l[i:i+3]
i += 2
elif l[i] == '"' or l[i] == "'":
in_string = l[i]
elif l[i] == '#':
in_comment = True
break
elif l[i] in '([{':
unmatched_brackets += l[i]
elif l[i] in ')]}':
# XXX What if l[i] doesn't match unmatched_brackets[:-1] ?
unmatched_brackets = unmatched_brackets[:-1]
elif re.match('(:|;)\s*[^#\s]', l[i:]) and not unmatched_brackets:
logical_line += [statement + l[:i+1]]
statement = ""
l = l[i+1:]
i = -1
i += 1
statement += l
if len(unmatched_brackets) == 0 and not in_triple_quote_string() and \
(in_comment or not l.endswith('\\\n')):
yield logical_line + [statement]
logical_line = []
statement = ""
if logical_line or statement: yield logical_line + [statement]
class Statement:
def __init__(self, text, end_indent):
self.text = text
self.end_indent = end_indent
def where_am_i(file, stop_line_number):
"""Return the lines defining the class/es and function/s that a specified
line belongs to as well as the conditions that must be satisfied for
program execution to reach the specified line.
stop_line_number is 1-based. (1, not 0, refers to the first line.)
"""
conditions = []
blank_line_or_only_a_comment_regexp = re.compile('^\s*(#.*)?\n?$')
line_number = 0
for line in logical_lines(file):
logical_line = ''.join(line)
# ---------------------------------------------------------------------
# Remove conditions that no longer apply.
# ---------------------------------------------------------------------
if not blank_line_or_only_a_comment_regexp.match(line[0]):
indent = indent_column(line[0])
while conditions and indent <= conditions[-1].end_indent and (
# Lines indented the same as the current line can still be
# relevant (e.g. the "if" corresponding to an "else").
not (indent == conditions[-1].end_indent and
re.match('\s*(else|elif|except)\W', line[0]) and
not re.match('\s*except\W', conditions[-1].text))):
conditions.pop()
# ---------------------------------------------------------------------
# Stop if finished.
# ---------------------------------------------------------------------
line_number += logical_line.count('\n')
if line_number >= stop_line_number:
break
# ---------------------------------------------------------------------
# Add new condition, if there is one.
# ---------------------------------------------------------------------
if re.match('\s*(if|for|while|else|elif|return|raise|break|continue|'
'try|except)\W', logical_line) or \
(len(line) == 1 and re.match('\s*(class|def)\W', logical_line)):
end_indent = indent_column(logical_line)
for statement in line:
if re.match('\s*(return|raise)\W', statement):
for c in reversed(conditions):
if re.match('\s*def\W', c.text):
end_indent = c.end_indent
break
else: end_indent = -1
if re.match('\s*(break|continue)\W', statement):
for c in reversed(conditions):
end_indent = min(end_indent, c.end_indent)
if re.match('\s*(for|while)\W', c.text):
break
conditions.append(Statement(logical_line, end_indent))
return ''.join([c.text for c in conditions])
def _xmap(fn, iterator):
for i in iterator: yield fn(i)
print where_am_i(file=_xmap(lambda line:line+'\n', vim.current.buffer),
stop_line_number=vim.current.window.cursor[0]),
END
endfunction