1
0
mirror of https://github.com/gryf/pythonhelper.git synced 2025-12-19 12:28:16 +01:00

Merge pull request #2 from cheater/master

Some updates to pythonhelper

1. revamped all the comments
2. made it possible to get just the tag name or just the tag type
3. fixed a lot of stupid code (but not all)
4. made it stop clobbering updatetime (still optional, though)
This commit is contained in:
Oluf Lorenzen
2012-02-13 05:57:37 -08:00

View File

@@ -5,26 +5,30 @@
" "
" Overview " Overview
" -------- " --------
" Vim script to help moving around in larger Python source files. It displays " This Vim script helps you find yourself in larger Python source files. It
" current class, method or function the cursor is placed in on the status " displays the current Python class, method or function the cursor is placed
" line for every python file. It's more clever than Yegappan Lakshmanan's " on in the status line. It's smarter than Yegappan Lakshmanan's taglist.vim
" taglist.vim because it takes into account indetation and comments to " because it takes indentation and comments into account in order to determine
" determine what tag the cursor is placed in. " what identifier the cursor is placed on.
" "
" Requirements " Requirements
" ------------ " ------------
" This script needs only VIM compiled with Python interpreter. It doesn't rely " This script needs only VIM compiled with the Python interpreter. It doesn't
" on exuberant ctags utility. You can determine whether your VIM has Python " rely on the exuberant ctags utilities. You can determine whether your VIM
" support by issuing command :ver and looking for +python in the list of " has Python support by issuing command :ver and looking for +python in the
" features. " list of features.
" "
" Installation " Installation
" ------------ " ------------
" 1. Make sure your Vim has python feature on (+python). If not, you will need " 1. Make sure your Vim has the Python feature enabled (+python). If not, you
" to recompile it with --with-pythoninterp option to the configure script " will need to recompile it with the --with-pythoninterp option passed to
" 2. Copy script pythonhelper.vim to the $HOME/.vim/plugin directory " the configure script
" 3. add '%{TagInStatusLine()}' yo your vimrc/statusline " 2. Copy the pythonhelper.vim script to the $HOME/.vim/plugin directory, or
" 3. Run Vim and open any python file. " install it in some other way (vim-addon-manager, pathogen, ...)
" 3. Add '%{TagInStatusLine()}' to the statusline in your vimrc. You can also
" use %{TagInStatusLineTag()} or %{TagInStatusLineType()} for just the tag
" name or tag type respectively.
" 4. Run Vim and open any Python file.
" "
python << EOS python << EOS
@@ -124,7 +128,7 @@ class PythonTag(object):
# class SimplePythonTagsParser() {{{ # class SimplePythonTagsParser() {{{
class SimplePythonTagsParser(object): class SimplePythonTagsParser(object):
# DOC {{{ # DOC {{{
"""Provides a simple python tag parser. """Provides a simple Python tag parser.
""" """
# }}} # }}}
@@ -152,17 +156,10 @@ class SimplePythonTagsParser(object):
Parameters Parameters
source -- source for which the tags will be generated. It must source -- source for which the tags will be generated. It must
provide callable method readline (i.e. as file objects do). be a generator.
""" """
# }}} # }}}
# CODE {{{
# make sure source has readline() method {{{
if ((hasattr(source, 'readline') == 0) or
(callable(source.readline) == 0)):
raise AttributeError("Source must have callable readline method.")
# }}}
# remember what the source is # remember what the source is
self.source = source self.source = source
# }}} # }}}
@@ -170,36 +167,26 @@ class SimplePythonTagsParser(object):
def getTags(self): def getTags(self):
# DOC {{{ # DOC {{{
"""Determines all the tags for the buffer. Returns a tuple in format """Determines all the tags for the buffer. Returns a tuple in the format
(tagLineNumbers, tags,). (tagLineNumbers, tags,).
""" """
# }}} # }}}
# CODE {{{ # CODE {{{
# initialize the resulting list of the tag line numbers and the tag information {{{ # initialize the resulting list of tag line numbers and the tag information {{{
tagLineNumbers = [] tagLineNumbers = []
tags = {} tags = {}
# }}} # }}}
# initalize local auxiliary variables {{{ # initalize local auxiliary variables {{{
tagsStack = [] tagsStack = []
lineNumber = 0
# }}} # }}}
# go through all the lines in the source and localize all python tags in it {{{ import itertools
while 1: # go through all the lines in the source and localize all Python tags in it {{{
# get next line for (line, lineNumber) in zip(self.source, itertools.count(1)):
line = self.source.readline()
# finish if this is the end of the source {{{ # extract the line's indentation characters and its content {{{
if (line == ''):
break
# }}}
# increase the line number
lineNumber += 1
# extract the line indentation characters and its content {{{
lineMatch = self.COMMENTS_INDENT_RE.match(line) lineMatch = self.COMMENTS_INDENT_RE.match(line)
lineContent = lineMatch.group(2) lineContent = lineMatch.group(2)
# }}} # }}}
@@ -231,14 +218,14 @@ class SimplePythonTagsParser(object):
# }}} # }}}
# }}} # }}}
# return the tags data for the source # return tag data for the source
return (tagLineNumbers, tags,) return (tagLineNumbers, tags,)
# }}} # }}}
def getParentTag(self, tagsStack): def getParentTag(self, tagsStack):
# DOC {{{ # DOC {{{
"""Returns the parent/enclosing tag (instance of PythonTag()) from the """Given a tag, returns its parent tag (instance of PythonTag()) from the
specified tag list. If no such parent tag exists, returns None. specified tag list. If no such parent tag exists, returns None.
Parameters Parameters
@@ -304,18 +291,18 @@ class SimplePythonTagsParser(object):
tagName -- short name of the current tag tagName -- short name of the current tag
tagTypeDecidingMethod -- reference to method that is called to tagTypeDecidingMethod -- reference to the method that is called to
determine the type of the current tag determine the type of the current tag
""" """
# }}} # }}}
# CODE {{{ # CODE {{{
# compute the indentation level # compute indentation level
indentLevel = self.computeIndentationLevel(indentChars) indentLevel = self.computeIndentationLevel(indentChars)
# get the parent tag # get parent tag
parentTag = self.getParentTag(tagsStack) parentTag = self.getParentTag(tagsStack)
# handle an enclosed tag {{{ # handle enclosed tag {{{
while (parentTag): while (parentTag):
# if the indent level of the parent tag is greater than of the current tag, use parent tag of the parent tag {{{ # if the indent level of the parent tag is greater than of the current tag, use parent tag of the parent tag {{{
if (parentTag.indentLevel >= indentLevel): if (parentTag.indentLevel >= indentLevel):
@@ -330,7 +317,7 @@ class SimplePythonTagsParser(object):
break break
# }}} # }}}
# use parent tag of the parent tag # use the parent tag of the parent tag
parentTag = self.getParentTag(tagsStack) parentTag = self.getParentTag(tagsStack)
# }}} # }}}
# handle a top-indent level tag {{{ # handle a top-indent level tag {{{
@@ -386,74 +373,20 @@ class SimplePythonTagsParser(object):
# }}} # }}}
# }}} # }}}
def vimBufferIterator(vimBuffer):
# class VimReadlineBuffer() {{{ for line in vimBuffer:
class VimReadlineBuffer(object): yield line + "\n"
# DOC {{{
"""A simple wrapper class around vim's buffer that provides readline
method.
"""
# }}}
# METHODS {{{
def __init__(self, vimBuffer):
# DOC {{{
"""Initializes instances of VimReadlineBuffer().
Parameters
vimBuffer -- VIM's buffer
"""
# }}}
# CODE {{{
# remember the settings
self.vimBuffer = vimBuffer
# initialize instance attributes {{{
self.currentLine = -1
self.bufferLines = len(vimBuffer)
# }}}
# }}}
def readline(self):
# DOC {{{
"""Returns next line from the buffer. If all the buffer has been read,
returns empty string.
"""
# }}}
# CODE {{{
# increase the current line counter
self.currentLine += 1
# notify end of file if we reached beyond the last line {{{
if (self.currentLine == self.bufferLines):
return ''
# }}}
# return the line with an added newline (vim stores the lines without it)
return "%s\n" % (self.vimBuffer[self.currentLine],)
# }}}
# }}}
# }}}
def getNearestLineIndex(row, tagLineNumbers): def getNearestLineIndex(row, tagLineNumbers):
# DOC {{{ # DOC {{{
"""Returns the index of line in 'tagLineNumbers' list that is nearest to the """Returns the index of 'tagLineNumbers' that contains the line nearest to
specified cursor row. the specified cursor row.
Parameters Parameters
row -- current cursor row row -- current cursor row
tagLineNumbers -- list of tags' line numbers (ie. their position) tagLineNumbers -- list of tags' line numbers (ie. their position)
""" """
# }}} # }}}
@@ -465,34 +398,34 @@ def getNearestLineIndex(row, tagLineNumbers):
# go through all tag line numbers and find the one nearest to the specified row {{{ # go through all tag line numbers and find the one nearest to the specified row {{{
for lineIndex, lineNumber in enumerate(tagLineNumbers): for lineIndex, lineNumber in enumerate(tagLineNumbers):
# if the current line is nearer the current cursor position, take it {{{ # if the current line is nearer the current cursor position, take it {{{
if (nearestLineNumber < lineNumber <= row): if (nearestLineNumber < lineNumber <= row):
nearestLineNumber = lineNumber nearestLineNumber = lineNumber
nearestLineIndex = lineIndex nearestLineIndex = lineIndex
# }}} # }}}
# if we've got past the current cursor position, let's end the search {{{ # if we've come past the current cursor position, end the search {{{
if (lineNumber >= row): if (lineNumber >= row):
break break
# }}} # }}}
# }}} # }}}
# return index of the line with the nearest tag # return the index of the line with the nearest tag
return nearestLineIndex return nearestLineIndex
# }}} # }}}
def getTags(bufferNumber, changedTick): def getTags(bufferNumber, changedTick):
# DOC {{{ # DOC {{{
"""Reads the tags for the specified buffer number. Returns a tuple """Reads the tags for the buffer specified by the number. Returns a tuple
(taglinenumber[buffer], tags[buffer],). of the format (taglinenumber[buffer], tags[buffer],).
Parameters Parameters
bufferNumber -- number of the current buffer bufferNumber -- number of the current buffer
changedTick -- ever increasing number used to tell if the buffer has changedTick -- always-increasing number used to indicate that the buffer
been modified since the last time has been modified since the last time
""" """
# }}} # }}}
@@ -502,11 +435,11 @@ def getTags(bufferNumber, changedTick):
# return immediately if there's no need to update the tags {{{ # return immediately if there's no need to update the tags {{{
if (BUFFERTICKS.get(bufferNumber, None) == changedTick): if (BUFFERTICKS.get(bufferNumber, None) == changedTick):
return (TAGLINENUMBERS[bufferNumber], TAGS[bufferNumber],) return (TAGLINENUMBERS[bufferNumber], TAGS[bufferNumber],)
# }}} # }}}
# get the tags {{{ # get the tags {{{
simpleTagsParser = SimplePythonTagsParser(VimReadlineBuffer(vim.current.buffer)) simpleTagsParser = SimplePythonTagsParser(vimBufferIterator(vim.current.buffer))
tagLineNumbers, tags = simpleTagsParser.getTags() tagLineNumbers, tags = simpleTagsParser.getTags()
# }}} # }}}
@@ -527,37 +460,38 @@ def findTag(bufferNumber, changedTick):
Parameters Parameters
bufferNumber -- number of the current buffer bufferNumber -- number of the current buffer
changedTick -- ever increasing number used to tell if the buffer has changedTick -- always-increasing number used to indicate that the buffer
been modified since the last time has been modified since the last time
""" """
# }}} # }}}
# CODE {{{ # CODE {{{
# try to find the best tag {{{ # try to find the best tag {{{
try: try:
# get the tags data for the current buffer # get the tag data for the current buffer
tagLineNumbers, tags = getTags(bufferNumber, changedTick) tagLineNumbers, tags = getTags(bufferNumber, changedTick)
# link to vim's internal data {{{ # link to Vim's internal data {{{
currentBuffer = vim.current.buffer currentBuffer = vim.current.buffer
currentWindow = vim.current.window currentWindow = vim.current.window
row, col = currentWindow.cursor row, col = currentWindow.cursor
# }}} # }}}
# get the index of the nearest line # get the index of the nearest line
nearestLineIndex = getNearestLineIndex(row, tagLineNumbers) nearestLineIndex = getNearestLineIndex(row, tagLineNumbers)
# if any line was found, try to find if the tag is appropriate {{{ # if a line has been found, find out if the tag is correct {{{
# (ie. the cursor can be below the last tag but on a code that has nothing # E.g. the cursor might be below the last tag, but in code that has
# to do with the tag, because it's indented differently, in such case no # nothing to do with the tag, which we know because the line is
# appropriate tag has been found.) # indented differently. In such a case no applicable tag has been
# found.
while (nearestLineIndex > -1): while (nearestLineIndex > -1):
# get the line number of the nearest tag # get the line number of the nearest tag
nearestLineNumber = tagLineNumbers[nearestLineIndex] nearestLineNumber = tagLineNumbers[nearestLineIndex]
# walk through all the lines in range (nearestTagLine, cursorRow) {{{ # walk through all the lines in the range (nearestTagLine, cursorRow) {{{
for lineNumber in xrange(nearestLineNumber + 1, row): for lineNumber in xrange(nearestLineNumber + 1, row):
# get the current line # get the current line
line = currentBuffer[lineNumber] line = currentBuffer[lineNumber]
@@ -586,56 +520,69 @@ def findTag(bufferNumber, changedTick):
if (i == len(line)): if (i == len(line)):
continue continue
# }}} # }}}
# if the next character is a '#' (python comment), skip the line {{{ # if the next character is a '#' (python comment), skip to the next line {{{
if (line[i] == '#'): if (line[i] == '#'):
continue continue
# }}} # }}}
# if the line's indentation starts before or at the nearest tag's one, the tag is invalid {{{ # if the line's indentation starts before or at the nearest tag's, the tag is invalid {{{
if (lineStart <= tags[nearestLineNumber].indentLevel): if (lineStart <= tags[nearestLineNumber].indentLevel):
nearestLineIndex -= 1 nearestLineIndex -= 1
break break
# }}} # }}}
# }}} # }}}
# }}} # }}}
# the tag is appropriate, so use it {{{ # the tag is correct, so use it {{{
else: else:
break break
# }}} # }}}
# }}}
# no applicable tag has been found {{{
else:
nearestLineNumber = -1
# }}} # }}}
# no appropriate tag has been found {{{
else:
nearestLineNumber = -1
# }}}
# describe the cursor position (what tag the cursor is on) {{{ # describe the cursor position (what tag the cursor is on) {{{
# reset the description # reset the description
tagDescription = "" tagDescription = ""
tagDescriptionTag = ""
tagDescriptionType = ""
# if an appropriate tag has been found, set the description accordingly {{{ # if an applicable tag has been found, set the description accordingly {{{
if (nearestLineNumber > -1): if (nearestLineNumber > -1):
tagInfo = tags[nearestLineNumber] tagInfo = tags[nearestLineNumber]
tagDescription = "%s (%s)" % (tagInfo.fullName, PythonTag.TAG_TYPE_NAME[tagInfo.type],) tagDescriptionTag = tagInfo.fullName
# }}} tagDescriptionType = PythonTag.TAG_TYPE_NAME[tagInfo.type]
# }}} tagDescription = "%s (%s)" % (tagDescriptionTag, tagDescriptionType)
# }}}
# }}}
# update the variable for the status line so it get updated with the new description # update the variable for the status line so it get updated with the new description
vim.command("let w:PHStatusLine=\"%s\"" % (tagDescription,)) vim.command("let w:PHStatusLine=\"%s\"" % (tagDescription,))
vim.command("let w:PHStatusLineTag=\"%s\"" % (tagDescriptionTag,))
vim.command("let w:PHStatusLineType=\"%s\"" % (tagDescriptionType,))
# }}} # }}}
# handle possible exceptions {{{ # handle possible exceptions {{{
except Exception: except Exception:
# FIXME: wrap try/except blocks around single sources of exceptions
# ONLY. Break this try/except block into as many small ones as you
# need, and only catch classes of exceptions that you have encountered.
# Catching "Exception" is very, very bad style!
# To the author: why is this clause here? There's no git log for why you
# have added it. Can you please put in a comment of a specific situation
# where you have encountered exceptions?
# bury into the traceback {{{ # bury into the traceback {{{
ec, ei, tb = sys.exc_info() ec, ei, tb = sys.exc_info()
while (tb != None): while (tb != None):
if (tb.tb_next == None): if (tb.tb_next == None):
break break
tb = tb.tb_next tb = tb.tb_next
# }}} # }}}
# spit out the error {{{ # spit out the error {{{
print "ERROR: %s %s %s:%u" % (ec.__name__, ei, tb.tb_frame.f_code.co_filename, tb.tb_lineno,) print "ERROR: %s %s %s:%u" % (ec.__name__, ei, tb.tb_frame.f_code.co_filename, tb.tb_lineno,)
time.sleep(0.5) time.sleep(0.5)
# }}} # }}}
# }}} # }}}
# }}} # }}}
@@ -643,7 +590,7 @@ def findTag(bufferNumber, changedTick):
def deleteTags(bufferNumber): def deleteTags(bufferNumber):
# DOC {{{ # DOC {{{
"""Removes tags data for the specified buffer number. """Removes tag data for the specified buffer number.
Parameters Parameters
@@ -656,12 +603,11 @@ def deleteTags(bufferNumber):
global TAGS, TAGLINENUMBERS, BUFFERTICKS global TAGS, TAGLINENUMBERS, BUFFERTICKS
# try to delete the tags for the buffer {{{ # try to delete the tags for the buffer {{{
try: for o in (TAGS, TAGLINENUMBERS, BUFFERTICKS):
del TAGS[bufferNumber] try:
del TAGLINENUMBERS[bufferNumber] del o[bufferNumber]
del BUFFERTICKS[bufferNumber] except KeyError:
except: pass
pass
# }}} # }}}
# }}} # }}}
@@ -671,23 +617,27 @@ EOS
" VIM functions {{{ " VIM functions {{{
function! PHCursorHold() function! PHCursorHold()
" only python is supported {{{ " only Python is supported {{{
if (!exists('b:current_syntax') || (b:current_syntax != 'python')) if (!exists('b:current_syntax') || (b:current_syntax != 'python'))
let w:PHStatusLine = '' let w:PHStatusLine = ''
return let w:PHStatusLineTag = ''
let w:PHStatusLineType = ''
return
endif endif
" }}} " }}}
" call python function findTag() with the current buffer number and changed ticks " call Python function findTag() with the current buffer number and change status indicator
execute 'python findTag(' . expand("<abuf>") . ', ' . b:changedtick . ')' execute 'python findTag(' . expand("<abuf>") . ', ' . b:changedtick . ')'
endfunction endfunction
function! PHBufferDelete() function! PHBufferDelete()
" set PHStatusLine for this window to empty string " set the PHStatusLine etc for this window to an empty string
let w:PHStatusLine = "" let w:PHStatusLine = ""
let w:PHStatusLineTag = ''
let w:PHStatusLineType = ''
" call python function deleteTags() with the cur " call Python function deleteTags() with the current buffer number and change status indicator
execute 'python deleteTags(' . expand("<abuf>") . ')' execute 'python deleteTags(' . expand("<abuf>") . ')'
endfunction endfunction
@@ -696,7 +646,29 @@ function! TagInStatusLine()
" return value of w:PHStatusLine in case it's set " return value of w:PHStatusLine in case it's set
if (exists("w:PHStatusLine")) if (exists("w:PHStatusLine"))
return w:PHStatusLine return w:PHStatusLine
" otherwise just return empty string " otherwise just return an empty string
else
return ""
endif
endfunction
function! TagInStatusLineTag()
" return value of w:PHStatusLineTag in case it's set
if (exists("w:PHStatusLineTag"))
return w:PHStatusLineTag
" otherwise just return an empty string
else
return ""
endif
endfunction
function! TagInStatusLineType()
" return value of w:PHStatusLineType in case it's set
if (exists("w:PHStatusLineType"))
return w:PHStatusLineType
" otherwise just return an empty string
else else
return "" return ""
endif endif
@@ -735,16 +707,17 @@ endfunction
" }}} " }}}
" event binding, vim customizing {{{ " event binding, Vim customization {{{
" autocommands binding " autocommands
autocmd CursorHold * call PHCursorHold() autocmd CursorHold * call PHCursorHold()
autocmd CursorHoldI * call PHCursorHold() autocmd CursorHoldI * call PHCursorHold()
autocmd BufDelete * silent call PHBufferDelete() autocmd BufDelete * silent call PHBufferDelete()
" time that determines after how long time of no activity the CursorHold event " period of no activity after which the CursorHold event is triggered
" is fired up if (exists("g:pythonhelper_updatetime"))
set updatetime=1000 let &updatetime = g:pythonhelper_updatetime
endif
" }}} " }}}