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

46 Commits

Author SHA1 Message Date
gryf 4a65fbb8b1 Fixed wrong msg string concatenation 2016-12-11 10:23:09 +01:00
gryf 468b229276 Fix for selecting right interpreter for python3 enabled vim 2016-12-10 19:09:47 +01:00
gryf 154c343257 Updated README, added license file 2016-05-30 19:42:17 +02:00
gryf 81bdd2f51c Docstring cleanup, small refactoring, removed outdated docs 2016-05-30 19:35:41 +02:00
gryf 839012de28 Fixed bug regarding deleting not existed tags cache. 2016-05-25 08:32:54 +02:00
gryf c41a450990 Bugfix and move plugin to python specific ftplugin
Move to python specific plugin (ftplugin),
Fixed bug regarding empty buffer.
2016-05-25 08:27:36 +02:00
gryf 81a638762b Moved variable with script path out of function
Otherwise it will point to the current path, instead of script path.
Changing it to script variables will change that behaviour to desirable one.
2016-05-24 14:56:40 +02:00
gryf 3164a9e568 Make vim mock Python3 compatible 2016-05-23 21:18:17 +02:00
gryf 4f1dbc74bd Simplify implementation for tag search
Provide alternative implementation of searching for the right tag.
Also, make functionality testable.
2016-05-23 20:59:39 +02:00
gryf b2ca2a2fca Added tests for pythonhelper 2016-05-23 20:58:06 +02:00
gryf c011e488ce WIP: Rewrite parser 2016-05-21 19:48:12 +02:00
gryf d8a44e8439 Get rid of catch-everything exception mechanism.
It seams, that entire block for looking for tags has been closed between
try:except keywords, catching all the exceptions, which is not necessary just
for iterating all over the file looking for particular pattern. Remove it
until real problem appear on wild - for sure I will extensively test that ;).
2016-05-19 21:24:22 +02:00
gryf 03f3c9ef1e Unifying regexp for detecting class/function
Removing unnecessary functions for "detecting" context of the
class/function. Unifying regexp for detecting class/method/function,
get rid of excess iterator as a facade for vim buffer. Simplifying function
for gathering tags.
2016-05-19 21:14:07 +02:00
gryf c9e210a331 Get rid of globals 2016-05-19 06:48:31 +02:00
gryf 327dce4b7a Refactor PythonTag class 2016-05-18 21:22:21 +02:00
gryf 9809859c73 Enforce PEP8 on the Python code 2016-05-18 21:16:23 +02:00
gryf d40dbfe22b Separate vim and pthon scripts 2016-05-18 20:19:10 +02:00
gryf e37a64afc4 Removing clutter from python section 2016-05-17 18:38:49 +02:00
Oluf Lorenzen c6b824f536 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)
2012-02-13 05:57:37 -08:00
cheater 8f88e4bc7f Only set updatetime if g:pythonhelper_updatetime is set. 2012-02-12 14:27:54 +01:00
cheater 476707b687 Deleted stray comment. 2012-02-12 14:27:53 +01:00
cheater e1d3306adc You can now set the updatetime used by pythonhelper with g:pythonhelper_updatetime. 2012-02-12 14:27:51 +01:00
cheater 1e1d1cc6f1 Doc bug. 2012-02-12 14:27:50 +01:00
cheater 4a9a963dd6 Added the ability to separately access the tag name and tag type, via TagInStatusLineTag or TagInStatusLineType respectively. 2012-02-12 14:27:48 +01:00
cheater dcf5507d0f Doc bug. 2012-02-12 14:27:47 +01:00
cheater 1b61929c9a Doc bug. 2012-02-12 14:27:44 +01:00
cheater 0cccd82b2a Use a zipper instead of counting by hand. 2012-02-12 14:27:41 +01:00
cheater 702b07e390 Code style. 2012-02-12 14:27:39 +01:00
cheater a361304e73 Deleted non-iterator code. Refactoring complete. 2012-02-12 14:27:36 +01:00
cheater aee6f328aa Continue refactoring towards an iterator. 2012-02-12 14:27:34 +01:00
cheater ba115f64cc Initial iterator-based implementation. 2012-02-12 14:27:32 +01:00
cheater d6046886a3 Exceptions can tell you the same thing. 2012-02-12 14:27:31 +01:00
cheater f323d3cc74 Starting refactor towards an iterator. 2012-02-12 14:27:29 +01:00
cheater fe86a19e35 Added a FIXME for the 'except Exception'. 2012-02-12 14:27:27 +01:00
cheater c9dc1f3ec0 Lame use of exceptions. Never ever use an except block without qualifying the exception type, never ever catch exceptions from multiple sources at once. 2012-02-12 14:27:25 +01:00
cheater e70cca0a11 Corrected documentation. 2012-02-12 14:27:22 +01:00
cheater 005a35dcf6 More bad English in the doc. 2012-02-12 14:27:20 +01:00
cheater 42d72d9e6b More bad English in the doc. 2012-02-12 14:27:18 +01:00
cheater 6494b9de1c Tweaked installation instructions. 2012-02-12 14:27:11 +01:00
cheater 864d36fd02 Bad English in the installation instructions. 2012-02-12 14:26:58 +01:00
cheater 333db2d78e Yet more typos in the installation instructions 2012-02-12 14:26:55 +01:00
cheater 888970d931 More typos in the installation instructions 2012-02-12 14:26:52 +01:00
cheater 2e27597939 Code style. Tabs AND spaces? WTF. http://i.imgur.com/Lo5Vv.png 2012-02-12 14:26:47 +01:00
cheater 0ce33afc6c Typo in installation instructions. 2012-02-12 14:26:41 +01:00
Oluf Lorenzen 49d018fdc6 let user decide where/how to add the StatusLine
Signed-off-by: Oluf Lorenzen <ol@axxeo.de>
2010-12-17 14:57:15 +01:00
Michal Vitecek 2caa97d75f Version 0.83
Added support for the CursorHoldI event so that the class/method/function is recognized also in Insert mode.
-
8 changed files with 701 additions and 769 deletions
+24
View File
@@ -0,0 +1,24 @@
Copyright (c) 2016, Roman Dobosz at al.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the organization nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ROMAN DOBOSZ BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-5
View File
@@ -1,5 +0,0 @@
This is a mirror of http://www.vim.org/scripts/script.php?script_id=435
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 line for every python file. It's more clever than Yegappan Lakshmanan's taglist.vim because it takes into account indetation and comments to determine what tag the cursor is placed in and from version 0.80 doesn't need exuberant ctags utility.
Note: The script displays current tag on the status line only in NORMAL mode. This is because CursorHold event in VIM is fired up only in this mode. However if you'd like to know what tag you are in even in INSERT or VISUAL mode, contact me (email specified in the script) and I'll send you a patch that enables firing up CursorHold event in those modes as well.
+100
View File
@@ -0,0 +1,100 @@
Pythonhelper
============
This was a mirror of `http://www.vim.org/scripts/script.php?script_id=435`_.
This Vim plugin helps in Python development by placing a name of current class,
method or function under the cursor on the status line in normal and insert
mode.
Installation
------------
To use this plugin either ``+python`` or ``+python3`` feature compiled in vim is
required. To check it, issue ``:version`` in vim instance and look for python
entries.
To install it, any kind of Vim package manager can be used, like NeoBundle_,
Pathogen_, Vundle_ or vim-plug_.
For manual installation, copy subdirectories from this repository to your
``~/.vim`` directory.
Next, place one of the available functions either on your ``.vimrc``:
.. code:: vim
set statusline=[....]\ %{TagInStatusLine()}\ [.....]
or under ``~/.vim/ftplugin/python/your_file.vim``:
.. code:: vim
setlocal statusline=[....]\ %{TagInStatusLine()}\ [.....]
Functions, which may be placed on the status line are as follows:
* ``TagInStatusLine`` - shows name and type of the tag, i.e.:
.. code::
ClassName.some_method_name (method)
* ``TagInStatusLineTag`` - shows only the name:
.. code::
ClassName.some_method_name
* ``TagInStatusLineType`` - shows only the tag type:
.. code::
method
Restart vim, and you all set.
Changelog
---------
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Version | Date | Author | Notes |
+=========+============+================+===========================================================================================================================================================================================+
| 1.1 | 2016-12-10 | Roman Dobosz | Fixed python3 enabled vim (without python2) |
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 1.0 | 2016-05-30 | Roman Dobosz | Rewrite python part (simplifying the code, clean it up, separate from vimscript, add some tests), make it Python3 compatible, lots of other changes |
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | 2012-02-12 | cheater | `Several bug fixes`_, code cleanup, docs update |
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| | 2010-02-13 | Oluf Lorenzen | `Updated the way how to display information on status line`_ |
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 0.83 | 2010-01-04 | Michal Vitecek | Added support for the CursorHoldI event so that the class/method/function is recognized also in Insert mode |
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 0.82 | 2009-07-10 | Michal Vitecek | fixed a bug when nested functions/classes were not properly detected |
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 0.81 | 2003-03-13 | Michal Vitecek | fixed a small bug in indent level recognition |
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 0.80 | 2002-10-18 | Michal Vitecek | 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. |
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 0.72 | 2002-10-03 | Michal Vitecek | 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 |
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 0.71 | 2002-10-02 | Michal Vitecek | 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 |
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 0.70 | 2002-10-02 | Michal Vitecek | Initial upload |
+---------+------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
License
-------
Originally, there was no licence whatsoever, so I've put it under under 3-clause
BSD license. See LICENSE file for details.
.. _Pathogen: https://github.com/tpope/vim-pathogen
.. _Vundle: https://github.com/gmarik/Vundle.vim
.. _NeoBundle: https://github.com/Shougo/neobundle.vim
.. _vim-plug: https://github.com/junegunn/vim-plug
.. _http://www.vim.org/scripts/script.php?script_id=435: http://www.vim.org/scripts/script.php?script_id=435
.. _Updated the way how to display information on status line: https://github.com/Finkregh/pythonhelper/commit/49d018fdc638f759a4d3d89f97ba5d26baddb1cd
.. _Several bug fixes: https://github.com/Finkregh/pythonhelper/pull/2
+241
View File
@@ -0,0 +1,241 @@
"""
Simple analyzer for python source files. Collect and give info about file
structure: classes, its methods and functions.
Note, it'll probably not be behaving well with mixed spaces and tabs
indentation.
version: 1.0
date: 2016-05-30
author: Roman Dobosz <gryf@vimja.com>
"""
from collections import OrderedDict
import re
import vim
RE_TAG_TYPE = re.compile(r'\s*(def|class)[ \t]+([^(:]+).*')
RE_INDENT = re.compile(r'([ \t]*).*')
class PythonTag(object):
"""A simple storage class representing a python tag."""
def __init__(self, tag_type='', full_name='', line_number=0,
indent_level=0):
"""Initializes instances of Python tags.
:param tag_type: Tag type as string
:param full_name: Full tag name (in dotted notation)
:param line_number: line number on which the tag starts
:param indent_level: indentation level of the tag (number)
"""
self.tag_type = tag_type
self.name = full_name.split(".")[-1]
self.full_name = full_name
self.line_number = line_number
self.indent_level = indent_level
def __repr__(self):
"""Representation for the PythonTag objects"""
return ("<PythonTag object at %s: %0.2d [%d] %s %s>" %
(hex(id(self)),
self.line_number,
self.indent_level,
self.tag_type,
self.full_name))
class EvenSimplerPythonTagsParser(object):
"""Simplified version for Python source code tag parser."""
def get_tags(self):
"""
Find tag in current buffer. Store them in OrderedDict and return.
:returns: OrderedDict with tags for current buffer or empty
OrderedDict in case of no tags found.
"""
tags_stack = []
tags = OrderedDict()
for line_no, line in enumerate(vim.current.buffer):
tag_match = RE_TAG_TYPE.match(line)
if not tag_match:
continue
indent_level = self._get_indent_level(line)
for _ in range(len(tags_stack)):
if tags_stack and tags_stack[-1].indent_level >= indent_level:
tags_stack.pop()
if not tags_stack:
break
tag = PythonTag(tag_match.group(1),
self._get_full_name(tags_stack,
tag_match.group(2)),
line_no,
indent_level)
tag.tag_type = self._get_tag_type(tag, tags_stack)
tags[line_no] = tag
tags_stack.append(tag)
return tags
def _get_tag_type(self, tag, tags_stack):
"""
Calculate name for provided tag.
:param tag: PythonTag object
:param tags_stack: list of PythonTag objects ordered from root to leaf
:returns: 'class', 'method' or 'function' as a tag type
"""
if tag.tag_type == 'class':
return 'class'
if tags_stack and tags_stack[-1].tag_type == 'class':
return 'method'
return 'function'
def _get_full_name(self, tags_stack, name):
"""
Return full logical name dot separated starting from upper entity
:param tags_stack: list of PythonTag objects ordered from root to leaf
:param name: class, method or function name
:returns: full name starting from root, separated by dot, like:
function_name
ClassName
ClassName.method_name
ClassName.method_name.inner_function_name
"""
if tags_stack:
return tags_stack[-1].full_name + "." + name
return name
def _get_indent_level(self, line):
"""
Calculate and get the indentation level for provided line
:param line: a string, against which indentation should be calculated
:returns: counted number of whitespaces
"""
return len(RE_INDENT.match(line).group(1))
class PythonHelper(object):
TAGS = {}
@classmethod
def find_tag(cls, buffer_number, changed_tick):
"""
Tries to find the best tag for the current cursor position.
:param buffer_number: buffer number in vim
:param changed_tick: always-increasing number used to indicate that
the buffer has been modified since the last time
"""
tag = PythonHelper._get_tag(buffer_number, changed_tick)
s_line = '%s (%s)' % (tag.full_name, tag.tag_type) if tag else ''
s_line_tag = tag.full_name if tag else ''
s_line_type = tag.tag_type if tag else ''
vim.command('let w:PHStatusLine="%s"' % s_line)
vim.command('let w:PHStatusLineTag="%s"' % s_line_tag)
vim.command('let w:PHStatusLineType="%s"' % s_line_type)
@classmethod
def _get_tag(cls, buffer_number, changed_tick):
"""
Get the nearest tag object or None.
:param buffer_number: buffer number in vim
:param changed_tick: always-increasing number used to indicate that
the buffer has been modified since the last time
:returns: PythonTag tag object or None
"""
if PythonHelper.TAGS.get(buffer_number) and \
PythonHelper.TAGS[buffer_number]['changed_tick'] == changed_tick:
tags = PythonHelper.TAGS[buffer_number]['tags']
else:
parser = EvenSimplerPythonTagsParser()
tags = parser.get_tags()
PythonHelper.TAGS['buffer_number'] = {'changed_tick': changed_tick,
'tags': tags}
# get line number of current cursor position from Vim's internal data.
# It is always a positive number, starts from 1. Let's decrease it by
# one, so that it will not confuse us while operating vim interface by
# python, where everything starts from 0.
line_number = vim.current.window.cursor[0] - 1
while True:
try:
line = vim.current.buffer[line_number]
except IndexError:
return None
line_indent = len(RE_INDENT.match(line).group(1))
if line.strip():
break
# line contains nothing but white characters, looking up to grab
# some more context
line_number -= 1
tag = tags.get(line_number)
# if we have something at the beginning of the line, just return it;
# it doesn't matter if it is the tag found there or not
if line_indent == 0 or tag:
return tag
# get nearest tag
for line_no in range(line_number - 1, 0, -1):
tag = tags.get(line_no)
line = vim.current.buffer[line_no]
upper_line_indent = len(RE_INDENT.match(line).group(1))
if tag and upper_line_indent < line_indent:
return tag
if not line.strip():
continue
if upper_line_indent == 0:
return None
if upper_line_indent >= line_indent:
continue
if tag and tag.indent_level >= line_indent:
tag = None
continue
return tag
@classmethod
def delete_tags(cls, buffer_number):
"""Removes tag data for the specified buffer number.
:param buffer_number: buffer number in vim
"""
try:
del PythonHelper.TAGS[buffer_number]
except KeyError:
# If we don't have tags for specified buffer, just pass
pass
+148
View File
@@ -0,0 +1,148 @@
" File: pythonhelper.vim
" Author: Michal Vitecek <fuf-at-mageo-dot-cz>
" Author: Roman Dobosz <gryf@vimja.com>
" Version: 1.1
" License: 3-clause BSD license
" Last Modified: 2016-12-10
" VIM functions {{{
let s:plugin_path = expand('<sfile>:p:h', 1)
function! s:SetPython(msg)
if !exists('g:_python')
if has('python')
let g:_python = {'exec': 'python', 'file': 'pyfile'}
elseif has('python3')
let g:_python = {'exec': 'python3', 'file': 'py3file'}
else
echohl WarningMsg|echomsg a:msg|echohl None
finish
endif
endif
endfunction
function! s:PHLoader()
if !exists('g:pythonhelper_py_loaded')
call s:SetPython("PythonHelper unavailable: "
\ . "requires Vim with Python support")
execute g:_python['file'] . ' ' . s:plugin_path . '/pythonhelper.py'
let g:pythonhelper_py_loaded = 1
else
echohl "already loaded"
endif
endfunction
function! PHCursorHold()
" only Python is supported {{{
if (!exists('b:current_syntax') || (b:current_syntax != 'python'))
let w:PHStatusLine = ''
let w:PHStatusLineTag = ''
let w:PHStatusLineType = ''
return
endif
" }}}
" call Python function findTag() with the current buffer number and change
" status indicator
execute g:_python['exec'] . ' PythonHelper.find_tag(' . expand("<abuf>") .
\ ', ' . b:changedtick . ')'
endfunction
function! PHBufferDelete()
" set the PHStatusLine etc for this window to an empty string
let w:PHStatusLine = ""
let w:PHStatusLineTag = ''
let w:PHStatusLineType = ''
" call Python function deleteTags() with the current buffer number and
" change status indicator
execute g:_python['exec'] . ' PythonHelper.delete_tags(' . expand("<abuf>") . ')'
endfunction
function! TagInStatusLine()
" return value of w:PHStatusLine in case it's set
if (exists("w:PHStatusLine"))
return w:PHStatusLine
" 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
return ""
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
" }}}
" event binding, Vim customization {{{
" load Python code
call s:PHLoader()
" autocommands
autocmd CursorHold * call PHCursorHold()
autocmd CursorHoldI * call PHCursorHold()
autocmd BufDelete * silent call PHBufferDelete()
" period of no activity after which the CursorHold event is triggered
if (exists("g:pythonhelper_updatetime"))
let &updatetime = g:pythonhelper_updatetime
endif
" }}}
" vim:foldmethod=marker
+42
View File
@@ -0,0 +1,42 @@
import os
class Foo(object):
"""Some doc"""
CLASS_ATTR = {"dict": 1,
"bla": "foobar"}
def __init__(self, arg):
"""initializaion"""
self.arg = arg
def method(self, x, y):
"""very important method"""
def inner_funtion(x, y):
for i in y:
x = x + i
result = y[:]
result.append(x)
return result
result = None
result2 = """\
multiline
string
the
annoying
bastard"""
if self.arg < 100:
result = inner_funtion(x, y)
return result if result else result2
def main():
instance = Foo(10)
print(os.path.curdir, instance.method(2, [1, 2, 3]))
if __name__ == "__main__":
main()
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest
import sys
class Command(object):
def __call__(self, args):
print(args)
class Window(object):
def __init__(self):
self.cursor = (1, 1)
class Current(object):
def __init__(self):
self.buffer = []
self.window = Window()
class MockVim(object):
current = Current()
command = Command()
with open('test_py_example.py') as fobj:
BUFFER = fobj.read().split('\n')
sys.modules['vim'] = vim = MockVim
import pythonhelper
class TestTagsHelperWithEmptyBuffer(unittest.TestCase):
def setUp(self):
vim.current.buffer = []
def test_get_tag(self):
vim.current.window.cursor = (1, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 2)
self.assertIsNone(tag)
class TestTagsHelper(unittest.TestCase):
def setUp(self):
vim.current.buffer = BUFFER
def test_import(self):
vim.current.window.cursor = (1, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 2)
self.assertIsNone(tag)
def test_class(self):
vim.current.window.cursor = (4, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'class')
vim.current.window.cursor = (6, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'class')
vim.current.window.cursor = (7, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'class')
def test_init(self):
vim.current.window.cursor = (9, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'method')
vim.current.window.cursor = (11, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'method')
def test_method(self):
vim.current.window.cursor = (13, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'method')
vim.current.window.cursor = (15, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'method')
vim.current.window.cursor = (24, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'method')
# those tests should run if we have some kind of lexer analiser, not a
# simple py source parser.
#
# vim.current.window.cursor = (32, 1)
# tag = pythonhelper.PythonHelper._get_tag(1, 3)
# self.assertEqual(tag.tag_type, 'method')
# vim.current.window.cursor = (34, 1)
# tag = pythonhelper.PythonHelper._get_tag(1, 3)
# self.assertEqual(tag.tag_type, 'method')
def test_inner_function(self):
vim.current.window.cursor = (16, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'function')
vim.current.window.cursor = (18, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'function')
vim.current.window.cursor = (22, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'function')
vim.current.window.cursor = (23, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'function')
def test_main(self):
vim.current.window.cursor = (37, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'function')
vim.current.window.cursor = (38, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'function')
vim.current.window.cursor = (40, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertEqual(tag.tag_type, 'function')
def test_ifmain(self):
vim.current.window.cursor = (41, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertIsNone(tag)
vim.current.window.cursor = (42, 1)
tag = pythonhelper.PythonHelper._get_tag(1, 3)
self.assertIsNone(tag)
if __name__ == "__main__":
unittest.main()
-764
View File
@@ -1,764 +0,0 @@
" File: pythonhelper.vim
" Author: Michal Vitecek <fuf-at-mageo-dot-cz>
" Version: 0.82
" Last Modified: Jul 10, 2009
"
" Overview
" --------
" Vim script to help moving around in larger Python source files. It displays
" 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
" taglist.vim because it takes into account indetation and comments to
" determine what tag the cursor is placed in.
"
" Requirements
" ------------
" This script needs only VIM compiled with Python interpreter. It doesn't rely
" on exuberant ctags utility. You can determine whether your VIM has Python
" support by issuing command :ver and looking for +python in the list of
" features.
"
" Note: The script displays 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 in even in INSERT or
" VISUAL mode, contact me on the above specified email address and I'll send
" you a patch that enables firing up CursorHold event in those modes as well.
"
" Installation
" ------------
" 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
" 2. Copy script pythonhelper.vim to the $HOME/.vim/plugin directory
" 3. Run Vim and open any python file.
"
python << EOS
# import of required modules {{{
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 = {}
# }}}
# class PythonTag() {{{
class PythonTag(object):
# DOC {{{
"""A simple storage class representing a python tag.
"""
# }}}
# STATIC VARIABLES {{{
# possible tag types {{{
TT_CLASS = 0
TT_METHOD = 1
TT_FUNCTION = 2
# }}}
# tag type names {{{
TAG_TYPE_NAME = {
TT_CLASS : "class",
TT_METHOD : "method",
TT_FUNCTION : "function",
}
# }}}
# }}}
# METHODS {{{
def __init__(self, type, name, fullName, lineNumber, indentLevel):
# DOC {{{
"""Initializes instances of 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 {{{
# remember the settings {{{
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.TAG_TYPE_NAME[self.type],
self.fullName, self.lineNumber, self.indentLevel,)
# }}}
__repr__ = __str__
# }}}
# }}}
# class SimplePythonTagsParser() {{{
class SimplePythonTagsParser(object):
# DOC {{{
"""Provides a simple python tag parser.
"""
# }}}
# STATIC VARIABLES {{{
# how many chars a single tab represents (visually)
TABSIZE = 8
# 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]+([^(]+).*')
# }}}
# METHODS {{{
def __init__(self, source):
# DOC {{{
"""Initializes instances of 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 ((hasattr(source, 'readline') == 0) or
(callable(source.readline) == 0)):
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 a tuple in format
(tagLineNumbers, tags,).
"""
# }}}
# CODE {{{
# initialize the resulting list of the tag line numbers and the tag information {{{
tagLineNumbers = []
tags = {}
# }}}
# initalize local auxiliary variables {{{
tagsStack = []
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()
# finish if this is the end of the source {{{
if (line == ''):
break
# }}}
# increase the line number
lineNumber += 1
# 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
# }}}
# }}}
# handle the function/method/none tag {{{
else:
# 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 getParentTag(self, tagsStack):
# DOC {{{
"""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 open PythonTag() instances
"""
# }}}
# CODE {{{
# determine the parent tag {{{
if (len(tagsStack)):
parentTag = tagsStack[-1]
else:
parentTag = None
# }}}
# return the tag
return parentTag
# }}}
def computeIndentationLevel(indentChars):
# DOC {{{
"""Computes the indentation level from the specified string.
Parameters
indentChars -- white space before any other character on line
"""
# }}}
# CODE {{{
# initialize the indentation level
indentLevel = 0
# compute the indentation level (expand tabs) {{{
for char in indentChars:
if (char == '\t'):
indentLevel += SimplePythonTagsParser.TABSIZE
else:
indentLevel += 1
# }}}
# return the computed indentation level
return indentLevel
# }}}
computeIndentationLevel = staticmethod(computeIndentationLevel)
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 the type of the current tag
"""
# }}}
# CODE {{{
# 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:
# 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)
# }}}
# 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, 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 {{{
# is always class no matter what
return PythonTag.TT_CLASS
# }}}
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 (parentTagType == PythonTag.TT_CLASS):
return PythonTag.TT_METHOD
else:
return PythonTag.TT_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 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):
# DOC {{{
"""Returns the index of line in 'tagLineNumbers' list that is nearest to the
specified cursor row.
Parameters
row -- current cursor row
tagLineNumbers -- list of tags' line numbers (ie. their position)
"""
# }}}
# CODE {{{
# 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 = lineIndex
# }}}
# if we've got past the current cursor position, let's end the search {{{
if (lineNumber >= row):
break
# }}}
# }}}
# 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 a tuple
(taglinenumber[buffer], tags[buffer],).
Parameters
bufferNumber -- number of the current buffer
changedTick -- ever increasing number used to tell if the buffer has
been modified since the last time
"""
# }}}
# CODE {{{
# define global variables
global TAGLINENUMBERS, TAGS, BUFFERTICKS
# return immediately if there's no need to update the tags {{{
if (BUFFERTICKS.get(bufferNumber, None) == changedTick):
return (TAGLINENUMBERS[bufferNumber], TAGS[bufferNumber],)
# }}}
# get the tags {{{
simpleTagsParser = SimplePythonTagsParser(VimReadlineBuffer(vim.current.buffer))
tagLineNumbers, tags = simpleTagsParser.getTags()
# }}}
# update the global variables {{{
TAGS[bufferNumber] = tags
TAGLINENUMBERS[bufferNumber] = tagLineNumbers
BUFFERTICKS[bufferNumber] = changedTick
# }}}
# return the tuple (tagLineNumbers, tags,)
return (tagLineNumbers, tags,)
# }}}
def findTag(bufferNumber, changedTick):
# DOC {{{
"""Tries to find the best tag for the current cursor position.
Parameters
bufferNumber -- number of the current buffer
changedTick -- ever increasing number used to tell if the buffer has
been modified since the last time
"""
# }}}
# CODE {{{
# try to find the best tag {{{
try:
# get the tags data for the current buffer
tagLineNumbers, tags = getTags(bufferNumber, changedTick)
# link to vim's internal data {{{
currentBuffer = vim.current.buffer
currentWindow = vim.current.window
row, col = currentWindow.cursor
# }}}
# 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.)
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 {{{
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 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
# }}}
# }}}
# }}}
# the tag is appropriate, so use it {{{
else:
break
# }}}
# }}}
# no appropriate tag has been found {{{
else:
nearestLineNumber = -1
# }}}
# 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.TAG_TYPE_NAME[tagInfo.type],)
# }}}
# }}}
# update the variable for the status line so it get updated with the new description
vim.command("let w:PHStatusLine=\"%s\"" % (tagDescription,))
# }}}
# 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):
# DOC {{{
"""Removes tags data for the specified buffer number.
Parameters
bufferNumber -- number of the buffer
"""
# }}}
# CODE {{{
# define global variables
global TAGS, TAGLINENUMBERS, BUFFERTICKS
# try to delete the tags for the buffer {{{
try:
del TAGS[bufferNumber]
del TAGLINENUMBERS[bufferNumber]
del BUFFERTICKS[bufferNumber]
except:
pass
# }}}
# }}}
EOS
" VIM functions {{{
function! PHCursorHold()
" only python is supported {{{
if (!exists('b:current_syntax') || (b:current_syntax != 'python'))
let w:PHStatusLine = ''
return
endif
" }}}
" call python function findTag() with the current buffer number and changed ticks
execute 'python findTag(' . expand("<abuf>") . ', ' . b:changedtick . ')'
endfunction
function! PHBufferDelete()
" set PHStatusLine for this window to empty string
let w:PHStatusLine = ""
" call python function deleteTags() with the cur
execute 'python deleteTags(' . expand("<abuf>") . ')'
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
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
" }}}
" event binding, vim customizing {{{
" autocommands binding
autocmd CursorHold * call PHCursorHold()
autocmd BufDelete * silent call PHBufferDelete()
" time that determines after how long time of no activity the CursorHold event
" is fired up
set updatetime=1000
" color of the current tag in the status line (bold cyan on black)
highlight User1 gui=bold guifg=cyan guibg=black
" color of the modified flag in the status line (bold black on red)
highlight User2 gui=bold guifg=black guibg=red
" the status line will be displayed for every window
set laststatus=2
" set the status line to display some useful information
set stl=%-f%r\ %2*%m%*\ \ \ \ %1*%{TagInStatusLine()}%*%=[%l:%c]\ \ \ \ [buf\ %n]
" }}}
" vim:foldmethod=marker