From b0841be7ee13371a795ed8673af148584cdb6016 Mon Sep 17 00:00:00 2001 From: Michal Vitecek Date: Fri, 10 Jul 2009 00:00:00 +0000 Subject: [PATCH] Version 0.82: - fixed a bug when nested functions/classes were not properly detected --- plugin/pythonhelper.vim | 396 ++++++++++++++++++++++++++-------------- 1 file changed, 262 insertions(+), 134 deletions(-) diff --git a/plugin/pythonhelper.vim b/plugin/pythonhelper.vim index 7ffeb90..602b98a 100644 --- a/plugin/pythonhelper.vim +++ b/plugin/pythonhelper.vim @@ -1,7 +1,7 @@ " File: pythonhelper.vim " Author: Michal Vitecek -" Version: 0.81 -" Last Modified: Oct 24, 2002 +" Version: 0.82 +" Last Modified: Jul 10, 2009 " " Overview " -------- @@ -34,17 +34,18 @@ python << EOS # import of required modules {{{ -import vim -import time -import sys import re +import sys +import time +import traceback +import vim # }}} # global dictionaries of tags and their line numbers, keys are buffer numbers {{{ -TAGS = {} -TAGLINENUMBERS = {} -BUFFERTICKS = {} +TAGS = {} +TAGLINENUMBERS = {} +BUFFERTICKS = {} # }}} @@ -58,17 +59,17 @@ class PythonTag(object): # STATIC VARIABLES {{{ - # tag type IDs {{{ - TAGTYPE_CLASS = 0 - TAGTYPE_METHOD = 1 - TAGTYPE_FUNCTION = 2 + # possible tag types {{{ + TT_CLASS = 0 + TT_METHOD = 1 + TT_FUNCTION = 2 # }}} # tag type names {{{ - typeName = { - TAGTYPE_CLASS : "class", - TAGTYPE_METHOD : "method", - TAGTYPE_FUNCTION : "function", + TAG_TYPE_NAME = { + TT_CLASS : "class", + TT_METHOD : "method", + TT_FUNCTION : "function", } # }}} @@ -79,7 +80,7 @@ class PythonTag(object): def __init__(self, type, name, fullName, lineNumber, indentLevel): # DOC {{{ - """Initializes instances of class PythonTag(). + """Initializes instances of PythonTag(). Parameters @@ -96,11 +97,13 @@ class PythonTag(object): # }}} # CODE {{{ - self.type = type - self.name = name - self.fullName = fullName - self.lineNumber = lineNumber - self.indentLevel = indentLevel + # remember the settings {{{ + self.type = type + self.name = name + self.fullName = fullName + self.lineNumber = lineNumber + self.indentLevel = indentLevel + # }}} # }}} @@ -111,10 +114,11 @@ class PythonTag(object): # }}} # CODE {{{ - return "%s (%s) [%s, %u, %u]" % (self.name, PythonTag.typeName[self.type], + return "%s (%s) [%s, %u, %u]" % (self.name, PythonTag.TAG_TYPE_NAME[self.type], self.fullName, self.lineNumber, self.indentLevel,) # }}} + __repr__ = __str__ @@ -125,8 +129,7 @@ class PythonTag(object): # class SimplePythonTagsParser() {{{ class SimplePythonTagsParser(object): # DOC {{{ - """Provides a simple python tag parser. Returns list of PythonTag() - instances. + """Provides a simple python tag parser. """ # }}} @@ -135,13 +138,12 @@ class SimplePythonTagsParser(object): # how many chars a single tab represents (visually) TABSIZE = 8 - - # regexp used to get indentation and strip comments - commentsIndentStripRE = re.compile('([ \t]*)([^\n#]*).*') - # regexp used to get class name - classRE = re.compile('class[ \t]+([^(:]+).*') - # regexp used to get method or function name - methodRE = re.compile('def[ \t]+([^(]+).*') + # regexp used to extract indentation and strip comments + COMMENTS_INDENT_RE = re.compile('([ \t]*)([^\n#]*).*') + # regexp used to extract a class name + CLASS_RE = re.compile('class[ \t]+([^(:]+).*') + # regexp used to extract a method or function name + METHOD_RE = re.compile('def[ \t]+([^(]+).*') # }}} @@ -150,7 +152,7 @@ class SimplePythonTagsParser(object): def __init__(self, source): # DOC {{{ - """Initializes the instance of class SimplePythonTagsParser(). + """Initializes instances of SimplePythonTagsParser(). Parameters @@ -161,8 +163,8 @@ class SimplePythonTagsParser(object): # CODE {{{ # make sure source has readline() method {{{ - if (not(hasattr(source, 'readline') and - callable(source.readline))): + if ((hasattr(source, 'readline') == 0) or + (callable(source.readline) == 0)): raise AttributeError("Source must have callable readline method.") # }}} @@ -173,19 +175,23 @@ class SimplePythonTagsParser(object): def getTags(self): # DOC {{{ - """Determines all the tags for the buffer. Returns tuple in format + """Determines all the tags for the buffer. Returns a tuple in format (tagLineNumbers, tags,). """ # }}} # CODE {{{ - tagLineNumbers = [] - tags = {} + # initialize the resulting list of the tag line numbers and the tag information {{{ + tagLineNumbers = [] + tags = {} + # }}} - # list (stack) of all currently active tags - tagsStack = [] + # initalize local auxiliary variables {{{ + tagsStack = [] + lineNumber = 0 + # }}} - lineNumber = 0 + # go through all the lines in the source and localize all python tags in it {{{ while 1: # get next line line = self.source.readline() @@ -195,57 +201,73 @@ class SimplePythonTagsParser(object): break # }}} + # increase the line number lineNumber += 1 - lineMatch = self.commentsIndentStripRE.match(line) - lineContents = lineMatch.group(2) - # class tag {{{ - tagMatch = self.classRE.match(lineContents) + + # extract the line indentation characters and its content {{{ + lineMatch = self.COMMENTS_INDENT_RE.match(line) + lineContent = lineMatch.group(2) + # }}} + + # handle the class tag {{{ + # match for the class tag + tagMatch = self.CLASS_RE.match(lineContent) + + # if the class tag has been found, store some information on it {{{ if (tagMatch): currentTag = self.getPythonTag(tagsStack, lineNumber, lineMatch.group(1), tagMatch.group(1), self.tagClassTypeDecidingMethod) tagLineNumbers.append(lineNumber) tags[lineNumber] = currentTag # }}} - # function/method/none tag {{{ + # }}} + # handle the function/method/none tag {{{ else: - tagMatch = self.methodRE.match(lineContents) + # match for the method/function tag + tagMatch = self.METHOD_RE.match(lineContent) + + # if the method/function tag has been found, store some information on it {{{ if (tagMatch): currentTag = self.getPythonTag(tagsStack, lineNumber, lineMatch.group(1), tagMatch.group(1), self.tagFunctionTypeDecidingMethod) tagLineNumbers.append(lineNumber) tags[lineNumber] = currentTag + # }}} # }}} + # }}} # return the tags data for the source return (tagLineNumbers, tags,) # }}} - def getPreviousTag(self, tagsStack): + def getParentTag(self, tagsStack): # DOC {{{ - """Returns the previous tag (instance of PythonTag()) from the - specified tag list if possible. If not, returns None. + """Returns the parent/enclosing tag (instance of PythonTag()) from the + specified tag list. If no such parent tag exists, returns None. Parameters - tagsStack -- list (stack) of currently active PythonTag() instances + tagsStack -- list (stack) of currently open PythonTag() instances """ # }}} # CODE {{{ + # determine the parent tag {{{ if (len(tagsStack)): - previousTag = tagsStack[-1] + parentTag = tagsStack[-1] else: - previousTag = None + parentTag = None + # }}} # return the tag - return previousTag + return parentTag # }}} - def computeIndentLevel(self, indentChars): + def computeIndentationLevel(indentChars): # DOC {{{ - """Computes indent level from the specified string. + """Computes the indentation level from the specified string. Parameters @@ -254,15 +276,21 @@ class SimplePythonTagsParser(object): # }}} # CODE {{{ + # initialize the indentation level indentLevel = 0 + + # compute the indentation level (expand tabs) {{{ for char in indentChars: if (char == '\t'): - indentLevel += self.TABSIZE + indentLevel += SimplePythonTagsParser.TABSIZE else: indentLevel += 1 + # }}} + # return the computed indentation level return indentLevel # }}} + computeIndentationLevel = staticmethod(computeIndentationLevel) def getPythonTag(self, tagsStack, lineNumber, indentChars, tagName, tagTypeDecidingMethod): @@ -282,58 +310,81 @@ class SimplePythonTagsParser(object): tagName -- short name of the current tag tagTypeDecidingMethod -- reference to method that is called to - determine type of the current tag + determine the type of the current tag """ # }}} # CODE {{{ - indentLevel = self.computeIndentLevel(indentChars) - previousTag = self.getPreviousTag(tagsStack) - # code for enclosed tag {{{ - while (previousTag): - if (previousTag.indentLevel >= indentLevel): + # compute the indentation level + indentLevel = self.computeIndentationLevel(indentChars) + # get the parent tag + parentTag = self.getParentTag(tagsStack) + + # handle an enclosed tag {{{ + 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 (parentTag.indentLevel >= indentLevel): del tagsStack[-1] + # }}} + # otherwise we have all information on the current tag and can return it {{{ else: - tagType = tagTypeDecidingMethod(previousTag.type) - tag = PythonTag(tagType, tagName, "%s.%s" % (previousTag.fullName, tagName,), lineNumber, indentLevel) - tagsStack.append(tag) - return tag - previousTag = self.getPreviousTag(tagsStack) + # create the tag + tag = PythonTag(tagTypeDecidingMethod(parentTag.type), tagName, "%s.%s" % (parentTag.fullName, tagName,), lineNumber, indentLevel) + + # break the loop + break + # }}} + + # use parent tag of the parent tag + parentTag = self.getParentTag(tagsStack) # }}} - # code for tag in top indent level {{{ + # handle a top-indent level tag {{{ else: - tagType = tagTypeDecidingMethod(None) - tag = PythonTag(tagType, tagName, tagName, lineNumber, indentLevel) - tagsStack.append(tag) - return tag + # create the tag + tag = PythonTag(tagTypeDecidingMethod(None), tagName, tagName, lineNumber, indentLevel) # }}} + + # add the tag to the list of tags + tagsStack.append(tag) + + # return the tag + return tag # }}} - def tagClassTypeDecidingMethod(self, previousTagsType): + def tagClassTypeDecidingMethod(self, parentTagType): # DOC {{{ """Returns tag type of the current tag based on its previous tag (super tag) for classes. + + Parameters + + parentTagType -- type of the enclosing/parent tag """ # }}} # CODE {{{ - return PythonTag.TAGTYPE_CLASS + # is always class no matter what + return PythonTag.TT_CLASS # }}} - def tagFunctionTypeDecidingMethod(self, previousTagsType): + def tagFunctionTypeDecidingMethod(self, parentTagType): # DOC {{{ """Returns tag type of the current tag based on its previous tag (super tag) for functions/methods. + + Parameters + + parentTagType -- type of the enclosing/parent tag """ # }}} # CODE {{{ - if (previousTagsType == PythonTag.TAGTYPE_CLASS): - return PythonTag.TAGTYPE_METHOD + if (parentTagType == PythonTag.TT_CLASS): + return PythonTag.TT_METHOD else: - return PythonTag.TAGTYPE_FUNCTION + return PythonTag.TT_FUNCTION # }}} @@ -354,7 +405,7 @@ class VimReadlineBuffer(object): def __init__(self, vimBuffer): # DOC {{{ - """Initializes the instance of class VimReadlineBuffer(). + """Initializes instances of VimReadlineBuffer(). Parameters @@ -363,9 +414,13 @@ class VimReadlineBuffer(object): # }}} # CODE {{{ - self.vimBuffer = vimBuffer - self.currentLine = -1 - self.bufferLines = len(vimBuffer) + # remember the settings + self.vimBuffer = vimBuffer + + # initialize instance attributes {{{ + self.currentLine = -1 + self.bufferLines = len(vimBuffer) + # }}} # }}} @@ -377,6 +432,7 @@ class VimReadlineBuffer(object): # }}} # CODE {{{ + # increase the current line counter self.currentLine += 1 # notify end of file if we reached beyond the last line {{{ @@ -384,9 +440,10 @@ class VimReadlineBuffer(object): return '' # }}} - # return the line with added newline (vim stores the lines without newline) + # return the line with an added newline (vim stores the lines without it) return "%s\n" % (self.vimBuffer[self.currentLine],) # }}} + # }}} # }}} @@ -394,8 +451,8 @@ class VimReadlineBuffer(object): def getNearestLineIndex(row, tagLineNumbers): # DOC {{{ - """Returns index of line in tagLineNumbers list that is nearest to the - current cursor row. + """Returns the index of line in 'tagLineNumbers' list that is nearest to the + specified cursor row. Parameters @@ -406,27 +463,33 @@ def getNearestLineIndex(row, tagLineNumbers): # }}} # CODE {{{ - nearestLineNumber = -1 - nearestLineIndex = -1 - i = 0 - for lineNumber in tagLineNumbers: + # initialize local auxiliary variables {{{ + nearestLineNumber = -1 + nearestLineIndex = -1 + # }}} + + # go through all tag line numbers and find the one nearest to the specified row {{{ + for lineIndex, lineNumber in enumerate(tagLineNumbers): # if the current line is nearer the current cursor position, take it {{{ if (nearestLineNumber < lineNumber <= row): - nearestLineNumber = lineNumber - nearestLineIndex = i + nearestLineNumber = lineNumber + nearestLineIndex = lineIndex # }}} + # if we've got past the current cursor position, let's end the search {{{ if (lineNumber >= row): break # }}} - i += 1 + # }}} + + # return index of the line with the nearest tag return nearestLineIndex # }}} def getTags(bufferNumber, changedTick): # DOC {{{ - """Reads the tags for the specified buffer number. Returns tuple + """Reads the tags for the specified buffer number. Returns a tuple (taglinenumber[buffer], tags[buffer],). Parameters @@ -439,10 +502,11 @@ def getTags(bufferNumber, changedTick): # }}} # CODE {{{ - global TAGLINENUMBERS, TAGS, BUFFERTICKS + # define global variables + global TAGLINENUMBERS, TAGS, BUFFERTICKS # return immediately if there's no need to update the tags {{{ - if ((BUFFERTICKS.has_key(bufferNumber)) and (BUFFERTICKS[bufferNumber] == changedTick)): + if (BUFFERTICKS.get(bufferNumber, None) == changedTick): return (TAGLINENUMBERS[bufferNumber], TAGS[bufferNumber],) # }}} @@ -452,12 +516,12 @@ def getTags(bufferNumber, changedTick): # }}} # update the global variables {{{ - TAGS[bufferNumber] = tags - TAGLINENUMBERS[bufferNumber] = tagLineNumbers - BUFFERTICKS[bufferNumber] = changedTick + TAGS[bufferNumber] = tags + TAGLINENUMBERS[bufferNumber] = tagLineNumbers + BUFFERTICKS[bufferNumber] = changedTick # }}} - # return the tags data + # return the tuple (tagLineNumbers, tags,) return (tagLineNumbers, tags,) # }}} @@ -476,12 +540,12 @@ def findTag(bufferNumber, changedTick): # }}} # CODE {{{ + # try to find the best tag {{{ try: - # get the tags data for the current buffer {{{ + # get the tags data for the current buffer tagLineNumbers, tags = getTags(bufferNumber, changedTick) - # }}} - # link to vim internal data {{{ + # link to vim's internal data {{{ currentBuffer = vim.current.buffer currentWindow = vim.current.window row, col = currentWindow.cursor @@ -489,66 +553,97 @@ def findTag(bufferNumber, changedTick): # get the index of the nearest line nearestLineIndex = getNearestLineIndex(row, tagLineNumbers) + # if any line was found, try to find if the tag is appropriate {{{ # (ie. the cursor can be below the last tag but on a code that has nothing # to do with the tag, because it's indented differently, in such case no # appropriate tag has been found.) - if (nearestLineIndex > -1): - nearestLineNumber = tagLineNumbers[nearestLineIndex] - # walk through all the lines in range (nearestTagLine, cursorRow) {{{ - for i in xrange(nearestLineNumber + 1, row): - line = currentBuffer[i] - # count the indentation of the line, if it's lower that the tag's, the found tag is wrong {{{ - if (len(line)): + while (nearestLineIndex > -1): + # get the line number of the nearest tag + nearestLineNumber = tagLineNumbers[nearestLineIndex] + + # walk through all the lines in range (nearestTagLine, cursorRow) {{{ + for lineNumber in xrange(nearestLineNumber + 1, row): + # get the current line + line = currentBuffer[lineNumber] + + # count the indentation of the line, if it's lower than the tag's, the tag is invalid {{{ + if (len(line)): + # initialize local auxiliary variables {{{ + lineStart = 0 + i = 0 + # }}} + # compute the indentation of the line {{{ - lineStart = 0 - j = 0 - while ((j < len(line)) and (line[j].isspace())): - if (line[j] == '\t'): - lineStart += SimplePythonTagsParser.TABSIZE - else: - lineStart += 1 - j += 1 - # if the line contains only spaces, it doesn't count {{{ - if (j == len(line)): + while ((i < len(line)) and (line[i].isspace())): + # move the start of the line code {{{ + if (line[i] == '\t'): + lineStart += SimplePythonTagsParser.TABSIZE + else: + lineStart += 1 + # }}} + + # go to the next character on the line + i += 1 + # }}} + + # if the line contains only spaces, skip it {{{ + if (i == len(line)): continue # }}} - # if the next character is # (python comment), this line doesn't count {{{ - if (line[j] == '#'): + # if the next character is a '#' (python comment), skip the line {{{ + if (line[i] == '#'): continue # }}} + + # if the line's indentation starts before or at the nearest tag's one, the tag is invalid {{{ + if (lineStart <= tags[nearestLineNumber].indentLevel): + nearestLineIndex -= 1 + break # }}} - # if the line's indentation starts before or at the nearest tag's one, the tag is wrong {{{ - if (lineStart <= tags[nearestLineNumber].indentLevel): - nearestLineNumber = -1 - break - # }}} - # }}} + # }}} + # }}} + # the tag is appropriate, so use it {{{ + else: + break # }}} + # }}} + # no appropriate tag has been found {{{ else: nearestLineNumber = -1 # }}} - # describe the cursor position (what tag it's in) {{{ + # describe the cursor position (what tag the cursor is on) {{{ + # reset the description tagDescription = "" + + # if an appropriate tag has been found, set the description accordingly {{{ if (nearestLineNumber > -1): tagInfo = tags[nearestLineNumber] - tagDescription = "[in %s (%s)]" % (tagInfo.fullName, PythonTag.typeName[tagInfo.type],) + tagDescription = "[in %s (%s)]" % (tagInfo.fullName, PythonTag.TAG_TYPE_NAME[tagInfo.type],) + # }}} # }}} - # update the variable for the status line so it will be updated next time + # update the variable for the status line so it get updated with the new description vim.command("let w:PHStatusLine=\"%s\"" % (tagDescription,)) - except: - # spit out debugging information {{{ + # }}} + + # handle possible exceptions {{{ + except Exception: + # bury into the traceback {{{ ec, ei, tb = sys.exc_info() while (tb != None): if (tb.tb_next == None): break tb = tb.tb_next + # }}} + + # spit out the error {{{ print "ERROR: %s %s %s:%u" % (ec.__name__, ei, tb.tb_frame.f_code.co_filename, tb.tb_lineno,) time.sleep(0.5) # }}} # }}} + # }}} def deleteTags(bufferNumber): @@ -562,8 +657,10 @@ def deleteTags(bufferNumber): # }}} # CODE {{{ - global TAGS, TAGLINENUMBERS, BUFFERTICKS + # define global variables + global TAGS, TAGLINENUMBERS, BUFFERTICKS + # try to delete the tags for the buffer {{{ try: del TAGS[bufferNumber] del TAGLINENUMBERS[bufferNumber] @@ -571,6 +668,7 @@ def deleteTags(bufferNumber): except: pass # }}} + # }}} EOS @@ -609,6 +707,36 @@ function! TagInStatusLine() endif endfunction + +function! PHPreviousClassMethod() + call search('^[ \t]*\(class\|def\)\>', 'bw') +endfunction + + +function! PHNextClassMethod() + call search('^[ \t]*\(class\|def\)\>', 'w') +endfunction + + +function! PHPreviousClass() + call search('^[ \t]*class\>', 'bw') +endfunction + + +function! PHNextClass() + call search('^[ \t]*class\>', 'w') +endfunction + + +function! PHPreviousMethod() + call search('^[ \t]*def\>', 'bw') +endfunction + + +function! PHNextMethod() + call search('^[ \t]*def\>', 'w') +endfunction + " }}}