1
0
mirror of https://github.com/gryf/pythonhelper.git synced 2026-04-25 07:01:25 +02:00

4 Commits

Author SHA1 Message Date
Michal Vitecek ab9d0e5e69 Version 0.81: - fixed a small bug in indent level recognition -
Michal Vitecek f5b70c33bb Version 0.80
- removed the dependency on exuberant ctags which parsed the python source code wrongly anyways. From now on only VIM with python support is needed. This might greatly help windoze users.
-
Michal Vitecek 91b230cb51 Version 0.72
- fixed problem with parsing ctags output on python files that use tabs
- when there is a syntax error in the file and ctags parses it incorrectly a warning is displayed in the command line
-
Michal Vitecek 9f5c2d1dc2 Version 0.71
- fixed problem with undefined window-bound variable w:PHStatusLine when a window has been split into two.
- unbound event BufWinEnter because it's not needed because of the above change now
-
+396 -118
View File
@@ -1,57 +1,46 @@
" File: pythonhelper.vim " File: pythonhelper.vim
" Author: Michal Vitecek <fuf-at-mageo-dot-cz> " Author: Michal Vitecek <fuf-at-mageo-dot-cz>
" Version: 0.7 " Version: 0.81
" Last Modified: Oct 2, 2002 " Last Modified: Oct 24, 2002
" "
" Overview " Overview
" -------- " --------
" Vim script to help moving around in larger Python source files. It displays " Vim script to help moving around in larger Python source files. It displays
" current class, method or function the cursor is placed in in the status " current class, method or function the cursor is placed in on the status
" line for every python file. It's more clever than Yegappan Lakshmanan's " line for every python file. It's more clever than Yegappan Lakshmanan's
" taglist.vim because it takes into account indetation and comments to " taglist.vim because it takes into account indetation and comments to
" determine what tag the cursor is placed in. " determine what tag the cursor is placed in.
" "
" Requirements " Requirements
" ------------ " ------------
" This script needs VIM compiled with Python interpreter and relies on " This script needs only VIM compiled with Python interpreter. It doesn't rely
" exuberant ctags utility to generate the tag listing. You can determine " on exuberant ctags utility. You can determine whether your VIM has Python
" whether your VIM has Python support by issuing command :ver and looking for " support by issuing command :ver and looking for +python in the list of
" +python in the list of features. " features.
" "
" The exuberant ctags can be downloaded from http://ctags.sourceforge.net/ and " Note: The script displays current tag on the status line only in NORMAL
" should be reasonably new version (tested with 5.3). " mode. This is because CursorHold event is fired up only in this mode.
" " However if you badly need to know what tag you are in even in INSERT or
" Note: The script doesn't display current tag on the status line only in
" NORMAL mode. This is because CursorHold event is fired up only in this mode.
" However if you badly need to know what tag you are on even in INSERT or
" VISUAL mode, contact me on the above specified email address and I'll send " VISUAL mode, contact me on the above specified email address and I'll send
" you patch that enables it. " you a patch that enables firing up CursorHold event in those modes as well.
" "
" Installation " Installation
" ------------ " ------------
" 1. Make sure your Vim has python feature on (+python). If not, you will need " 1. Make sure your Vim has python feature on (+python). If not, you will need
" to recompile it with --with-pythoninterp option to the configure script " to recompile it with --with-pythoninterp option to the configure script
" 2. Copy script pythonhelper.vim to the $HOME/.vim/plugin directory " 2. Copy script pythonhelper.vim to the $HOME/.vim/plugin directory
" 3. Edit the script and modify the location of your exuberant tags utility " 3. Run Vim and open any python file.
" (variable CTAGS_PROGRAM).
" 4. Run Vim and open any python file.
" "
python << EOS python << EOS
# import of required modules {{{ # import of required modules {{{
import vim import vim
import os
import popen2
import time import time
import sys import sys
import re
# }}} # }}}
# CTAGS program and parameters {{{
CTAGS_PROGRAM = "/usr/local/bin/ctags"
CTAGS_PARAMETERS = "--language-force=python --format=2 --sort=0 --fields=+nK -L - -f - "
# }}}
# global dictionaries of tags and their line numbers, keys are buffer numbers {{{ # global dictionaries of tags and their line numbers, keys are buffer numbers {{{
TAGS = {} TAGS = {}
TAGLINENUMBERS = {} TAGLINENUMBERS = {}
@@ -59,6 +48,350 @@ BUFFERTICKS = {}
# }}} # }}}
# class PythonTag() {{{
class PythonTag(object):
# DOC {{{
"""A simple storage class representing a python tag.
"""
# }}}
# STATIC VARIABLES {{{
# tag type IDs {{{
TAGTYPE_CLASS = 0
TAGTYPE_METHOD = 1
TAGTYPE_FUNCTION = 2
# }}}
# tag type names {{{
typeName = {
TAGTYPE_CLASS : "class",
TAGTYPE_METHOD : "method",
TAGTYPE_FUNCTION : "function",
}
# }}}
# }}}
# METHODS {{{
def __init__(self, type, name, fullName, lineNumber, indentLevel):
# DOC {{{
"""Initializes instances of class PythonTag().
Parameters
type -- tag type
name -- short tag name
fullName -- full tag name (in dotted notation)
lineNumber -- line number on which the tag starts
indentLevel -- indentation level of the tag
"""
# }}}
# CODE {{{
self.type = type
self.name = name
self.fullName = fullName
self.lineNumber = lineNumber
self.indentLevel = indentLevel
# }}}
def __str__(self):
# DOC {{{
"""Returns a string representation of the tag.
"""
# }}}
# CODE {{{
return "%s (%s) [%s, %u, %u]" % (self.name, PythonTag.typeName[self.type],
self.fullName, self.lineNumber, self.indentLevel,)
# }}}
__repr__ = __str__
# }}}
# }}}
# class SimplePythonTagsParser() {{{
class SimplePythonTagsParser(object):
# DOC {{{
"""Provides a simple python tag parser. Returns list of PythonTag()
instances.
"""
# }}}
# STATIC VARIABLES {{{
# 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]+([^(]+).*')
# }}}
# METHODS {{{
def __init__(self, source):
# DOC {{{
"""Initializes the instance of class SimplePythonTagsParser().
Parameters
source -- source for which the tags will be generated. It must
provide callable method readline (i.e. as file objects do).
"""
# }}}
# CODE {{{
# make sure source has readline() method {{{
if (not(hasattr(source, 'readline') and
callable(source.readline))):
raise AttributeError("Source must have callable readline method.")
# }}}
# remember what the source is
self.source = source
# }}}
def getTags(self):
# DOC {{{
"""Determines all the tags for the buffer. Returns tuple in format
(tagLineNumbers, tags,).
"""
# }}}
# CODE {{{
tagLineNumbers = []
tags = {}
# list (stack) of all currently active tags
tagsStack = []
lineNumber = 0
while 1:
# get next line
line = self.source.readline()
# finish if this is the end of the source {{{
if (line == ''):
break
# }}}
lineNumber += 1
lineMatch = self.commentsIndentStripRE.match(line)
lineContents = lineMatch.group(2)
# class tag {{{
tagMatch = self.classRE.match(lineContents)
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 {{{
else:
tagMatch = self.methodRE.match(lineContents)
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):
# DOC {{{
"""Returns the previous tag (instance of PythonTag()) from the
specified tag list if possible. If not, returns None.
Parameters
tagsStack -- list (stack) of currently active PythonTag() instances
"""
# }}}
# CODE {{{
if (len(tagsStack)):
previousTag = tagsStack[-1]
else:
previousTag = None
# return the tag
return previousTag
# }}}
def computeIndentLevel(self, indentChars):
# DOC {{{
"""Computes indent level from the specified string.
Parameters
indentChars -- white space before any other character on line
"""
# }}}
# CODE {{{
indentLevel = 0
for char in indentChars:
if (char == '\t'):
indentLevel += self.TABSIZE
else:
indentLevel += 1
return indentLevel
# }}}
def getPythonTag(self, tagsStack, lineNumber, indentChars, tagName, tagTypeDecidingMethod):
# DOC {{{
"""Returns instance of PythonTag() based on the specified data.
Parameters
tagsStack -- list (stack) of tags currently active. Note: Modified
in this method!
lineNumber -- current line number
indentChars -- characters making up the indentation level of the
current tag
tagName -- short name of the current tag
tagTypeDecidingMethod -- reference to method that is called to
determine type of the current tag
"""
# }}}
# CODE {{{
indentLevel = self.computeIndentLevel(indentChars)
previousTag = self.getPreviousTag(tagsStack)
# code for enclosed tag {{{
while (previousTag):
if (previousTag.indentLevel >= indentLevel):
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 {{{
else:
tagType = tagTypeDecidingMethod(None)
tag = PythonTag(tagType, tagName, tagName, lineNumber, indentLevel)
tagsStack.append(tag)
return tag
# }}}
# }}}
def tagClassTypeDecidingMethod(self, previousTagsType):
# DOC {{{
"""Returns tag type of the current tag based on its previous tag (super
tag) for classes.
"""
# }}}
# CODE {{{
return PythonTag.TAGTYPE_CLASS
# }}}
def tagFunctionTypeDecidingMethod(self, previousTagsType):
# DOC {{{
"""Returns tag type of the current tag based on its previous tag (super
tag) for functions/methods.
"""
# }}}
# CODE {{{
if (previousTagsType == PythonTag.TAGTYPE_CLASS):
return PythonTag.TAGTYPE_METHOD
else:
return PythonTag.TAGTYPE_FUNCTION
# }}}
# }}}
# }}}
# class VimReadlineBuffer() {{{
class VimReadlineBuffer(object):
# DOC {{{
"""A simple wrapper class around vim's buffer that provides readline
method.
"""
# }}}
# METHODS {{{
def __init__(self, vimBuffer):
# DOC {{{
"""Initializes the instance of class VimReadlineBuffer().
Parameters
vimBuffer -- VIM's buffer
"""
# }}}
# CODE {{{
self.vimBuffer = vimBuffer
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 {{{
self.currentLine += 1
# notify end of file if we reached beyond the last line {{{
if (self.currentLine == self.bufferLines):
return ''
# }}}
# return the line with added newline (vim stores the lines without newline)
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 index of line in tagLineNumbers list that is nearest to the
@@ -93,9 +426,8 @@ def getNearestLineIndex(row, tagLineNumbers):
def getTags(bufferNumber, changedTick): def getTags(bufferNumber, changedTick):
# DOC {{{ # DOC {{{
"""Reads the tags for the specified buffer number. It does so by executing """Reads the tags for the specified buffer number. Returns tuple
the CTAGS program and parsing its output. Returns tuple (taglinenumber[buffer], tags[buffer],).
(taglinenumber[buffer], tags[buffer]).
Parameters Parameters
@@ -107,82 +439,16 @@ def getTags(bufferNumber, changedTick):
# }}} # }}}
# CODE {{{ # CODE {{{
global CTAGS_PROGRAM, CTAGS_PARAMETERS
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.has_key(bufferNumber)) and (BUFFERTICKS[bufferNumber] == changedTick)):
return (TAGLINENUMBERS[bufferNumber], TAGS[bufferNumber],) return (TAGLINENUMBERS[bufferNumber], TAGS[bufferNumber],)
# }}} # }}}
# read the tags and fill the global variables {{{ # get the tags {{{
currentBuffer = vim.current.buffer simpleTagsParser = SimplePythonTagsParser(VimReadlineBuffer(vim.current.buffer))
currentWindow = vim.current.window tagLineNumbers, tags = simpleTagsParser.getTags()
row, col = currentWindow.cursor
# create a temporary file with the current content of the buffer {{{
fileName = "/tmp/.%s.%u.ph" % (os.path.basename(currentBuffer.name), os.getpid(),)
f = open(fileName, "w")
for line in currentBuffer:
f.write(line)
f.write('\n')
f.close()
# }}}
# run ctags on it {{{
try:
ctagsOutPut, ctagsInPut = popen2.popen4("%s %s" % (CTAGS_PROGRAM, CTAGS_PARAMETERS,))
ctagsInPut.write(fileName + "\n")
ctagsInPut.close()
except:
os.unlink(fileName)
return
# }}}
# parse the ctags' output {{{
tagLineNumbers = []
tags = {}
while 1:
line = ctagsOutPut.readline()
# if empty line has been read, it's the end of the file {{{
if (line == ''):
break
# }}}
# if the line starts with !, then it's a comment line {{{
if (line[0] == '!'):
continue
# }}}
# split the line into parts and parse the data {{{
# the format is: [0]tagName [1]fileName [2]tagLine [3]tagType [4]tagLineNumber [[5]tagOwner]
tagData = line.split('\t')
name = tagData[0]
# get the tag's indentation {{{
start = 2
j = 2
while ((j < len(tagData[2])) and (tagData[2][j].isspace())):
if (tagData[2][j] == '\t'):
start += 8
else:
start += 1
j += 1
# }}}
type = tagData[3]
line = int(tagData[4][5:])
if (len(tagData) == 6):
owner = tagData[5].strip()
else:
owner = None
# }}}
tagLineNumbers.append(line)
tags[line] = (name, type, owner, start)
ctagsOutPut.close()
# }}}
# clean up the now unnecessary stuff {{{
os.unlink(fileName)
# }}} # }}}
# update the global variables {{{ # update the global variables {{{
@@ -190,9 +456,9 @@ def getTags(bufferNumber, changedTick):
TAGLINENUMBERS[bufferNumber] = tagLineNumbers TAGLINENUMBERS[bufferNumber] = tagLineNumbers
BUFFERTICKS[bufferNumber] = changedTick BUFFERTICKS[bufferNumber] = changedTick
# }}} # }}}
# }}}
return (TAGLINENUMBERS[bufferNumber], TAGS[bufferNumber],) # return the tags data
return (tagLineNumbers, tags,)
# }}} # }}}
@@ -211,8 +477,9 @@ def findTag(bufferNumber, changedTick):
# CODE {{{ # CODE {{{
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 internal data {{{
currentBuffer = vim.current.buffer currentBuffer = vim.current.buffer
@@ -238,7 +505,7 @@ def findTag(bufferNumber, changedTick):
j = 0 j = 0
while ((j < len(line)) and (line[j].isspace())): while ((j < len(line)) and (line[j].isspace())):
if (line[j] == '\t'): if (line[j] == '\t'):
lineStart += 8 lineStart += SimplePythonTagsParser.TABSIZE
else: else:
lineStart += 1 lineStart += 1
j += 1 j += 1
@@ -251,8 +518,8 @@ def findTag(bufferNumber, changedTick):
continue continue
# }}} # }}}
# }}} # }}}
# if the line's indentation starts before 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 wrong {{{
if (lineStart < tags[nearestLineNumber][3]): if (lineStart <= tags[nearestLineNumber].indentLevel):
nearestLineNumber = -1 nearestLineNumber = -1
break break
# }}} # }}}
@@ -266,15 +533,7 @@ def findTag(bufferNumber, changedTick):
tagDescription = "" tagDescription = ""
if (nearestLineNumber > -1): if (nearestLineNumber > -1):
tagInfo = tags[nearestLineNumber] tagInfo = tags[nearestLineNumber]
# use the owner if any exists {{{ tagDescription = "[in %s (%s)]" % (tagInfo.fullName, PythonTag.typeName[tagInfo.type],)
if (tagInfo[2] != None):
fullTagName = "%s.%s()" % (tagInfo[2].split(':')[1], tagInfo[0],)
# }}}
# otherwise use just the tag name {{{
else:
fullTagName = tagInfo[0]
# }}}
tagDescription = "[in %s (%s)]" % (fullTagName, tagInfo[1],)
# }}} # }}}
# update the variable for the status line so it will be updated next time # update the variable for the status line so it will be updated next time
@@ -303,7 +562,7 @@ def deleteTags(bufferNumber):
# }}} # }}}
# CODE {{{ # CODE {{{
global TAGS, TAGLINENUMBERS, BUFFERTICKS global TAGS, TAGLINENUMBERS, BUFFERTICKS
try: try:
del TAGS[bufferNumber] del TAGS[bufferNumber]
@@ -316,10 +575,11 @@ def deleteTags(bufferNumber):
EOS EOS
" 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 return
endif endif
@@ -331,15 +591,31 @@ endfunction
function! PHBufferDelete() function! PHBufferDelete()
" set PHStatusLine for this window to empty string
let w:PHStatusLine = ""
" call python function deleteTags() with the cur " call python function deleteTags() with the cur
execute 'python deleteTags(' . expand("<abuf>") . ')' execute 'python deleteTags(' . expand("<abuf>") . ')'
endfunction endfunction
function! TagInStatusLine()
" return value of w:PHStatusLine in case it's set
if (exists("w:PHStatusLine"))
return w:PHStatusLine
" otherwise just return empty string
else
return ""
endif
endfunction
" }}}
" event binding, vim customizing {{{
" autocommands binding " autocommands binding
autocmd CursorHold * silent call PHCursorHold() autocmd CursorHold * call PHCursorHold()
autocmd BufWinEnter * silent 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 " time that determines after how long time of no activity the CursorHold event
@@ -352,7 +628,9 @@ highlight User1 gui=bold guifg=cyan guibg=black
highlight User2 gui=bold guifg=black guibg=red highlight User2 gui=bold guifg=black guibg=red
" the status line will be displayed for every window " the status line will be displayed for every window
set laststatus=2 set laststatus=2
" set the status variable for the current window
let w:PHStatusLine = ''
" set the status line to display some useful information " set the status line to display some useful information
set stl=%-f%r\ %2*%m%*\ \ \ \ %1*%{w:PHStatusLine}%*%=[%l:%c]\ \ \ \ [buf\ %n] set stl=%-f%r\ %2*%m%*\ \ \ \ %1*%{TagInStatusLine()}%*%=[%l:%c]\ \ \ \ [buf\ %n]
" }}}
" vim:foldmethod=marker