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

Version 0.82: - fixed a bug when nested functions/classes were not properly detected

This commit is contained in:
Michal Vitecek
2009-07-10 00:00:00 +00:00
committed by Able Scraper
parent ab9d0e5e69
commit b0841be7ee

View File

@@ -1,7 +1,7 @@
" File: pythonhelper.vim " File: pythonhelper.vim
" Author: Michal Vitecek <fuf-at-mageo-dot-cz> " Author: Michal Vitecek <fuf-at-mageo-dot-cz>
" Version: 0.81 " Version: 0.82
" Last Modified: Oct 24, 2002 " Last Modified: Jul 10, 2009
" "
" Overview " Overview
" -------- " --------
@@ -34,10 +34,11 @@
python << EOS python << EOS
# import of required modules {{{ # import of required modules {{{
import vim
import time
import sys
import re import re
import sys
import time
import traceback
import vim
# }}} # }}}
@@ -58,17 +59,17 @@ class PythonTag(object):
# STATIC VARIABLES {{{ # STATIC VARIABLES {{{
# tag type IDs {{{ # possible tag types {{{
TAGTYPE_CLASS = 0 TT_CLASS = 0
TAGTYPE_METHOD = 1 TT_METHOD = 1
TAGTYPE_FUNCTION = 2 TT_FUNCTION = 2
# }}} # }}}
# tag type names {{{ # tag type names {{{
typeName = { TAG_TYPE_NAME = {
TAGTYPE_CLASS : "class", TT_CLASS : "class",
TAGTYPE_METHOD : "method", TT_METHOD : "method",
TAGTYPE_FUNCTION : "function", TT_FUNCTION : "function",
} }
# }}} # }}}
@@ -79,7 +80,7 @@ class PythonTag(object):
def __init__(self, type, name, fullName, lineNumber, indentLevel): def __init__(self, type, name, fullName, lineNumber, indentLevel):
# DOC {{{ # DOC {{{
"""Initializes instances of class PythonTag(). """Initializes instances of PythonTag().
Parameters Parameters
@@ -96,12 +97,14 @@ class PythonTag(object):
# }}} # }}}
# CODE {{{ # CODE {{{
# remember the settings {{{
self.type = type self.type = type
self.name = name self.name = name
self.fullName = fullName self.fullName = fullName
self.lineNumber = lineNumber self.lineNumber = lineNumber
self.indentLevel = indentLevel self.indentLevel = indentLevel
# }}} # }}}
# }}}
def __str__(self): def __str__(self):
@@ -111,10 +114,11 @@ class PythonTag(object):
# }}} # }}}
# CODE {{{ # 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,) self.fullName, self.lineNumber, self.indentLevel,)
# }}} # }}}
__repr__ = __str__ __repr__ = __str__
@@ -125,8 +129,7 @@ class PythonTag(object):
# class SimplePythonTagsParser() {{{ # class SimplePythonTagsParser() {{{
class SimplePythonTagsParser(object): class SimplePythonTagsParser(object):
# DOC {{{ # DOC {{{
"""Provides a simple python tag parser. Returns list of PythonTag() """Provides a simple python tag parser.
instances.
""" """
# }}} # }}}
@@ -135,13 +138,12 @@ class SimplePythonTagsParser(object):
# how many chars a single tab represents (visually) # how many chars a single tab represents (visually)
TABSIZE = 8 TABSIZE = 8
# regexp used to extract indentation and strip comments
# regexp used to get indentation and strip comments COMMENTS_INDENT_RE = re.compile('([ \t]*)([^\n#]*).*')
commentsIndentStripRE = re.compile('([ \t]*)([^\n#]*).*') # regexp used to extract a class name
# regexp used to get class name CLASS_RE = re.compile('class[ \t]+([^(:]+).*')
classRE = re.compile('class[ \t]+([^(:]+).*') # regexp used to extract a method or function name
# regexp used to get method or function name METHOD_RE = re.compile('def[ \t]+([^(]+).*')
methodRE = re.compile('def[ \t]+([^(]+).*')
# }}} # }}}
@@ -150,7 +152,7 @@ class SimplePythonTagsParser(object):
def __init__(self, source): def __init__(self, source):
# DOC {{{ # DOC {{{
"""Initializes the instance of class SimplePythonTagsParser(). """Initializes instances of SimplePythonTagsParser().
Parameters Parameters
@@ -161,8 +163,8 @@ class SimplePythonTagsParser(object):
# CODE {{{ # CODE {{{
# make sure source has readline() method {{{ # make sure source has readline() method {{{
if (not(hasattr(source, 'readline') and if ((hasattr(source, 'readline') == 0) or
callable(source.readline))): (callable(source.readline) == 0)):
raise AttributeError("Source must have callable readline method.") raise AttributeError("Source must have callable readline method.")
# }}} # }}}
@@ -173,19 +175,23 @@ class SimplePythonTagsParser(object):
def getTags(self): def getTags(self):
# DOC {{{ # 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,). (tagLineNumbers, tags,).
""" """
# }}} # }}}
# CODE {{{ # CODE {{{
# initialize the resulting list of the tag line numbers and the tag information {{{
tagLineNumbers = [] tagLineNumbers = []
tags = {} tags = {}
# }}}
# list (stack) of all currently active tags # initalize local auxiliary variables {{{
tagsStack = [] tagsStack = []
lineNumber = 0 lineNumber = 0
# }}}
# go through all the lines in the source and localize all python tags in it {{{
while 1: while 1:
# get next line # get next line
line = self.source.readline() line = self.source.readline()
@@ -195,57 +201,73 @@ class SimplePythonTagsParser(object):
break break
# }}} # }}}
# increase the line number
lineNumber += 1 lineNumber += 1
lineMatch = self.commentsIndentStripRE.match(line)
lineContents = lineMatch.group(2) # extract the line indentation characters and its content {{{
# class tag {{{ lineMatch = self.COMMENTS_INDENT_RE.match(line)
tagMatch = self.classRE.match(lineContents) 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): if (tagMatch):
currentTag = self.getPythonTag(tagsStack, lineNumber, lineMatch.group(1), currentTag = self.getPythonTag(tagsStack, lineNumber, lineMatch.group(1),
tagMatch.group(1), self.tagClassTypeDecidingMethod) tagMatch.group(1), self.tagClassTypeDecidingMethod)
tagLineNumbers.append(lineNumber) tagLineNumbers.append(lineNumber)
tags[lineNumber] = currentTag tags[lineNumber] = currentTag
# }}} # }}}
# function/method/none tag {{{ # }}}
# handle the function/method/none tag {{{
else: 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): if (tagMatch):
currentTag = self.getPythonTag(tagsStack, lineNumber, lineMatch.group(1), currentTag = self.getPythonTag(tagsStack, lineNumber, lineMatch.group(1),
tagMatch.group(1), self.tagFunctionTypeDecidingMethod) tagMatch.group(1), self.tagFunctionTypeDecidingMethod)
tagLineNumbers.append(lineNumber) tagLineNumbers.append(lineNumber)
tags[lineNumber] = currentTag tags[lineNumber] = currentTag
# }}} # }}}
# }}}
# }}}
# return the tags data for the source # return the tags data for the source
return (tagLineNumbers, tags,) return (tagLineNumbers, tags,)
# }}} # }}}
def getPreviousTag(self, tagsStack): def getParentTag(self, tagsStack):
# DOC {{{ # DOC {{{
"""Returns the previous tag (instance of PythonTag()) from the """Returns the parent/enclosing tag (instance of PythonTag()) from the
specified tag list if possible. If not, returns None. specified tag list. If no such parent tag exists, returns None.
Parameters Parameters
tagsStack -- list (stack) of currently active PythonTag() instances tagsStack -- list (stack) of currently open PythonTag() instances
""" """
# }}} # }}}
# CODE {{{ # CODE {{{
# determine the parent tag {{{
if (len(tagsStack)): if (len(tagsStack)):
previousTag = tagsStack[-1] parentTag = tagsStack[-1]
else: else:
previousTag = None parentTag = None
# }}}
# return the tag # return the tag
return previousTag return parentTag
# }}} # }}}
def computeIndentLevel(self, indentChars): def computeIndentationLevel(indentChars):
# DOC {{{ # DOC {{{
"""Computes indent level from the specified string. """Computes the indentation level from the specified string.
Parameters Parameters
@@ -254,15 +276,21 @@ class SimplePythonTagsParser(object):
# }}} # }}}
# CODE {{{ # CODE {{{
# initialize the indentation level
indentLevel = 0 indentLevel = 0
# compute the indentation level (expand tabs) {{{
for char in indentChars: for char in indentChars:
if (char == '\t'): if (char == '\t'):
indentLevel += self.TABSIZE indentLevel += SimplePythonTagsParser.TABSIZE
else: else:
indentLevel += 1 indentLevel += 1
# }}}
# return the computed indentation level
return indentLevel return indentLevel
# }}} # }}}
computeIndentationLevel = staticmethod(computeIndentationLevel)
def getPythonTag(self, tagsStack, lineNumber, indentChars, tagName, tagTypeDecidingMethod): def getPythonTag(self, tagsStack, lineNumber, indentChars, tagName, tagTypeDecidingMethod):
@@ -282,58 +310,81 @@ 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 method that is called to
determine type of the current tag determine the type of the current tag
""" """
# }}} # }}}
# CODE {{{ # CODE {{{
indentLevel = self.computeIndentLevel(indentChars) # compute the indentation level
previousTag = self.getPreviousTag(tagsStack) indentLevel = self.computeIndentationLevel(indentChars)
# code for enclosed tag {{{ # get the parent tag
while (previousTag): parentTag = self.getParentTag(tagsStack)
if (previousTag.indentLevel >= indentLevel):
# 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] del tagsStack[-1]
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)
# }}} # }}}
# code for tag in top indent level {{{ # otherwise we have all information on the current tag and can return it {{{
else: else:
tagType = tagTypeDecidingMethod(None) # create the tag
tag = PythonTag(tagType, tagName, tagName, lineNumber, indentLevel) tag = PythonTag(tagTypeDecidingMethod(parentTag.type), tagName, "%s.%s" % (parentTag.fullName, tagName,), lineNumber, indentLevel)
tagsStack.append(tag)
return tag # break the loop
break
# }}} # }}}
# use parent tag of the parent tag
parentTag = self.getParentTag(tagsStack)
# }}}
# handle a top-indent level tag {{{
else:
# 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 {{{ # DOC {{{
"""Returns tag type of the current tag based on its previous tag (super """Returns tag type of the current tag based on its previous tag (super
tag) for classes. tag) for classes.
Parameters
parentTagType -- type of the enclosing/parent tag
""" """
# }}} # }}}
# CODE {{{ # CODE {{{
return PythonTag.TAGTYPE_CLASS # is always class no matter what
return PythonTag.TT_CLASS
# }}} # }}}
def tagFunctionTypeDecidingMethod(self, previousTagsType): def tagFunctionTypeDecidingMethod(self, parentTagType):
# DOC {{{ # DOC {{{
"""Returns tag type of the current tag based on its previous tag (super """Returns tag type of the current tag based on its previous tag (super
tag) for functions/methods. tag) for functions/methods.
Parameters
parentTagType -- type of the enclosing/parent tag
""" """
# }}} # }}}
# CODE {{{ # CODE {{{
if (previousTagsType == PythonTag.TAGTYPE_CLASS): if (parentTagType == PythonTag.TT_CLASS):
return PythonTag.TAGTYPE_METHOD return PythonTag.TT_METHOD
else: else:
return PythonTag.TAGTYPE_FUNCTION return PythonTag.TT_FUNCTION
# }}} # }}}
@@ -354,7 +405,7 @@ class VimReadlineBuffer(object):
def __init__(self, vimBuffer): def __init__(self, vimBuffer):
# DOC {{{ # DOC {{{
"""Initializes the instance of class VimReadlineBuffer(). """Initializes instances of VimReadlineBuffer().
Parameters Parameters
@@ -363,10 +414,14 @@ class VimReadlineBuffer(object):
# }}} # }}}
# CODE {{{ # CODE {{{
# remember the settings
self.vimBuffer = vimBuffer self.vimBuffer = vimBuffer
# initialize instance attributes {{{
self.currentLine = -1 self.currentLine = -1
self.bufferLines = len(vimBuffer) self.bufferLines = len(vimBuffer)
# }}} # }}}
# }}}
def readline(self): def readline(self):
@@ -377,6 +432,7 @@ class VimReadlineBuffer(object):
# }}} # }}}
# CODE {{{ # CODE {{{
# increase the current line counter
self.currentLine += 1 self.currentLine += 1
# notify end of file if we reached beyond the last line {{{ # notify end of file if we reached beyond the last line {{{
@@ -384,18 +440,19 @@ class VimReadlineBuffer(object):
return '' 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],) return "%s\n" % (self.vimBuffer[self.currentLine],)
# }}} # }}}
# }}} # }}}
# }}} # }}}
def getNearestLineIndex(row, tagLineNumbers): def getNearestLineIndex(row, tagLineNumbers):
# DOC {{{ # DOC {{{
"""Returns index of line in tagLineNumbers list that is nearest to the """Returns the index of line in 'tagLineNumbers' list that is nearest to the
current cursor row. specified cursor row.
Parameters Parameters
@@ -406,27 +463,33 @@ def getNearestLineIndex(row, tagLineNumbers):
# }}} # }}}
# CODE {{{ # CODE {{{
# initialize local auxiliary variables {{{
nearestLineNumber = -1 nearestLineNumber = -1
nearestLineIndex = -1 nearestLineIndex = -1
i = 0 # }}}
for lineNumber in tagLineNumbers:
# 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 the current line is nearer the current cursor position, take it {{{
if (nearestLineNumber < lineNumber <= row): if (nearestLineNumber < lineNumber <= row):
nearestLineNumber = lineNumber nearestLineNumber = lineNumber
nearestLineIndex = i nearestLineIndex = lineIndex
# }}} # }}}
# if we've got past the current cursor position, let's end the search {{{ # if we've got past the current cursor position, let's end the search {{{
if (lineNumber >= row): if (lineNumber >= row):
break break
# }}} # }}}
i += 1 # }}}
# return 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 tuple """Reads the tags for the specified buffer number. Returns a tuple
(taglinenumber[buffer], tags[buffer],). (taglinenumber[buffer], tags[buffer],).
Parameters Parameters
@@ -439,10 +502,11 @@ def getTags(bufferNumber, changedTick):
# }}} # }}}
# CODE {{{ # CODE {{{
# define global variables
global TAGLINENUMBERS, TAGS, BUFFERTICKS global TAGLINENUMBERS, TAGS, BUFFERTICKS
# return immediately if there's no need to update the tags {{{ # 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],) return (TAGLINENUMBERS[bufferNumber], TAGS[bufferNumber],)
# }}} # }}}
@@ -457,7 +521,7 @@ def getTags(bufferNumber, changedTick):
BUFFERTICKS[bufferNumber] = changedTick BUFFERTICKS[bufferNumber] = changedTick
# }}} # }}}
# return the tags data # return the tuple (tagLineNumbers, tags,)
return (tagLineNumbers, tags,) return (tagLineNumbers, tags,)
# }}} # }}}
@@ -476,12 +540,12 @@ def findTag(bufferNumber, changedTick):
# }}} # }}}
# CODE {{{ # CODE {{{
# try to find the best tag {{{
try: try:
# get the tags data for the current buffer {{{ # get the tags data for the current buffer
tagLineNumbers, tags = getTags(bufferNumber, changedTick) tagLineNumbers, tags = getTags(bufferNumber, changedTick)
# }}}
# link to vim 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
@@ -489,66 +553,97 @@ def findTag(bufferNumber, changedTick):
# 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 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 # (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 # to do with the tag, because it's indented differently, in such case no
# appropriate tag has been found.) # appropriate tag has been found.)
if (nearestLineIndex > -1): while (nearestLineIndex > -1):
# 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 range (nearestTagLine, cursorRow) {{{
for i in xrange(nearestLineNumber + 1, row): for lineNumber in xrange(nearestLineNumber + 1, row):
line = currentBuffer[i] # get the current line
# count the indentation of the line, if it's lower that the tag's, the found tag is wrong {{{ line = currentBuffer[lineNumber]
# count the indentation of the line, if it's lower than the tag's, the tag is invalid {{{
if (len(line)): if (len(line)):
# compute the indentation of the line {{{ # initialize local auxiliary variables {{{
lineStart = 0 lineStart = 0
j = 0 i = 0
while ((j < len(line)) and (line[j].isspace())): # }}}
if (line[j] == '\t'):
# compute the indentation of the line {{{
while ((i < len(line)) and (line[i].isspace())):
# move the start of the line code {{{
if (line[i] == '\t'):
lineStart += SimplePythonTagsParser.TABSIZE lineStart += SimplePythonTagsParser.TABSIZE
else: else:
lineStart += 1 lineStart += 1
j += 1 # }}}
# if the line contains only spaces, it doesn't count {{{
if (j == len(line)): # go to the next character on the line
i += 1
# }}}
# if the line contains only spaces, skip it {{{
if (i == len(line)):
continue continue
# }}} # }}}
# if the next character is # (python comment), this line doesn't count {{{ # if the next character is a '#' (python comment), skip the line {{{
if (line[j] == '#'): if (line[i] == '#'):
continue continue
# }}} # }}}
# }}}
# if the line's indentation starts before or at the nearest tag's one, the tag is wrong {{{ # if the line's indentation starts before or at the nearest tag's one, the tag is invalid {{{
if (lineStart <= tags[nearestLineNumber].indentLevel): if (lineStart <= tags[nearestLineNumber].indentLevel):
nearestLineNumber = -1 nearestLineIndex -= 1
break break
# }}} # }}}
# }}} # }}}
# }}} # }}}
# the tag is appropriate, so use it {{{
else:
break
# }}}
# }}}
# no appropriate tag has been found {{{
else: else:
nearestLineNumber = -1 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 = "" tagDescription = ""
# if an appropriate tag has been found, set the description accordingly {{{
if (nearestLineNumber > -1): if (nearestLineNumber > -1):
tagInfo = tags[nearestLineNumber] 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,)) 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() 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 {{{
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)
# }}} # }}}
# }}} # }}}
# }}}
def deleteTags(bufferNumber): def deleteTags(bufferNumber):
@@ -562,8 +657,10 @@ def deleteTags(bufferNumber):
# }}} # }}}
# CODE {{{ # CODE {{{
# define global variables
global TAGS, TAGLINENUMBERS, BUFFERTICKS global TAGS, TAGLINENUMBERS, BUFFERTICKS
# try to delete the tags for the buffer {{{
try: try:
del TAGS[bufferNumber] del TAGS[bufferNumber]
del TAGLINENUMBERS[bufferNumber] del TAGLINENUMBERS[bufferNumber]
@@ -571,6 +668,7 @@ def deleteTags(bufferNumber):
except: except:
pass pass
# }}} # }}}
# }}}
EOS EOS
@@ -609,6 +707,36 @@ function! TagInStatusLine()
endif endif
endfunction 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
" }}} " }}}