├── .gitignore ├── autoload ├── python_ftplugin │ └── misc │ │ ├── str.vim │ │ ├── complete.vim │ │ ├── escape.vim │ │ ├── list.vim │ │ ├── msg.vim │ │ ├── buffer.vim │ │ ├── open.vim │ │ ├── timer.vim │ │ ├── option.vim │ │ ├── os.vim │ │ └── path.vim └── python_ftplugin.vim ├── examples ├── folding.py └── type-inference.py ├── misc └── python-ftplugin │ ├── example.py │ ├── test_inference.py │ ├── support.py │ └── inference.py ├── README.md ├── ftplugin └── python.vim └── doc └── ft_python.txt /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /autoload/python_ftplugin/misc/str.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " String handling. 8 | " 9 | " Author: Peter Odding 10 | " Last Change: May 19, 2013 11 | " URL: http://peterodding.com/code/vim/misc/ 12 | 13 | function! python_ftplugin#misc#str#compact(s) 14 | " Compact whitespace in the string given as the first argument. 15 | return join(split(a:s), " ") 16 | endfunction 17 | 18 | function! python_ftplugin#misc#str#trim(s) 19 | " Trim all whitespace from the start and end of the string given as the 20 | " first argument. 21 | return substitute(a:s, '^\_s*\(.\{-}\)\_s*$', '\1', '') 22 | endfunction 23 | 24 | " vim: ts=2 sw=2 et 25 | -------------------------------------------------------------------------------- /examples/folding.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Examples of code for which the syntax based folding 3 | support should be able to generate good text folds 4 | (this is one of the lines that will be folded). 5 | ''' 6 | 7 | # Block 8 | # comment 9 | # on 10 | # multiple 11 | # lines. 12 | 13 | foo = 0 14 | 15 | def inline(): return 3 16 | 17 | def test2(): 18 | pass 19 | pass 20 | pass 21 | 22 | def test(test): 23 | """ 24 | This is the 25 | docstring! 26 | """ 27 | pass 28 | 29 | class Test: 30 | 31 | def foo(): 32 | ''' docstring for foo() ''' 33 | pass 34 | 35 | @foo(bar='foo') # foo needs foo as bar. 36 | @property 37 | def bar(): 38 | ''' 39 | docstring for bar() 40 | ''' 41 | pass 42 | 43 | @property 44 | def baz(bar='', 45 | baz=':', 46 | foo=None): 47 | ''' 48 | docstring for baz() 49 | ''' 50 | pass 51 | 52 | def gr(): 53 | pass 54 | 55 | -------------------------------------------------------------------------------- /misc/python-ftplugin/example.py: -------------------------------------------------------------------------------- 1 | global_var = 42 2 | var1, var2 = 1, 2 3 | some_string = '' 4 | shadowed = '' 5 | 6 | class ExampleClass: 7 | 8 | def __init__(self, init_arg, kw=[], kw_indirect=some_string): 9 | shadowed = [] 10 | print global_var, shadowed, kw_indirect, kw 11 | if init_arg: 12 | def fun_inside_if(): pass 13 | elif 1: 14 | def fun_inside_elif_1(): pass 15 | elif 2: 16 | def fun_inside_elif_2(): pass 17 | else: 18 | def fun_inside_else(): pass 19 | 20 | def a(self, example_argument): 21 | for i in xrange(5): 22 | print example_argument 23 | while True: 24 | def inside_while(): pass 25 | 26 | def b(self): 27 | def nested(): 28 | print 5 29 | nested() 30 | 31 | ExampleClass.b(normal_arg, kw_arg=5, *(1, 2), **{'key': 42}) 32 | ExampleClass.a('') 33 | 34 | def newlist(): 35 | return [] 36 | 37 | l = newlist() 38 | 39 | def newmaps(): 40 | return set(), dict() 41 | 42 | s, d = newmaps() 43 | 44 | if True: 45 | variant = [] 46 | else: 47 | variant = '' 48 | print variant 49 | 50 | def func(blaat): 51 | print dir(s2) 52 | 53 | func(set()) 54 | func(list()) 55 | -------------------------------------------------------------------------------- /autoload/python_ftplugin/misc/complete.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " Tab completion for user defined commands. 8 | " 9 | " Author: Peter Odding 10 | " Last Change: May 19, 2013 11 | " URL: http://peterodding.com/code/vim/misc/ 12 | 13 | function! python_ftplugin#misc#complete#keywords(arglead, cmdline, cursorpos) 14 | " This function can be used to perform keyword completion for user defined 15 | " Vim commands based on the contents of the current buffer. Here's an 16 | " example of how you would use it: 17 | " 18 | " :command -nargs=* -complete=customlist,python_ftplugin#misc#complete#keywords MyCmd call s:MyCmd() 19 | let words = {} 20 | for line in getline(1, '$') 21 | for word in split(line, '\W\+') 22 | let words[word] = 1 23 | endfor 24 | endfor 25 | return sort(keys(filter(words, 'v:key =~# a:arglead'))) 26 | endfunction 27 | 28 | " vim: ts=2 sw=2 et 29 | -------------------------------------------------------------------------------- /misc/python-ftplugin/test_inference.py: -------------------------------------------------------------------------------- 1 | from inference import TypeInferenceEngine 2 | 3 | def resolve_simplified(tie, node): 4 | nodes = [] 5 | for parent, kind, context in tie.resolve(node): 6 | nodes.append(tie.format(parent)) 7 | return nodes 8 | 9 | source = open('example.py').read() 10 | tie = TypeInferenceEngine(source) 11 | shadowed_var = tie.find_node(9, 5) 12 | global_var = tie.find_node(10, 12) 13 | kw_indirect = tie.find_node(10, 33) 14 | kw = tie.find_node(10, 46) 15 | init_arg = tie.find_node(11, 9) 16 | 17 | def test_resolving(): 18 | assert global_var.id == 'global_var' 19 | assert kw_indirect.id == 'kw_indirect' 20 | assert init_arg.id == 'init_arg' 21 | assert resolve_simplified(tie, global_var) == ['global_var=42'] 22 | assert resolve_simplified(tie, init_arg) == ['function __init__'] 23 | assert resolve_simplified(tie, kw_indirect) == ['function __init__'] 24 | assert sorted(resolve_simplified(tie, shadowed_var)) == sorted(["shadowed=''", "shadowed=[]"]) 25 | 26 | def test_evaluation(): 27 | assert list(tie.evaluate(global_var)) == [int] 28 | assert list(tie.evaluate(kw)) == [list] 29 | assert list(tie.evaluate(kw_indirect)) == [str] 30 | assert list(tie.evaluate(tie.find_node(37, 1))) == [list] 31 | assert list(tie.evaluate(tie.find_node(37, 1))) == [list] 32 | assert list(tie.evaluate(tie.find_node(42, 1))) == [set] 33 | assert list(tie.evaluate(tie.find_node(42, 4))) == [dict] 34 | 35 | def test_completion(): 36 | assert 'conjugate' in tie.complete(1, 1) 37 | assert 'keys' in tie.complete(42, 4) 38 | assert 'append' in tie.complete(48, 7) and 'capitalize' in tie.complete(47, 7) 39 | assert 'difference' in tie.complete(51, 13) 40 | assert 'test' in tie.complete(22, 13) 41 | -------------------------------------------------------------------------------- /autoload/python_ftplugin/misc/escape.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " String escaping functions. 8 | " 9 | " Author: Peter Odding 10 | " Last Change: May 19, 2013 11 | " URL: http://peterodding.com/code/vim/misc/ 12 | 13 | function! python_ftplugin#misc#escape#pattern(string) " {{{1 14 | " Takes a single string argument and converts it into a [:substitute] 15 | " [subcmd] / [substitute()] [subfun] pattern string that matches the given 16 | " string literally. 17 | " 18 | " [subfun]: http://vimdoc.sourceforge.net/htmldoc/eval.html#substitute() 19 | " [subcmd]: http://vimdoc.sourceforge.net/htmldoc/change.html#:substitute 20 | if type(a:string) == type('') 21 | let string = escape(a:string, '^$.*\~[]') 22 | return substitute(string, '\n', '\\n', 'g') 23 | endif 24 | return '' 25 | endfunction 26 | 27 | function! python_ftplugin#misc#escape#substitute(string) " {{{1 28 | " Takes a single string argument and converts it into a [:substitute] 29 | " [subcmd] / [substitute()] [subfun] replacement string that inserts the 30 | " given string literally. 31 | if type(a:string) == type('') 32 | let string = escape(a:string, '\&~%') 33 | return substitute(string, '\n', '\\r', 'g') 34 | endif 35 | return '' 36 | endfunction 37 | 38 | function! python_ftplugin#misc#escape#shell(string) " {{{1 39 | " Takes a single string argument and converts it into a quoted command line 40 | " argument. 41 | " 42 | " I was going to add a long rant here about Vim's ['shellslash' option] 43 | " [shellslash], but really, it won't make any difference. Let's just suffice 44 | " to say that I have yet to encounter a single person out there who uses 45 | " this option for its intended purpose (running a UNIX style shell on 46 | " Microsoft Windows). 47 | " 48 | " [shellslash]: http://vimdoc.sourceforge.net/htmldoc/options.html#'shellslash' 49 | if python_ftplugin#misc#os#is_win() 50 | try 51 | let ssl_save = &shellslash 52 | set noshellslash 53 | return shellescape(a:string) 54 | finally 55 | let &shellslash = ssl_save 56 | endtry 57 | else 58 | return shellescape(a:string) 59 | endif 60 | endfunction 61 | 62 | " vim: ts=2 sw=2 et 63 | -------------------------------------------------------------------------------- /autoload/python_ftplugin/misc/list.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " List handling functions. 8 | " 9 | " Author: Peter Odding 10 | " Last Change: May 19, 2013 11 | " URL: http://peterodding.com/code/vim/misc/ 12 | 13 | function! python_ftplugin#misc#list#unique(list) " {{{1 14 | " Remove duplicate values from the given list in-place (preserves order). 15 | call reverse(a:list) 16 | call filter(a:list, 'count(a:list, v:val) == 1') 17 | return reverse(a:list) 18 | endfunction 19 | 20 | function! python_ftplugin#misc#list#binsert(list, value, ...) " {{{1 21 | " Performs in-place binary insertion, which depending on your use case can 22 | " be more efficient than calling Vim's [sort()] [sort] function after each 23 | " insertion (in cases where a single, final sort is not an option). Expects 24 | " three arguments: 25 | " 26 | " 1. A list 27 | " 2. A value to insert 28 | " 3. 1 (true) when case should be ignored, 0 (false) otherwise 29 | " 30 | " [sort]: http://vimdoc.sourceforge.net/htmldoc/eval.html#sort() 31 | let idx = s:binsert_r(a:list, 0, len(a:list), a:value, exists('a:1') && a:1) 32 | return insert(a:list, a:value, idx) 33 | endfunction 34 | 35 | function! s:binsert_r(list, low, high, value, ignorecase) 36 | let mid = a:low + (a:high - a:low) / 2 37 | if a:low == a:high 38 | return a:low 39 | elseif a:ignorecase ? a:value >? a:list[mid] : a:value > a:list[mid] 40 | return s:binsert_r(a:list, mid + 1, a:high, a:value, a:ignorecase) 41 | elseif a:ignorecase ? a:value 10 | " Last Change: May 20, 2013 11 | " URL: http://peterodding.com/code/vim/misc/ 12 | 13 | if !exists('g:xolox_message_buffer') 14 | " For when I lose my :messages history :-\ 15 | let g:xolox_message_buffer = 100 16 | endif 17 | 18 | if !exists('g:xolox_messages') 19 | let g:xolox_messages = [] 20 | endif 21 | 22 | function! python_ftplugin#misc#msg#info(...) " {{{1 23 | " Show a formatted informational message to the user. This function has the 24 | " same argument handling as Vim's [printf()] [printf] function. 25 | " 26 | " [printf]: http://vimdoc.sourceforge.net/htmldoc/eval.html#printf() 27 | call s:show_message('title', a:000) 28 | endfunction 29 | 30 | function! python_ftplugin#misc#msg#warn(...) " {{{1 31 | " Show a formatted warning message to the user. This function has the same 32 | " argument handling as Vim's [printf()] [printf] function. 33 | call s:show_message('warningmsg', a:000) 34 | endfunction 35 | 36 | function! python_ftplugin#misc#msg#debug(...) " {{{1 37 | " Show a formatted debugging message to the user, if the user has enabled 38 | " increased verbosity by setting Vim's ['verbose'] [verbose] option to one 39 | " (1) or higher. This function has the same argument handling as Vim's 40 | " [printf()] [printf] function. 41 | if &vbs >= 1 42 | call s:show_message('question', a:000) 43 | endif 44 | endfunction 45 | 46 | function! s:show_message(hlgroup, args) " {{{1 47 | " The implementation of info() and warn(). 48 | let nargs = len(a:args) 49 | if nargs == 1 50 | let message = a:args[0] 51 | elseif nargs >= 2 52 | let message = call('printf', a:args) 53 | endif 54 | if exists('message') 55 | try 56 | " Temporarily disable Vim's |hit-enter| prompt and mode display. 57 | if !exists('s:more_save') 58 | let s:more_save = &more 59 | let s:ruler_save = &ruler 60 | let s:smd_save = &showmode 61 | endif 62 | set nomore noshowmode 63 | if winnr('$') == 1 | set noruler | endif 64 | augroup PluginXoloxHideMode 65 | autocmd! CursorHold,CursorHoldI * call s:clear_message() 66 | augroup END 67 | execute 'echohl' a:hlgroup 68 | " Redraw to avoid |hit-enter| prompt. 69 | redraw 70 | for line in split(message, "\n") 71 | echomsg line 72 | endfor 73 | if g:xolox_message_buffer > 0 74 | call add(g:xolox_messages, message) 75 | if len(g:xolox_messages) > g:xolox_message_buffer 76 | call remove(g:xolox_messages, 0) 77 | endif 78 | endif 79 | finally 80 | " Always clear message highlighting, even when interrupted by Ctrl-C. 81 | echohl none 82 | endtry 83 | endif 84 | endfunction 85 | 86 | function! s:clear_message() " {{{1 87 | " Callback to clear message after some time has passed. 88 | echo '' 89 | let &more = s:more_save 90 | let &showmode = s:smd_save 91 | let &ruler = s:ruler_save 92 | unlet s:more_save s:ruler_save s:smd_save 93 | autocmd! PluginXoloxHideMode 94 | augroup! PluginXoloxHideMode 95 | endfunction 96 | 97 | " vim: ts=2 sw=2 et 98 | -------------------------------------------------------------------------------- /autoload/python_ftplugin/misc/buffer.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " Handling of special buffers 8 | " 9 | " Author: Peter Odding 10 | " Last Change: May 19, 2013 11 | " URL: http://peterodding.com/code/vim/misc/ 12 | " 13 | " The functions defined here make it easier to deal with special Vim buffers 14 | " that contain text generated by a Vim plug-in. For example my [vim-notes 15 | " plug-in] [vim-notes] generates several such buffers: 16 | " 17 | " - [:RecentNotes] [RecentNotes] lists recently modified notes 18 | " - [:ShowTaggedNotes] [ShowTaggedNotes] lists notes grouped by tags 19 | " - etc. 20 | " 21 | " Because the text in these buffers is generated, Vim shouldn't bother with 22 | " swap files and it should never prompt the user whether to save changes to 23 | " the generated text. 24 | " 25 | " [vim-notes]: http://peterodding.com/code/vim/notes/ 26 | " [RecentNotes]: http://peterodding.com/code/vim/notes/#recentnotes_command 27 | " [ShowTaggedNotes]: http://peterodding.com/code/vim/notes/#showtaggednotes_command 28 | 29 | function! python_ftplugin#misc#buffer#is_empty() " {{{1 30 | " Checks if the current buffer is an empty, unchanged buffer which can be 31 | " reused. Returns 1 if an empty buffer is found, 0 otherwise. 32 | return !&modified && expand('%') == '' && line('$') <= 1 && getline(1) == '' 33 | endfunction 34 | 35 | function! python_ftplugin#misc#buffer#prepare(...) " {{{1 36 | " Open a special buffer, i.e. a buffer that will hold generated contents, 37 | " not directly edited by the user. The buffer can be customized by passing a 38 | " dictionary with the following key/value pairs as the first argument: 39 | " 40 | " - **name** (required): The base name of the buffer (i.e. the base name of 41 | " the file loaded in the buffer, even though it isn't really a file and 42 | " nothing is really 'loaded' :-) 43 | " - **path** (required): The pathname of the buffer. May be relevant if 44 | " [:lcd] [lcd] or ['autochdir'] [acd] is being used. 45 | " 46 | " [lcd]: http://vimdoc.sourceforge.net/htmldoc/editing.html#:lcd 47 | " [acd]: http://vimdoc.sourceforge.net/htmldoc/options.html#'autochdir' 48 | if a:0 == 1 && type(a:1) == type('') 49 | " Backwards compatibility with old interface. 50 | let options = {'name': a:1, 'path': a:1} 51 | elseif type(a:1) == type({}) 52 | let options = a:1 53 | else 54 | throw "Invalid arguments" 55 | endif 56 | let winnr = 1 57 | let found = 0 58 | for bufnr in tabpagebuflist() 59 | if python_ftplugin#misc#path#equals(options['path'], bufname(bufnr)) 60 | execute winnr . 'wincmd w' 61 | let found = 1 62 | break 63 | else 64 | let winnr += 1 65 | endif 66 | endfor 67 | if !(found || python_ftplugin#misc#buffer#is_empty()) 68 | vsplit 69 | endif 70 | silent execute 'edit' fnameescape(options['path']) 71 | lcd " clear working directory 72 | setlocal buftype=nofile bufhidden=hide noswapfile 73 | let &l:statusline = '[' . options['name'] . ']' 74 | call python_ftplugin#misc#buffer#unlock() 75 | silent %delete 76 | endfunction 77 | 78 | function! python_ftplugin#misc#buffer#lock() " {{{1 79 | " Lock a special buffer so that its contents can no longer be edited. 80 | setlocal readonly nomodifiable nomodified 81 | endfunction 82 | 83 | function! python_ftplugin#misc#buffer#unlock() " {{{1 84 | " Unlock a special buffer so that its content can be updated. 85 | setlocal noreadonly modifiable 86 | endfunction 87 | -------------------------------------------------------------------------------- /misc/python-ftplugin/support.py: -------------------------------------------------------------------------------- 1 | # Supporting functions for the Python file type plug-in for Vim. 2 | # Authors: 3 | # - Peter Odding 4 | # - Bart Kroon 5 | # Last Change: October 9, 2011 6 | # URL: https://github.com/tarmack/vim-python-ftplugin 7 | 8 | import __builtin__ 9 | import os 10 | import platform 11 | import re 12 | import sys 13 | 14 | def complete_modules(base): 15 | ''' 16 | Find the names of the built-in, binary and source modules available on the 17 | user's system without executing any Python code except for this function (in 18 | other words, module name completion is completely safe). 19 | ''' 20 | # Start with the names of the built-in modules. 21 | if base: 22 | modulenames = set() 23 | else: 24 | modulenames = set(sys.builtin_module_names) 25 | # Find the installed modules. 26 | for root in sys.path: 27 | scan_modules(root, [x for x in base.split('.') if x], modulenames) 28 | # Sort the module list ignoring case and underscores. 29 | if base: 30 | base += '.' 31 | print '\n'.join(list(modulenames)) 32 | #return [base + modname for modname in friendly_sort(list(modulenames))] 33 | 34 | def scan_modules(directory, base, modulenames): 35 | sharedext = platform.system() == 'Windows' and '\.dll' or '\.so' 36 | if not os.path.isdir(directory): 37 | return 38 | for name in os.listdir(directory): 39 | pathname = '%s/%s' % (directory, name) 40 | if os.path.isdir(pathname): 41 | listing = os.listdir(pathname) 42 | if '__init__' in [os.path.splitext(x)[0] for x in listing]: 43 | if base: 44 | if name == base[0]: 45 | scan_modules(pathname, base[1:], modulenames) 46 | else: 47 | modulenames.add(name) 48 | elif (not base) and re.match(r'^[A-Za-z0-9_]+(\.py[co]?|%s)$' % sharedext, name): 49 | name = os.path.splitext(name)[0] 50 | if name != '__init__': 51 | modulenames.add(name) 52 | 53 | def complete_variables(expr): 54 | ''' 55 | Use __import__() and dir() to get the functions and/or variables available in 56 | the given module or submodule. 57 | ''' 58 | todo = [x for x in expr.split('.') if x] 59 | done = [] 60 | attributes = [] 61 | module = load_module(todo, done) 62 | subject = module 63 | while todo: 64 | if len(todo) == 1: 65 | expr = ('.'.join(done) + '.') if done else '' 66 | attributes = [expr + attr for attr in dir(subject) if attr.startswith(todo[0])] 67 | print '\n'.join(attributes) 68 | try: 69 | subject = getattr(subject, todo[0]) 70 | done.append(todo.pop(0)) 71 | except AttributeError: 72 | break 73 | if subject: 74 | expr = ('.'.join(done) + '.') if done else '' 75 | subject = [expr + entry for entry in dir(subject)] 76 | print '\n'.join(subject) 77 | 78 | def load_module(todo, done): 79 | ''' 80 | Find the most specific valid Python module given a tokenized identifier 81 | expression (e.g. `os.path' for `os.path.islink'). 82 | ''' 83 | path = [] 84 | module = __builtin__ 85 | while todo: 86 | path.append(todo[0]) 87 | try: 88 | temp = __import__('.'.join(path)) 89 | if temp.__name__ == '.'.join(path): 90 | module = temp 91 | else: 92 | break 93 | except ImportError: 94 | break 95 | done.append(todo.pop(0)) 96 | return module 97 | 98 | def find_module_path(name): 99 | ''' 100 | Look for a Python module on the module search path (used for "gf" and 101 | searching in imported modules). 102 | ''' 103 | fname = name.replace('.', '/') 104 | for directory in sys.path: 105 | scriptfile = directory + '/' + fname + '.py' 106 | if os.path.isfile(scriptfile): 107 | print scriptfile 108 | break 109 | 110 | # vim: ts=2 sw=2 sts=2 et 111 | -------------------------------------------------------------------------------- /autoload/python_ftplugin/misc/open.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " Integration between Vim and its environment. 8 | " 9 | " Author: Peter Odding 10 | " Last Change: May 19, 2013 11 | " URL: http://peterodding.com/code/vim/misc/ 12 | 13 | if !exists('s:version') 14 | let s:version = '1.1' 15 | let s:enoimpl = "open.vim %s: %s() hasn't been implemented for your platform! If you have suggestions, please contact peter@peterodding.com." 16 | let s:handlers = ['gnome-open', 'kde-open', 'exo-open', 'xdg-open'] 17 | endif 18 | 19 | function! python_ftplugin#misc#open#file(path, ...) " {{{1 20 | " Given a pathname as the first argument, this opens the file with the 21 | " program associated with the file type. So for example a text file might 22 | " open in Vim, an `*.html` file would probably open in your web browser and 23 | " a media file would open in a media player. 24 | " 25 | " This should work on Windows, Mac OS X and most Linux distributions. If 26 | " this fails to find a file association, you can pass one or more external 27 | " commands to try as additional arguments. For example: 28 | " 29 | " :call python_ftplugin#misc#open#file('/path/to/my/file', 'firefox', 'google-chrome') 30 | " 31 | " This generally shouldn't be necessary but it might come in handy now and 32 | " then. 33 | if python_ftplugin#misc#os#is_win() 34 | try 35 | call xolox#shell#open_with_windows_shell(a:path) 36 | catch /^Vim\%((\a\+)\)\=:E117/ 37 | let command = '!start CMD /C START "" %s' 38 | silent execute printf(command, python_ftplugin#misc#escape#shell(a:path)) 39 | endtry 40 | return 41 | elseif has('macunix') 42 | let cmd = 'open ' . shellescape(a:path) . ' 2>&1' 43 | call s:handle_error(cmd, system(cmd)) 44 | return 45 | else 46 | for handler in s:handlers + a:000 47 | if executable(handler) 48 | call python_ftplugin#misc#msg#debug("open.vim %s: Using '%s' to open '%s'.", s:version, handler, a:path) 49 | let cmd = shellescape(handler) . ' ' . shellescape(a:path) . ' 2>&1' 50 | call s:handle_error(cmd, system(cmd)) 51 | return 52 | endif 53 | endfor 54 | endif 55 | throw printf(s:enoimpl, s:script, 'python_ftplugin#misc#open#file') 56 | endfunction 57 | 58 | function! python_ftplugin#misc#open#url(url) " {{{1 59 | " Given a URL as the first argument, this opens the URL in your preferred or 60 | " best available web browser: 61 | " 62 | " - In GUI environments a graphical web browser will open (or a new tab will 63 | " be created in an existing window) 64 | " - In console Vim without a GUI environment, when you have any of `lynx`, 65 | " `links` or `w3m` installed it will launch a command line web browser in 66 | " front of Vim (temporarily suspending Vim) 67 | let url = a:url 68 | if url !~ '^\w\+://' 69 | if url !~ '@' 70 | let url = 'http://' . url 71 | elseif url !~ '^mailto:' 72 | let url = 'mailto:' . url 73 | endif 74 | endif 75 | if has('unix') && !has('gui_running') && $DISPLAY == '' 76 | for browser in ['lynx', 'links', 'w3m'] 77 | if executable(browser) 78 | execute '!' . browser fnameescape(url) 79 | call s:handle_error(browser . ' ' . url, '') 80 | return 81 | endif 82 | endfor 83 | endif 84 | call python_ftplugin#misc#open#file(url, 'firefox', 'google-chrome') 85 | endfunction 86 | 87 | function! s:handle_error(cmd, output) " {{{1 88 | if v:shell_error 89 | let message = "open.vim %s: Failed to execute program! (command line: %s%s)" 90 | let output = strtrans(python_ftplugin#misc#str#trim(a:output)) 91 | if output != '' 92 | let output = ", output: " . string(output) 93 | endif 94 | throw printf(message, s:version, a:cmd, output) 95 | endif 96 | endfunction 97 | 98 | " vim: et ts=2 sw=2 fdm=marker 99 | -------------------------------------------------------------------------------- /autoload/python_ftplugin/misc/timer.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " Timing of long during operations. 8 | " 9 | " Author: Peter Odding 10 | " Last Change: May 20, 2013 11 | " URL: http://peterodding.com/code/vim/misc/ 12 | 13 | if !exists('g:timer_enabled') 14 | let g:timer_enabled = 0 15 | endif 16 | 17 | if !exists('g:timer_verbosity') 18 | let g:timer_verbosity = 1 19 | endif 20 | 21 | let s:has_reltime = has('reltime') 22 | 23 | function! python_ftplugin#misc#timer#start() " {{{1 24 | " Start a timer. This returns a list which can later be passed to 25 | " `python_ftplugin#misc#timer#stop()`. 26 | return s:has_reltime ? reltime() : [localtime()] 27 | endfunction 28 | 29 | function! python_ftplugin#misc#timer#stop(...) " {{{1 30 | " Show a formatted debugging message to the user, if the user has enabled 31 | " increased verbosity by setting Vim's ['verbose'] [verbose] option to one 32 | " (1) or higher. 33 | " 34 | " This function has the same argument handling as Vim's [printf()] [printf] 35 | " function with one difference: At the point where you want the elapsed time 36 | " to be embedded, you write `%s` and you pass the list returned by 37 | " `python_ftplugin#misc#timer#start()` as an argument. 38 | " 39 | " [verbose]: http://vimdoc.sourceforge.net/htmldoc/options.html#'verbose' 40 | " [printf]: http://vimdoc.sourceforge.net/htmldoc/eval.html#printf() 41 | if (g:timer_enabled || &verbose >= g:timer_verbosity) 42 | call call('python_ftplugin#misc#msg#info', map(copy(a:000), 's:convert_value(v:val)')) 43 | endif 44 | endfunction 45 | 46 | function! python_ftplugin#misc#timer#force(...) " {{{1 47 | " Show a formatted message to the user. This function has the same argument 48 | " handling as Vim's [printf()] [printf] function with one difference: At the 49 | " point where you want the elapsed time to be embedded, you write `%s` and 50 | " you pass the list returned by `python_ftplugin#misc#timer#start()` as an argument. 51 | call call('python_ftplugin#misc#msg#info', map(copy(a:000), 's:convert_value(v:val)')) 52 | endfunction 53 | 54 | function! s:convert_value(value) " {{{1 55 | if type(a:value) != type([]) 56 | return a:value 57 | elseif !empty(a:value) 58 | if s:has_reltime 59 | let ts = python_ftplugin#misc#str#trim(reltimestr(reltime(a:value))) 60 | else 61 | let ts = localtime() - a:value[0] 62 | endif 63 | return python_ftplugin#misc#timer#format_timespan(ts) 64 | else 65 | return '?' 66 | endif 67 | endfunction 68 | 69 | " Format number of seconds as human friendly description. 70 | 71 | let s:units = [['day', 60 * 60 * 24], ['hour', 60 * 60], ['minute', 60], ['second', 1]] 72 | 73 | function! python_ftplugin#misc#timer#format_timespan(ts) " {{{1 74 | " Format a time stamp (a string containing a formatted floating point 75 | " number) into a human friendly format, for example 70 seconds is phrased as 76 | " "1 minute and 10 seconds". 77 | 78 | " Convert timespan to integer. 79 | let seconds = a:ts + 0 80 | 81 | " Fast common case with extra precision from reltime(). 82 | if seconds < 5 83 | let extract = matchstr(a:ts, '^\d\+\(\.0*[1-9][1-9]\?\)\?') 84 | if extract =~ '[123456789]' 85 | return extract . ' second' . (extract != '1' ? 's' : '') 86 | endif 87 | endif 88 | 89 | " Generic but slow code. 90 | let result = [] 91 | for [name, size] in s:units 92 | if seconds >= size 93 | let counter = seconds / size 94 | let seconds = seconds % size 95 | let suffix = counter != 1 ? 's' : '' 96 | call add(result, printf('%i %s%s', counter, name, suffix)) 97 | endif 98 | endfor 99 | 100 | " Format the resulting text? 101 | if len(result) == 1 102 | return result[0] 103 | else 104 | return join(result[0:-2], ', ') . ' and ' . result[-1] 105 | endif 106 | 107 | endfunction 108 | 109 | " vim: ts=2 sw=2 et 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vim file type plug-in for the Python programming language 2 | 3 | The Python file type plug-in for Vim helps you while developing in Python by providing the following features: 4 | 5 | * Automatic syntax checking using [pyflakes] [pyflakes]. 6 | * Syntax based folding for classes, functions, comment blocks and multi line strings (the fold text includes the docstring if found). 7 | * You can use `gf` to jump to imported files (searches the Python path). 8 | * You can search imported files using mappings such as `[i`. 9 | * Control-X Control-U completes all available module names. 10 | * Module name completion starts automatically after typing `import` or `from` (automatic completion can be disabled if you find it is too intrusive). 11 | * Control-X Control-O completes variable names, for example after `os` or `os.` it would complete `os.path` (and others). *Be aware that this imports modules to perform introspection and assumes that importing a module does not have serious side effects (although it might, however it shouldn't).* 12 | * You can enable automatic variable completion after typing a dot or if you type a space after `import` on a `from import ` line. (*this is not enabled by default because of the side effect issue mentioned above*). 13 | 14 | **Experimental features:** 15 | 16 | * The plug-in now comes with a Python script that uses the [AST module][ast] in the Python standard library to implement a type inference engine which can be used to suggest completion candidates. The type inference engine has two nice properties that the other completion methods don't have: It has a lot more information to go by and it works by parsing the source code so it's safe to use on code with side effects. 17 | 18 | ## Installation 19 | 20 | To use the plug-in, simply drop the files into your Vim profile directory (usually this is `~/.vim` on UNIX and `%USERPROFILE%\vimfiles` on Windows), restart Vim and execute the command `:helptags ~/.vim/doc` (use `:helptags ~\vimfiles\doc` instead on Windows). 21 | 22 | To use the syntax check, please make sure [pyflakes] [pyflakes] is installed. It can be found in most Linux distributions, e.g. on Debian/Ubuntu Linux it can be installed using the following command: 23 | 24 | $ sudo apt-get install pyflakes 25 | 26 | To enable folds for classes and functions defined without leading whitespace, make sure your Python syntax file uses a match rather than a keyword statement for the def and class keywords. 27 | 28 | Change the line to say something like: 29 | 30 | :syntax match [group] "\<\%(def\|class\)\>" [options] 31 | 32 | I can recommend you [Dmitry Vasiliev's] [extsyntax] adaptation of the default Python syntax file. In this file you will need to replace the following line: 33 | 34 | :syntax keyword pythonStatement def class nextgroup=pythonFunction skipwhite 35 | 36 | with: 37 | 38 | :syntax match pythonStatement "\<\%(def\|class\)\>" nextgroup=pythonFunction skipwhite 39 | 40 | ## Options 41 | 42 | All options are enabled by default. To disable an option do: 43 | 44 | :let g:OPTION_NAME = 0 45 | 46 | ### The `g:python_syntax_fold` option 47 | 48 | Enables syntax based folding for classes, functions and comments. 49 | 50 | ### The `g:python_fold_strings` option 51 | 52 | Enables syntax based folding for strings that span multiple lines. 53 | 54 | ### The `g:python_docstring_in_foldtext` option 55 | 56 | To display the docstring of a class/function in the fold text. 57 | 58 | ### The `g:python_decorators_in_foldtext` option 59 | 60 | To display the decorators in the fold text. Currently arguments to decorators are not shown. 61 | 62 | ### The `g:python_decorator_labels` option 63 | 64 | This is a dictionary mapping decorator names to short labels. By default it is empty. 65 | 66 | ### The `g:python_check_syntax` option 67 | 68 | Enables automatic syntax checking when saving Python buffers. This uses [pyflakes] [pyflakes] when available but falls back on the standard Python compiler for syntax checking. 69 | 70 | ### The `g:python_auto_complete_modules` option 71 | 72 | Controls automatic completion of module names after typing `import` or `from`. Enabled by default. 73 | 74 | ### The `g:python_auto_complete_variables` option 75 | 76 | Controls automatic completion of variables after typing a dot or `from import`. Disabled by default. 77 | 78 | ## Contact 79 | 80 | If you have questions, bug reports, suggestions, etc. you can contact Bart at or Peter at . The latest version is available at and . 81 | 82 | ## License 83 | 84 | This software is licensed under the [MIT license](http://en.wikipedia.org/wiki/MIT_License). 85 | © 2013 Peter Odding <> and Bart Kroon <>. 86 | 87 | 88 | [ast]: http://docs.python.org/library/ast.html 89 | [extsyntax]: http://www.vim.org/scripts/script.php?script_id=790 90 | [pyflakes]: http://pypi.python.org/pypi/pyflakes 91 | -------------------------------------------------------------------------------- /autoload/python_ftplugin/misc/option.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " Vim and plug-in option handling. 8 | " 9 | " Author: Peter Odding 10 | " Last Change: May 19, 2013 11 | " URL: http://peterodding.com/code/vim/misc/ 12 | 13 | function! python_ftplugin#misc#option#get(name, ...) " {{{1 14 | " Expects one or two arguments: 1. The name of a variable and 2. the default 15 | " value if the variable does not exist. 16 | " 17 | " Returns the value of the variable from a buffer local variable, global 18 | " variable or the default value, depending on which is defined. 19 | " 20 | " This is used by some of my Vim plug-ins for option handling, so that users 21 | " can customize options for specific buffers. 22 | if exists('b:' . a:name) 23 | " Buffer local variable. 24 | return eval('b:' . a:name) 25 | elseif exists('g:' . a:name) 26 | " Global variable. 27 | return eval('g:' . a:name) 28 | elseif exists('a:1') 29 | " Default value. 30 | return a:1 31 | endif 32 | endfunction 33 | 34 | function! python_ftplugin#misc#option#split(value) " {{{1 35 | " Given a multi-value Vim option like ['runtimepath'] [rtp] this returns a 36 | " list of strings. For example: 37 | " 38 | " :echo python_ftplugin#misc#option#split(&runtimepath) 39 | " ['/home/peter/Projects/Vim/misc', 40 | " '/home/peter/Projects/Vim/colorscheme-switcher', 41 | " '/home/peter/Projects/Vim/easytags', 42 | " ...] 43 | " 44 | " [rtp]: http://vimdoc.sourceforge.net/htmldoc/options.html#'runtimepath' 45 | let values = split(a:value, '[^\\]\zs,') 46 | return map(values, 's:unescape(v:val)') 47 | endfunction 48 | 49 | function! s:unescape(s) 50 | return substitute(a:s, '\\\([\\,]\)', '\1', 'g') 51 | endfunction 52 | 53 | function! python_ftplugin#misc#option#join(values) " {{{1 54 | " Given a list of strings like the ones returned by 55 | " `python_ftplugin#misc#option#split()`, this joins the strings together into a 56 | " single value that can be used to set a Vim option. 57 | let values = copy(a:values) 58 | call map(values, 's:escape(v:val)') 59 | return join(values, ',') 60 | endfunction 61 | 62 | function! s:escape(s) 63 | return escape(a:s, ',\') 64 | endfunction 65 | 66 | function! python_ftplugin#misc#option#split_tags(value) " {{{1 67 | " Customized version of `python_ftplugin#misc#option#split()` with specialized 68 | " handling for Vim's ['tags' option] [tags]. 69 | " 70 | " [tags]: http://vimdoc.sourceforge.net/htmldoc/options.html#'tags' 71 | let values = split(a:value, '[^\\]\zs,') 72 | return map(values, 's:unescape_tags(v:val)') 73 | endfunction 74 | 75 | function! s:unescape_tags(s) 76 | return substitute(a:s, '\\\([\\, ]\)', '\1', 'g') 77 | endfunction 78 | 79 | function! python_ftplugin#misc#option#join_tags(values) " {{{1 80 | " Customized version of `python_ftplugin#misc#option#join()` with specialized 81 | " handling for Vim's ['tags' option] [tags]. 82 | let values = copy(a:values) 83 | call map(values, 's:escape_tags(v:val)') 84 | return join(values, ',') 85 | endfunction 86 | 87 | function! s:escape_tags(s) 88 | return escape(a:s, ', ') 89 | endfunction 90 | 91 | function! python_ftplugin#misc#option#eval_tags(value, ...) " {{{1 92 | " Evaluate Vim's ['tags' option] [tags] without looking at the file 93 | " system, i.e. this will report tags files that don't exist yet. Expects 94 | " the value of the ['tags' option] [tags] as the first argument. If the 95 | " optional second argument is 1 (true) only the first match is returned, 96 | " otherwise (so by default) a list with all matches is returned. 97 | let pathnames = [] 98 | let first_only = exists('a:1') ? a:1 : 0 99 | for pattern in python_ftplugin#misc#option#split_tags(a:value) 100 | " Make buffer relative pathnames absolute. 101 | if pattern =~ '^\./' 102 | let directory = python_ftplugin#misc#escape#substitute(expand('%:p:h')) 103 | let pattern = substitute(pattern, '^.\ze/', directory, '') 104 | endif 105 | " Make working directory relative pathnames absolute. 106 | if python_ftplugin#misc#path#is_relative(pattern) 107 | let pattern = python_ftplugin#misc#path#merge(getcwd(), pattern) 108 | endif 109 | " Ignore the trailing `;' for recursive upwards searching because we 110 | " always want the most specific pathname available. 111 | let pattern = substitute(pattern, ';$', '', '') 112 | " Expand the pattern. 113 | call extend(pathnames, split(expand(pattern), "\n")) 114 | if first_only && !empty(pathnames) 115 | return pathnames[0] 116 | endif 117 | endfor 118 | return first_only ? '' : pathnames 119 | endfunction 120 | 121 | " vim: ts=2 sw=2 et 122 | -------------------------------------------------------------------------------- /ftplugin/python.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " Vim file type plug-in 8 | " Language: Python 9 | " Authors: 10 | " - Peter Odding 11 | " - Bart Kroon 12 | " Last Change: May 20, 2013 13 | " URL: https://github.com/tarmack/vim-python-ftplugin 14 | 15 | if exists('b:did_ftplugin') 16 | finish 17 | else 18 | let b:did_ftplugin = 1 19 | endif 20 | 21 | " Enable line continuation. 22 | let s:cpo_save = &cpo 23 | set cpo&vim 24 | 25 | " Buffer local options. {{{1 26 | 27 | " A list of commands that undo buffer local changes made below. 28 | let s:undo_ftplugin = [] 29 | 30 | " Make sure "#" doesn't jump to the start of the line. 31 | setlocal cinkeys-=0# indentkeys-=0# 32 | call add(s:undo_ftplugin, 'setlocal cinkeys< indentkeys<') 33 | 34 | " Follow import statements. 35 | setlocal include=\s*\\(from\\\|import\\) 36 | setlocal includeexpr=python_ftplugin#include_expr(v:fname) 37 | setlocal suffixesadd=.py 38 | call add(s:undo_ftplugin, 'setlocal include< includeexpr< suffixesadd<') 39 | 40 | " Enable formatting of comments. 41 | setlocal comments=b:# 42 | setlocal commentstring=#%s 43 | call add(s:undo_ftplugin, 'setlocal comments< commentstring<') 44 | 45 | " Ignore bytecode files during completion. 46 | set wildignore+=*.pyc wildignore+=*.pyo 47 | call add(s:undo_ftplugin, 'setlocal wildignore<') 48 | 49 | " Alternate fold text generating function. 50 | setlocal foldtext=python_ftplugin#fold_text() 51 | call add(s:undo_ftplugin, 'setlocal foldtext<') 52 | 53 | " Completion of modules and variables using Control-X Control-O. 54 | setlocal omnifunc=python_ftplugin#omni_complete 55 | call add(s:undo_ftplugin, 'setlocal omnifunc<') 56 | 57 | " File open/save dialog filename filter on Windows. 58 | if has('gui_win32') && !exists('b:browsefilter') 59 | let b:browsefilter = "Python Files (*.py)\t*.py\nAll Files (*.*)\t*.*\n" 60 | call add(s:undo_ftplugin, 'unlet! b:browsefilter') 61 | endif 62 | 63 | " Syntax based folding is known to slow Vim down significantly. The following 64 | " code implements a workaround that doesn't exactly fix the issue but at least 65 | " makes it less obnoxious. See also: 66 | " http://vim.wikia.com/wiki/Keep_folds_closed_while_inserting_text 67 | augroup PluginFileTypePython 68 | autocmd! InsertEnter if !exists('w:last_fdm') | let w:last_fdm = &fdm | setl fdm=manual | endif 69 | call add(s:undo_ftplugin, 'autocmd! PluginFileTypePython InsertEnter ') 70 | autocmd! InsertLeave,WinLeave if exists('w:last_fdm') | let &l:fdm = w:last_fdm | unlet w:last_fdm | endif 71 | call add(s:undo_ftplugin, 'autocmd! PluginFileTypePython InsertLeave,WinLeave ') 72 | augroup END 73 | 74 | " Mappings to jump between classes and functions. {{{1 75 | nnoremap ]] :call python_ftplugin#jump('/^\(class\\|def\)') 76 | nnoremap [[ :call python_ftplugin#jump('?^\(class\\|def\)') 77 | nnoremap ]m :call python_ftplugin#jump('/^\s*\(class\\|def\)') 78 | nnoremap [m :call python_ftplugin#jump('?^\s*\(class\\|def\)') 79 | call add(s:undo_ftplugin, 'nunmap ]]') 80 | call add(s:undo_ftplugin, 'nunmap [[') 81 | call add(s:undo_ftplugin, 'nunmap ]m') 82 | call add(s:undo_ftplugin, 'nunmap [m') 83 | 84 | " Enable syntax folding. {{{1 85 | if python_ftplugin#misc#option#get('python_syntax_fold', 1) 86 | setlocal foldmethod=syntax 87 | call add(s:undo_ftplugin, 'setlocal foldmethod<') 88 | " Match docstrings that span more than one line. 89 | if python_ftplugin#misc#option#get('python_fold_docstrings', 1) 90 | syn region pythonFoldedString start=+[Bb]\=[Rr]\=[Uu]\=\z("""\|'''\)+ end=+.*\z1+ fold transparent contained 91 | \ containedin=pythonString,pythonUniString,pythonUniRawString,pythonRawString 92 | endif 93 | " Match function and class definitions. 94 | syntax region pythonFunctionFold 95 | \ start="^\(\z(\s*\)\)\%(@.*\n\1\)\@ call python_ftplugin#syntax_check() 106 | call add(s:undo_ftplugin, 'autocmd! PluginFileTypePython BufWritePost ') 107 | augroup END 108 | 109 | " Support for automatic completion. {{{1 110 | inoremap python_ftplugin#auto_complete(' ') 111 | inoremap . python_ftplugin#auto_complete('.') 112 | 113 | " }}}1 114 | 115 | " Let Vim know how to disable the plug-in. 116 | call map(s:undo_ftplugin, "'execute ' . string(v:val)") 117 | let b:undo_ftplugin = join(s:undo_ftplugin, ' | ') 118 | unlet s:undo_ftplugin 119 | 120 | " Restore "cpoptions". 121 | let &cpo = s:cpo_save 122 | unlet s:cpo_save 123 | 124 | " vim: ts=2 sw=2 et 125 | -------------------------------------------------------------------------------- /doc/ft_python.txt: -------------------------------------------------------------------------------- 1 | *ft_python.txt* Vim file type plug-in for the Python programming language 2 | 3 | =============================================================================== 4 | *ft_python-contents* 5 | Contents ~ 6 | 7 | 1. Introduction |ft_python-introduction| 8 | 2. Installation |ft_python-installation| 9 | 3. Options |ft_python-options| 10 | 1. The |g:python_syntax_fold| option 11 | 2. The |g:python_fold_strings| option 12 | 3. The |g:python_docstring_in_foldtext| option 13 | 4. The |g:python_decorators_in_foldtext| option 14 | 5. The |g:python_decorator_labels| option 15 | 6. The |g:python_check_syntax| option 16 | 7. The |g:python_auto_complete_modules| option 17 | 8. The |g:python_auto_complete_variables| option 18 | 4. Contact |ft_python-contact| 19 | 5. License |ft_python-license| 20 | 21 | =============================================================================== 22 | *ft_python-introduction* 23 | Introduction ~ 24 | 25 | The Python file type plug-in for Vim helps you while developing in Python by 26 | providing the following features: 27 | 28 | - Automatic syntax checking using pyflakes [1]. 29 | 30 | - Syntax based folding for classes, functions, comment blocks and multi line 31 | strings (the fold text includes the docstring if found). 32 | 33 | - You can use 'gf' to jump to imported files (searches the Python path). 34 | 35 | - You can search imported files using mappings such as '[i'. 36 | 37 | - Control-X Control-U completes all available module names. 38 | 39 | - Module name completion starts automatically after typing 'import' or 'from' 40 | (automatic completion can be disabled if you find it is too intrusive). 41 | 42 | - Control-X Control-O completes variable names, for example after 'os' or 43 | 'os.' it would complete 'os.path' (and others). Be aware that this imports 44 | modules to perform introspection and assumes that importing a module does 45 | not have serious side effects (although it might, however it shouldn't). 46 | 47 | - You can enable automatic variable completion after typing a dot or if you 48 | type a space after 'import' on a 'from import ' line. 49 | (this is not enabled by default because of the side effect issue mentioned 50 | above). 51 | 52 | Experimental features: 53 | 54 | - The plug-in now comes with a Python script that uses the AST module [2] in 55 | the Python standard library to implement a type inference engine which can 56 | be used to suggest completion candidates. The type inference engine has two 57 | nice properties that the other completion methods don't have: It has a lot 58 | more information to go by and it works by parsing the source code so it's 59 | safe to use on code with side effects. 60 | 61 | =============================================================================== 62 | *ft_python-installation* 63 | Installation ~ 64 | 65 | To use the plug-in, simply drop the files into your Vim profile directory 66 | (usually this is '~/.vim' on UNIX and '%USERPROFILE%\vimfiles' on Windows), 67 | restart Vim and execute the command ':helptags ~/.vim/doc' (use ':helptags 68 | ~\vimfiles\doc' instead on Windows). 69 | 70 | To use the syntax check, please make sure pyflakes [1] is installed. It can be 71 | found in most Linux distributions, e.g. on Debian/Ubuntu Linux it can be 72 | installed using the following command: 73 | > 74 | $ sudo apt-get install pyflakes 75 | 76 | To enable folds for classes and functions defined without leading whitespace, 77 | make sure your Python syntax file uses a match rather than a keyword statement 78 | for the def and class keywords. 79 | 80 | Change the line to say something like: 81 | > 82 | :syntax match [group] "\<\%(def\|class\)\>" [options] 83 | 84 | I can recommend you Dmitry Vasiliev's [3] adaptation of the default Python 85 | syntax file. In this file you will need to replace the following line: 86 | > 87 | :syntax keyword pythonStatement def class nextgroup=pythonFunction skipwhite 88 | 89 | with: 90 | > 91 | :syntax match pythonStatement "\<\%(def\|class\)\>" nextgroup=pythonFunction skipwhite 92 | 93 | =============================================================================== 94 | *ft_python-options* 95 | Options ~ 96 | 97 | All options are enabled by default. To disable an option do: 98 | > 99 | :let g:OPTION_NAME = 0 100 | 101 | ------------------------------------------------------------------------------- 102 | The *g:python_syntax_fold* option 103 | 104 | Enables syntax based folding for classes, functions and comments. 105 | 106 | ------------------------------------------------------------------------------- 107 | The *g:python_fold_strings* option 108 | 109 | Enables syntax based folding for strings that span multiple lines. 110 | 111 | ------------------------------------------------------------------------------- 112 | The *g:python_docstring_in_foldtext* option 113 | 114 | To display the docstring of a class/function in the fold text. 115 | 116 | ------------------------------------------------------------------------------- 117 | The *g:python_decorators_in_foldtext* option 118 | 119 | To display the decorators in the fold text. Currently arguments to decorators 120 | are not shown. 121 | 122 | ------------------------------------------------------------------------------- 123 | The *g:python_decorator_labels* option 124 | 125 | This is a dictionary mapping decorator names to short labels. By default it is 126 | empty. 127 | 128 | ------------------------------------------------------------------------------- 129 | The *g:python_check_syntax* option 130 | 131 | Enables automatic syntax checking when saving Python buffers. This uses 132 | pyflakes [1] when available but falls back on the standard Python compiler for 133 | syntax checking. 134 | 135 | ------------------------------------------------------------------------------- 136 | The *g:python_auto_complete_modules* option 137 | 138 | Controls automatic completion of module names after typing 'import' or 139 | 'from'. Enabled by default. 140 | 141 | ------------------------------------------------------------------------------- 142 | The *g:python_auto_complete_variables* option 143 | 144 | Controls automatic completion of variables after typing a dot or 'from 145 | import'. Disabled by default. 146 | 147 | =============================================================================== 148 | *ft_python-contact* 149 | Contact ~ 150 | 151 | If you have questions, bug reports, suggestions, etc. you can contact Bart at 152 | bart@tarmack.eu or Peter at peter@peterodding.com. The latest version is 153 | available at http://peterodding.com/code/vim/python-ftplugin and 154 | https://github.com/tarmack/vim-python-ftplugin. 155 | 156 | =============================================================================== 157 | *ft_python-license* 158 | License ~ 159 | 160 | This software is licensed under the MIT license [4]. Copyright 2013 Peter 161 | Odding and Bart Kroon . 162 | 163 | =============================================================================== 164 | *ft_python-references* 165 | References ~ 166 | 167 | [1] http://pypi.python.org/pypi/pyflakes 168 | [2] http://docs.python.org/library/ast.html 169 | [3] http://www.vim.org/scripts/script.php?script_id=790 170 | [4] http://en.wikipedia.org/wiki/MIT_License 171 | 172 | vim: ft=help 173 | -------------------------------------------------------------------------------- /autoload/python_ftplugin/misc/os.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " Operating system interfaces. 8 | " 9 | " Author: Peter Odding 10 | " Last Change: May 20, 2013 11 | " URL: http://peterodding.com/code/vim/misc/ 12 | 13 | let g:python_ftplugin#misc#os#version = '0.5' 14 | 15 | function! python_ftplugin#misc#os#is_win() " {{{1 16 | " Returns 1 (true) when on Microsoft Windows, 0 (false) otherwise. 17 | return has('win16') || has('win32') || has('win64') 18 | endfunction 19 | 20 | function! python_ftplugin#misc#os#find_vim() " {{{1 21 | " Returns the program name of Vim as a string. On Windows and UNIX this 22 | " simply returns [v:progname] [progname] while on Mac OS X there is some 23 | " special magic to find MacVim's executable even though it's usually not on 24 | " the executable search path. 25 | " 26 | " [progname]: http://vimdoc.sourceforge.net/htmldoc/eval.html#v:progname 27 | let progname = '' 28 | if has('macunix') 29 | " Special handling for Mac OS X where MacVim is usually not on the $PATH. 30 | call python_ftplugin#misc#msg#debug("os.vim %s: Trying MacVim workaround to find Vim executable ..", g:python_ftplugin#misc#os#version) 31 | let segments = python_ftplugin#misc#path#split($VIMRUNTIME) 32 | if segments[-3:] == ['Resources', 'vim', 'runtime'] 33 | let progname = python_ftplugin#misc#path#join(segments[0:-4] + ['MacOS', 'Vim']) 34 | call python_ftplugin#misc#msg#debug("os.vim %s: The MacVim workaround resulted in the Vim executable %s.", g:python_ftplugin#misc#os#version, string(progname)) 35 | endif 36 | endif 37 | if empty(progname) 38 | call python_ftplugin#misc#msg#debug("os.vim %s: Looking for Vim executable named %s on search path ..", g:python_ftplugin#misc#os#version, string(v:progname)) 39 | let candidates = python_ftplugin#misc#path#which(v:progname) 40 | if !empty(candidates) 41 | call python_ftplugin#misc#msg#debug("os.vim %s: Found %i candidate(s) on search path: %s.", g:python_ftplugin#misc#os#version, len(candidates), string(candidates)) 42 | let progname = candidates[0] 43 | endif 44 | endif 45 | call python_ftplugin#misc#msg#debug("os.vim %s: Reporting Vim executable %s.", g:python_ftplugin#misc#os#version, string(progname)) 46 | return progname 47 | endfunction 48 | 49 | function! python_ftplugin#misc#os#exec(options) " {{{1 50 | " Execute an external command (hiding the console on Microsoft Windows when 51 | " my [vim-shell plug-in] [vim-shell] is installed). 52 | " 53 | " Expects a dictionary with the following key/value pairs as the first 54 | " argument: 55 | " 56 | " - **command** (required): The command line to execute 57 | " - **async** (optional): set this to 1 (true) to execute the command in the 58 | " background (asynchronously) 59 | " - **stdin** (optional): a string or list of strings with the input for the 60 | " external command 61 | " - **check** (optional): set this to 0 (false) to disable checking of the 62 | " exit code of the external command (by default an exception will be 63 | " raised when the command fails) 64 | " 65 | " Returns a dictionary with one or more of the following key/value pairs: 66 | " 67 | " - **command** (always available): the generated command line that was used 68 | " to run the external command 69 | " - **exit_code** (only in synchronous mode): the exit status of the 70 | " external command (an integer, zero on success) 71 | " - **stdout** (only in synchronous mode): the output of the command on the 72 | " standard output stream (a list of strings, one for each line) 73 | " - **stderr** (only in synchronous mode): the output of the command on the 74 | " standard error stream (as a list of strings, one for each line) 75 | " 76 | " [vim-shell]: http://peterodding.com/code/vim/shell/ 77 | try 78 | 79 | " Unpack the options. 80 | let cmd = a:options['command'] 81 | let async = get(a:options, 'async', 0) 82 | 83 | " Write the input for the external command to a temporary file? 84 | if has_key(a:options, 'stdin') 85 | let tempin = tempname() 86 | if type(a:options['stdin']) == type([]) 87 | let lines = a:options['stdin'] 88 | else 89 | let lines = split(a:options['stdin'], "\n") 90 | endif 91 | call writefile(lines, tempin) 92 | let cmd .= ' < ' . python_ftplugin#misc#escape#shell(tempin) 93 | endif 94 | 95 | " Redirect the standard output and standard error streams of the external 96 | " process to temporary files? (only in synchronous mode, which is the 97 | " default). 98 | if !async 99 | let tempout = tempname() 100 | let temperr = tempname() 101 | let cmd = printf('(%s) 1>%s 2>%s', cmd, python_ftplugin#misc#escape#shell(tempout), python_ftplugin#misc#escape#shell(temperr)) 102 | endif 103 | 104 | " If A) we're on Windows, B) the vim-shell plug-in is installed and C) the 105 | " compiled DLL works, we'll use that because it's the most user friendly 106 | " method. If the plug-in is not installed Vim will raise the exception 107 | " "E117: Unknown function" which is caught and handled below. 108 | try 109 | if xolox#shell#can_use_dll() 110 | " Let the user know what's happening (in case they're interested). 111 | call python_ftplugin#misc#msg#debug("os.vim %s: Executing external command using compiled DLL: %s", g:python_ftplugin#misc#os#version, cmd) 112 | let exit_code = xolox#shell#execute_with_dll(cmd, async) 113 | endif 114 | catch /^Vim\%((\a\+)\)\=:E117/ 115 | call python_ftplugin#misc#msg#debug("os.vim %s: The vim-shell plug-in is not installed, falling back to system() function.", g:python_ftplugin#misc#os#version) 116 | endtry 117 | 118 | " If we cannot use the DLL, we fall back to the default and generic 119 | " implementation using Vim's system() function. 120 | if !exists('exit_code') 121 | 122 | " Enable asynchronous mode (very platform specific). 123 | if async 124 | if python_ftplugin#misc#os#is_win() 125 | let cmd = 'start /b ' . cmd 126 | elseif has('unix') 127 | let cmd = '(' . cmd . ') &' 128 | else 129 | call python_ftplugin#misc#msg#warn("os.vim %s: I don't know how to run commands asynchronously on your platform! Falling back to synchronous mode.", g:python_ftplugin#misc#os#version) 130 | endif 131 | endif 132 | 133 | " Execute the command line using 'sh' instead of the default shell, 134 | " because we assume that standard output and standard error can be 135 | " redirected separately, but (t)csh does not support this. 136 | if has('unix') 137 | call python_ftplugin#misc#msg#debug("os.vim %s: Generated shell expression: %s", g:python_ftplugin#misc#os#version, cmd) 138 | let cmd = printf('sh -c %s', python_ftplugin#misc#escape#shell(cmd)) 139 | endif 140 | 141 | " Let the user know what's happening (in case they're interested). 142 | call python_ftplugin#misc#msg#debug("os.vim %s: Executing external command using system() function: %s", g:python_ftplugin#misc#os#version, cmd) 143 | call system(cmd) 144 | let exit_code = v:shell_error 145 | 146 | endif 147 | 148 | " Return the results as a dictionary with one or more key/value pairs. 149 | let result = {'command': cmd} 150 | if !async 151 | let result['exit_code'] = exit_code 152 | let result['stdout'] = s:readfile(tempout) 153 | let result['stderr'] = s:readfile(temperr) 154 | " If we just executed a synchronous command and the caller didn't 155 | " specifically ask us *not* to check the exit code of the external 156 | " command, we'll do so now. 157 | if get(a:options, 'check', 1) && exit_code != 0 158 | " Prepare an error message with enough details so the user can investigate. 159 | let msg = printf("os.vim %s: External command failed with exit code %d!", g:python_ftplugin#misc#os#version, result['exit_code']) 160 | let msg .= printf("\nCommand line: %s", result['command']) 161 | " If the external command reported an error, we'll include it in our message. 162 | if !empty(result['stderr']) 163 | " This is where we would normally expect to find an error message. 164 | let msg .= printf("\nOutput on standard output stream:\n%s", join(result['stderr'], "\n")) 165 | elseif !empty(result['stdout']) 166 | " Exuberant Ctags on Windows XP reports errors on standard output :-x. 167 | let msg .= printf("\nOutput on standard error stream:\n%s", join(result['stdout'], "\n")) 168 | endif 169 | throw msg 170 | endif 171 | endif 172 | return result 173 | 174 | finally 175 | " Cleanup any temporary files we created. 176 | for name in ['tempin', 'tempout', 'temperr'] 177 | if exists(name) 178 | call delete({name}) 179 | endif 180 | endfor 181 | endtry 182 | 183 | endfunction 184 | 185 | function! s:readfile(fname) " {{{1 186 | " readfile() that swallows errors. 187 | try 188 | return readfile(a:fname) 189 | catch 190 | return [] 191 | endtry 192 | endfunction 193 | 194 | " vim: ts=2 sw=2 et 195 | -------------------------------------------------------------------------------- /autoload/python_ftplugin/misc/path.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " Pathname manipulation functions. 8 | " 9 | " Author: Peter Odding 10 | " Last Change: May 20, 2013 11 | " URL: http://peterodding.com/code/vim/misc/ 12 | 13 | let s:windows_compatible = python_ftplugin#misc#os#is_win() 14 | let s:mac_os_x_compatible = has('macunix') 15 | 16 | function! python_ftplugin#misc#path#which(...) " {{{1 17 | " Scan the executable search path (`$PATH`) for one or more external 18 | " programs. Expects one or more string arguments with program names. Returns 19 | " a list with the absolute pathnames of all found programs. Here's an 20 | " example: 21 | " 22 | " :echo python_ftplugin#misc#path#which('gvim', 'vim') 23 | " ['/usr/local/bin/gvim', 24 | " '/usr/bin/gvim', 25 | " '/usr/local/bin/vim', 26 | " '/usr/bin/vim'] 27 | let extensions = [''] 28 | if s:windows_compatible 29 | call extend(extensions, split($PATHEXT, ';')) 30 | endif 31 | let matches = [] 32 | let checked = {} 33 | for program in a:000 34 | for extension in extensions 35 | for directory in split($PATH, s:windows_compatible ? ';' : ':') 36 | let directory = python_ftplugin#misc#path#absolute(directory) 37 | if isdirectory(directory) 38 | let path = python_ftplugin#misc#path#merge(directory, program . extension) 39 | if executable(path) 40 | call add(matches, path) 41 | endif 42 | endif 43 | endfor 44 | endfor 45 | endfor 46 | return matches 47 | endfunction 48 | 49 | function! python_ftplugin#misc#path#split(path) " {{{1 50 | " Split a pathname (the first and only argument) into a list of pathname 51 | " components. 52 | " 53 | " On Windows, pathnames starting with two slashes or backslashes are UNC 54 | " paths where the leading slashes are significant... In this case we split 55 | " like this: 56 | " 57 | " - Input: `'//server/share/directory'` 58 | " - Result: `['//server', 'share', 'directory']` 59 | " 60 | " Everything except Windows is treated like UNIX until someone has a better 61 | " suggestion :-). In this case we split like this: 62 | " 63 | " - Input: `'/foo/bar/baz'` 64 | " - Result: `['/', 'foo', 'bar', 'baz']` 65 | " 66 | " To join a list of pathname components back into a single pathname string, 67 | " use the `python_ftplugin#misc#path#join()` function. 68 | if type(a:path) == type('') 69 | if s:windows_compatible 70 | if a:path =~ '^[\/][\/]' 71 | " UNC pathname. 72 | return split(a:path, '\%>2c[\/]\+') 73 | else 74 | " If it's not a UNC path we can simply split on slashes & backslashes. 75 | return split(a:path, '[\/]\+') 76 | endif 77 | else 78 | " Everything else is treated as UNIX. 79 | let absolute = (a:path =~ '^/') 80 | let segments = split(a:path, '/\+') 81 | return absolute ? insert(segments, '/') : segments 82 | endif 83 | endif 84 | return [] 85 | endfunction 86 | 87 | function! python_ftplugin#misc#path#join(parts) " {{{1 88 | " Join a list of pathname components (the first and only argument) into a 89 | " single pathname string. This is the counterpart to the 90 | " `python_ftplugin#misc#path#split()` function and it expects a list of pathname 91 | " components as returned by `python_ftplugin#misc#path#split()`. 92 | if type(a:parts) == type([]) 93 | if s:windows_compatible 94 | return join(a:parts, python_ftplugin#misc#path#directory_separator()) 95 | elseif a:parts[0] == '/' 96 | " Absolute path on UNIX (non-Windows). 97 | return '/' . join(a:parts[1:], '/') 98 | else 99 | " Relative path on UNIX (non-Windows). 100 | return join(a:parts, '/') 101 | endif 102 | endif 103 | return '' 104 | endfunction 105 | 106 | function! python_ftplugin#misc#path#directory_separator() " {{{1 107 | " Find the preferred directory separator for the platform and settings. 108 | return exists('+shellslash') && &shellslash ? '/' : '\' 109 | endfunction 110 | 111 | function! python_ftplugin#misc#path#absolute(path) " {{{1 112 | " Canonicalize and resolve a pathname, *regardless of whether it exists*. 113 | " This is intended to support string comparison to determine whether two 114 | " pathnames point to the same directory or file. 115 | if type(a:path) == type('') 116 | let path = a:path 117 | " Make the pathname absolute. 118 | if path =~ '^\~' 119 | " Expand ~ to $HOME. 120 | let path = $HOME . '/' . path[1:] 121 | elseif python_ftplugin#misc#path#is_relative(path) 122 | " Make relative pathnames absolute. 123 | let path = getcwd() . '/' . path 124 | endif 125 | " Resolve symbolic links to find the canonical pathname. In my tests this 126 | " also removes all symbolic pathname segments (`.' and `..'), even when 127 | " the pathname does not exist. Also there used to be a bug in resolve() 128 | " where it wouldn't resolve pathnames ending in a directory separator. 129 | " Since it's not much trouble to work around, that's what we do. 130 | let path = resolve(substitute(path, s:windows_compatible ? '[\/]\+$' : '/\+$', '', '')) 131 | " Normalize directory separators (especially relevant on Windows). 132 | let parts = python_ftplugin#misc#path#split(path) 133 | if s:windows_compatible && parts[0] =~ '^[\/][\/]' 134 | " Also normalize the two leading "directory separators" (I'm not 135 | " sure what else to call them :-) in Windows UNC pathnames. 136 | let parts[0] = repeat(python_ftplugin#misc#path#directory_separator(), 2) . parts[0][2:] 137 | endif 138 | return python_ftplugin#misc#path#join(parts) 139 | endif 140 | return '' 141 | endfunction 142 | 143 | function! python_ftplugin#misc#path#relative(path, base) " {{{1 144 | " Make an absolute pathname (the first argument) relative to a directory 145 | " (the second argument). 146 | let path = python_ftplugin#misc#path#split(a:path) 147 | let base = python_ftplugin#misc#path#split(a:base) 148 | while path != [] && base != [] && path[0] == base[0] 149 | call remove(path, 0) 150 | call remove(base, 0) 151 | endwhile 152 | let distance = repeat(['..'], len(base)) 153 | return python_ftplugin#misc#path#join(distance + path) 154 | endfunction 155 | 156 | 157 | function! python_ftplugin#misc#path#merge(parent, child, ...) " {{{1 158 | " Join a directory pathname and filename into a single pathname. 159 | if type(a:parent) == type('') && type(a:child) == type('') 160 | " TODO Use python_ftplugin#misc#path#is_relative()? 161 | if s:windows_compatible 162 | let parent = substitute(a:parent, '[\\/]\+$', '', '') 163 | let child = substitute(a:child, '^[\\/]\+', '', '') 164 | return parent . '\' . child 165 | else 166 | let parent = substitute(a:parent, '/\+$', '', '') 167 | let child = substitute(a:child, '^/\+', '', '') 168 | return parent . '/' . child 169 | endif 170 | endif 171 | return '' 172 | endfunction 173 | 174 | function! python_ftplugin#misc#path#commonprefix(paths) " {{{1 175 | " Find the common prefix of path components in a list of pathnames. 176 | let common = python_ftplugin#misc#path#split(a:paths[0]) 177 | for path in a:paths 178 | let index = 0 179 | for segment in python_ftplugin#misc#path#split(path) 180 | if len(common) <= index 181 | break 182 | elseif common[index] != segment 183 | call remove(common, index, -1) 184 | break 185 | endif 186 | let index += 1 187 | endfor 188 | endfor 189 | return python_ftplugin#misc#path#join(common) 190 | endfunction 191 | 192 | function! python_ftplugin#misc#path#encode(path) " {{{1 193 | " Encode a pathname so it can be used as a filename. This uses URL encoding 194 | " to encode special characters. 195 | if s:windows_compatible 196 | let mask = '[*|\\/:"<>?%]' 197 | elseif s:mac_os_x_compatible 198 | let mask = '[\\/%:]' 199 | else 200 | let mask = '[\\/%]' 201 | endif 202 | return substitute(a:path, mask, '\=printf("%%%x", char2nr(submatch(0)))', 'g') 203 | endfunction 204 | 205 | 206 | function! python_ftplugin#misc#path#decode(encoded_path) " {{{1 207 | " Decode a pathname previously encoded with `python_ftplugin#misc#path#encode()`. 208 | return substitute(a:encoded_path, '%\(\x\x\?\)', '\=nr2char("0x" . submatch(1))', 'g') 209 | endfunction 210 | 211 | " python_ftplugin#misc#path#equals(a, b) - Check whether two pathnames point to the same file. {{{1 212 | 213 | if s:windows_compatible 214 | function! python_ftplugin#misc#path#equals(a, b) 215 | return a:a ==? a:b || python_ftplugin#misc#path#absolute(a:a) ==? python_ftplugin#misc#path#absolute(a:b) 216 | endfunction 217 | else 218 | function! python_ftplugin#misc#path#equals(a, b) 219 | return a:a ==# a:b || python_ftplugin#misc#path#absolute(a:a) ==# python_ftplugin#misc#path#absolute(a:b) 220 | endfunction 221 | endif 222 | 223 | function! python_ftplugin#misc#path#is_relative(path) " {{{1 224 | " Returns true (1) when the pathname given as the first argument is 225 | " relative, false (0) otherwise. 226 | if a:path =~ '^\w\+://' 227 | return 0 228 | elseif s:windows_compatible 229 | return a:path !~ '^\(\w:\|[\\/]\)' 230 | else 231 | return a:path !~ '^/' 232 | endif 233 | endfunction 234 | 235 | function! python_ftplugin#misc#path#tempdir() " {{{1 236 | " Create a temporary directory and return the pathname of the directory. 237 | if !exists('s:tempdir_counter') 238 | let s:tempdir_counter = 1 239 | endif 240 | if exists('*mkdir') 241 | if s:windows_compatible 242 | let template = $TMP . '\vim_tempdir_' 243 | elseif filewritable('/tmp') == 2 244 | let template = '/tmp/vim_tempdir_' 245 | endif 246 | endif 247 | if !exists('template') 248 | throw "python_ftplugin#misc#path#tempdir() hasn't been implemented on your platform!" 249 | endif 250 | while 1 251 | let directory = template . s:tempdir_counter 252 | try 253 | call mkdir(directory, '', 0700) 254 | return directory 255 | catch /\/ 256 | " Keep looking for a non-existing directory. 257 | endtry 258 | let s:tempdir_counter += 1 259 | endwhile 260 | endfunction 261 | 262 | " vim: ts=2 sw=2 et 263 | -------------------------------------------------------------------------------- /misc/python-ftplugin/inference.py: -------------------------------------------------------------------------------- 1 | # Type inference engine for the Python file type plug-in for Vim. 2 | # Authors: 3 | # - Peter Odding 4 | # - Bart Kroon 5 | # Last Change: October 9, 2011 6 | # URL: https://github.com/tarmack/vim-python-ftplugin 7 | 8 | # TODO Nested yields don't work in Python, are there any nice alternatives? (I miss Lua's coroutines) 9 | # http://groups.google.com/group/comp.lang.python/browse_frm/thread/fcd2709952d23e34?hl=en&lr=&ie=UTF-8&rnum=9&prev=/&frame=on 10 | 11 | import ast 12 | import collections 13 | 14 | DEBUG = False 15 | LOGFILE = '/tmp/inference.log' 16 | 17 | # Mapping of built-ins to result types. 18 | BUILTINS = { 19 | 'bool': bool, 20 | 'int': int, 21 | 'long': long, 22 | 'float': float, 23 | 'str': str, 24 | 'unicode': unicode, 25 | 'list': list, 26 | 'dict': dict, 27 | 'set': set, 28 | 'frozenset': frozenset, 29 | 'object': object, 30 | 'tuple': tuple, 31 | 'file': file, 32 | 'open': file, 33 | 'len': int, 34 | 'locals': dict, 35 | 'max': int, 36 | 'min': int, 37 | } 38 | 39 | # Mapping of AST types to constructors. 40 | AST_TYPES = { 41 | id(ast.Num): int, 42 | id(ast.Str): str, 43 | id(ast.List): list, 44 | id(ast.ListComp): list, 45 | id(ast.Dict): dict, 46 | } 47 | 48 | def log(msg, *args): 49 | if DEBUG: 50 | with open(LOGFILE, 'a') as handle: 51 | handle.write(msg % args + '\n') 52 | 53 | def complete_inferred_types(): 54 | import vim 55 | engine = TypeInferenceEngine(vim.eval('source')) 56 | line = int(vim.eval('line')) 57 | column = int(vim.eval('column')) 58 | for name, types in engine.complete(line, column).iteritems(): 59 | fields = [name] 60 | for t in types: 61 | fields.append(t.__name__) 62 | print '|'.join(fields) 63 | 64 | class TypeInferenceEngine: 65 | 66 | def __init__(self, source): 67 | self.tree = ast.parse(source) 68 | self.link_parents(self.tree) 69 | 70 | def complete(self, line, column): 71 | node = self.find_node(line, column) 72 | if node: 73 | candidates = collections.defaultdict(list) 74 | for possible_type in self.evaluate(node): 75 | for name in dir(possible_type): 76 | candidates[name].append(possible_type) 77 | return candidates 78 | 79 | def link_parents(self, node): 80 | ''' Decorate the AST with child -> parent references. ''' 81 | for child in self.get_children(node): 82 | self.link_parents(child) 83 | child.parent = node 84 | 85 | def get_parents(self, node): 86 | ''' Yield all parent nodes of a given AST node. ''' 87 | while hasattr(node, 'parent'): 88 | node = node.parent 89 | yield node 90 | 91 | def get_children(self, node): 92 | ''' Get the nodes directly contained in a given AST node. ''' 93 | nodes = [] 94 | if isinstance(node, ast.If): 95 | # Might be nice to exclude "if False:" blocks here? :-) 96 | # TODO Take isinstance() into account as a type hint. 97 | nodes.append(node.test) 98 | nodes.extend(node.body) 99 | nodes.extend(node.orelse) 100 | elif isinstance(node, ast.Print): 101 | nodes.extend(node.values) 102 | elif isinstance(node, (ast.Expr, ast.Return)): 103 | nodes.append(node.value) 104 | elif isinstance(node, (ast.Module, ast.ClassDef)): 105 | nodes.extend(node.body) 106 | elif isinstance(node, ast.FunctionDef): 107 | nodes.extend(node.args.args) 108 | nodes.extend(node.args.defaults) 109 | if node.args.vararg: 110 | nodes.append(node.args.vararg) 111 | if node.args.kwarg: 112 | nodes.append(node.args.kwarg) 113 | nodes.extend(node.body) 114 | elif isinstance(node, ast.While): 115 | nodes.append(node.test) 116 | nodes.extend(node.body) 117 | nodes.extend(node.orelse) 118 | elif isinstance(node, ast.For): 119 | nodes.append(node.target) 120 | nodes.append(node.iter) 121 | nodes.extend(node.body) 122 | nodes.extend(node.orelse) 123 | elif isinstance(node, ast.Call): 124 | nodes.append(node.func) 125 | nodes.extend(node.args) 126 | nodes.extend(k.value for k in node.keywords) 127 | if node.starargs: 128 | nodes.append(node.starargs) 129 | if node.kwargs: 130 | nodes.append(node.kwargs) 131 | elif isinstance(node, (ast.Tuple, ast.List)): 132 | nodes.extend(node.elts) 133 | elif isinstance(node, ast.Dict): 134 | for i in xrange(len(node.values)): 135 | nodes.append(node.keys[i]) 136 | nodes.append(node.values[i]) 137 | elif isinstance(node, ast.Assign): 138 | nodes.extend(node.targets) 139 | nodes.append(node.value) 140 | elif isinstance(node, ast.Attribute): 141 | nodes.append(node.value) 142 | #nodes.append(node.attr) 143 | elif isinstance(node, ast.Import): 144 | #for imp in node.names: 145 | # nodes.append(imp.asname or imp.name) 146 | pass 147 | elif isinstance(node, ast.ImportFrom): 148 | log("ImportFrom: %s", dir(node)) 149 | node 150 | elif isinstance(node, (ast.Num, ast.Str, ast.Name, ast.Pass)): 151 | # terminals 152 | pass 153 | else: 154 | assert False, 'Node %s (%s) unsupported' % (node, type(node)) 155 | return nodes 156 | 157 | def find_node(self, lnum, column): 158 | ''' Find the node at the given (line, column) in the AST. ''' 159 | for node in ast.walk(self.tree): 160 | node_id = getattr(node, 'id', '') 161 | node_lnum = getattr(node, 'lineno', 0) 162 | node_col = getattr(node, 'col_offset', 0) 163 | if node_lnum == lnum and node_col <= column <= node_col + len(node_id): 164 | return node 165 | 166 | def evaluate(self, node): 167 | ''' Resolve an AST expression node to its primitive value(s). ''' 168 | key = id(type(node)) 169 | if key in AST_TYPES: 170 | # Constructor with special syntax (0, '', [], {}). 171 | yield AST_TYPES[key] 172 | elif type(node) == type: 173 | yield node 174 | elif isinstance(node, ast.Call): 175 | # Function call. 176 | # FIXME node.func can be an ast.Attribute! 177 | name = getattr(node.func, 'id', None) 178 | if name in BUILTINS: 179 | # Call to built-in (int(), str(), len(), max(), etc). 180 | yield BUILTINS[name] 181 | # Search return type(s) of user defined function(s). 182 | for func in self.find_function_definitions(node): 183 | for n in ast.walk(func): 184 | if isinstance(n, ast.Return): 185 | for result in self.evaluate(n.value): 186 | yield result 187 | elif isinstance(node, (ast.Tuple, ast.List)): 188 | if node.elts: 189 | for value in node.elts: 190 | yield value 191 | elif isinstance(node, ast.Tuple): 192 | yield tuple 193 | elif isinstance(node, ast.List): 194 | yield list 195 | elif isinstance(node, ast.Name): 196 | # Evaluate variable reference. 197 | for parent, kind, location in self.resolve(node): 198 | if isinstance(parent, ast.Assign): 199 | # Variable has been assigned. 200 | if len(parent.targets) > 1: 201 | # FIXME Prone to breaking on nested structures.. Use a path expression instead! 202 | values = self.flatten(parent.value, []) 203 | else: 204 | values = [parent.value] 205 | # Evaluate stand alone function call before unpacking? 206 | if len(values) == 1 and isinstance(values[0], ast.Call): 207 | values = list(self.evaluate(values[0])) 208 | for result in self.evaluate(values[location]): 209 | yield result 210 | elif kind == 'pos': 211 | # Evaluate function argument default value? 212 | num_required = len(parent.args.args) - len(parent.args.defaults) 213 | if location >= num_required: 214 | for result in self.evaluate(parent.args.defaults[location - num_required]): 215 | yield result 216 | # Check function arguments at potential call sites. 217 | for call in self.find_function_calls(parent): 218 | for result in self.evaluate(call.args[location]): 219 | yield result 220 | elif kind == 'var': 221 | yield tuple 222 | elif kind == 'kw': 223 | yield dict 224 | else: 225 | assert False 226 | 227 | def find_function_calls(self, node): 228 | ''' Yield the function/method calls that might be related to a node. ''' 229 | assert isinstance(node, ast.FunctionDef) 230 | for n in ast.walk(self.tree): 231 | if isinstance(n, ast.Call) and self.call_to_name(n) == node.name: 232 | yield n 233 | 234 | def find_function_definitions(self, node): 235 | ''' Yield the function definitions that might be related to a node. ''' 236 | assert isinstance(node, ast.Call) 237 | name = self.call_to_name(node) 238 | for n in ast.walk(self.tree): 239 | if isinstance(n, ast.FunctionDef) and n.name == name: 240 | yield n 241 | 242 | def call_to_name(self, node): 243 | ''' Translate a call to a function/method name. ''' 244 | assert isinstance(node, ast.Call) 245 | if isinstance(node.func, ast.Name): 246 | return node.func.id 247 | elif isinstance(node.func, ast.Attribute): 248 | return node.func.attr 249 | else: 250 | assert False 251 | 252 | def resolve(self, node): 253 | ''' Resolve an ast.Name node to its definition(s). ''' 254 | # TODO Class and function definitions are assignments as well! 255 | # TODO Import statements are assignments as well! 256 | sources = set() 257 | assert isinstance(node, ast.Name) 258 | for parent in self.get_parents(node): 259 | # Search for variable assignments in the current scope? 260 | if isinstance(parent, (ast.Module, ast.FunctionDef)): 261 | for n in ast.walk(parent): 262 | if isinstance(n, ast.Assign): 263 | for i, target in enumerate(self.flatten(n.targets, [])): 264 | if target.id == node.id: 265 | sources.add((n, 'asn', i)) 266 | # Search function arguments? 267 | if isinstance(parent, ast.FunctionDef): 268 | # Check the named positional arguments. 269 | for i, argument in enumerate(parent.args.args): 270 | if argument.id == node.id: 271 | sources.add((parent, 'pos', i)) 272 | # Check the tuple with other positional arguments. 273 | if parent.args.vararg and parent.args.vararg.id == node.id: 274 | sources.add((parent, 'var', parent.args.vararg)) 275 | # Check the dictionary with other keyword arguments. 276 | if parent.args.kwarg and parent.args.kwarg.id == node.id: 277 | sources.add(parent, 'kw', parent.args.kwarg) 278 | return sources 279 | 280 | def flatten(self, nested, flat): 281 | ''' Squash a nested sequence into a flat list of nodes. ''' 282 | if isinstance(nested, (ast.Tuple, ast.List)): 283 | for node in nested.elts: 284 | self.flatten(node, flat) 285 | elif isinstance(nested, (tuple, list)): 286 | for node in nested: 287 | self.flatten(node, flat) 288 | else: 289 | flat.append(nested) 290 | return flat 291 | 292 | ######################## 293 | ## Debugging helpers. ## 294 | ######################## 295 | 296 | def dump(self, node, level=0): 297 | ''' Print an AST subtree as a multi line indented string. ''' 298 | if isinstance(node, list): 299 | for n in node: 300 | self.dump(n, level) 301 | return 302 | if not isinstance(node, ast.Expr): 303 | print ' ' * level + '(' + type(node).__name__ + ') ' + self.format(node) 304 | level += 1 305 | for child in self.get_children(node): 306 | self.dump(child, level) 307 | 308 | def format(self, node): 309 | ''' Format an AST node as a string. ''' 310 | # Blocks. 311 | if isinstance(node, ast.Module): 312 | return 'module' 313 | elif isinstance(node, ast.ClassDef): 314 | return 'class ' + node.name 315 | elif isinstance(node, ast.FunctionDef): 316 | return 'function ' + node.name 317 | elif isinstance(node, ast.For): 318 | return 'for' 319 | elif isinstance(node, ast.While): 320 | return 'while' 321 | elif isinstance(node, ast.If): 322 | return 'if' 323 | elif isinstance(node, ast.Pass): 324 | return 'pass' 325 | elif isinstance(node, ast.Print): 326 | return 'print' 327 | elif isinstance(node, ast.Assign): 328 | return '%s=%s' % (', '.join(self.format(t) for t in node.targets), self.format(node.value)) 329 | # Expressions. 330 | elif isinstance(node, ast.Call): 331 | args = [self.format(a) for a in node.args] 332 | for keyword in node.keywords: 333 | args.append(keyword.arg + '=' + self.format(keyword.value)) 334 | if node.starargs: 335 | args.append('*' + self.format(node.starargs)) 336 | if node.kwargs: 337 | args.append('**' + self.format(node.kwargs)) 338 | name = self.format(node.func) 339 | return 'call %s(%s)' % (name, ', '.join(args)) 340 | elif isinstance(node, ast.Tuple): 341 | elts = ', '.join(self.format(e) for e in node.elts) 342 | return '(' + elts + ')' 343 | elif isinstance(node, ast.List): 344 | elts = ', '.join(self.format(e) for e in node.elts) 345 | return '[' + elts + ']' 346 | elif isinstance(node, ast.Dict): 347 | pairs = [] 348 | for i in xrange(len(node.values)): 349 | key = self.format(node.keys[i]) 350 | value = self.format(node.values[i]) 351 | pairs.append(key + ': ' + value) 352 | return '{' + ', '.join(pairs) + '}' 353 | elif isinstance(node, ast.Name): 354 | return node.id 355 | elif isinstance(node, ast.Attribute): 356 | return self.format(node.value) + '.' + self.format(node.attr) 357 | elif isinstance(node, ast.Num): 358 | return str(node.n) 359 | elif isinstance(node, ast.Str): 360 | return "'%s'" % node.s 361 | elif isinstance(node, ast.Expr): 362 | return self.format(node.value) 363 | elif isinstance(node, (str, unicode)): 364 | return node 365 | elif isinstance(node, collections.Iterable): 366 | values = ', '.join(self.format(v) for v in node) 367 | if isinstance(node, tuple): values = '(%s)' % values 368 | if isinstance(node, list): values = '[%s]' % values 369 | return values 370 | else: 371 | return str(node) 372 | -------------------------------------------------------------------------------- /autoload/python_ftplugin.vim: -------------------------------------------------------------------------------- 1 | " This Vim script was modified by a Python script that I use to manage the 2 | " inclusion of miscellaneous functions in the plug-ins that I publish to Vim 3 | " Online and GitHub. Please don't edit this file, instead make your changes on 4 | " the 'dev' branch of the git repository (thanks!). This file was generated on 5 | " May 23, 2013 at 22:18. 6 | 7 | " Vim autoload script 8 | " Authors: 9 | " - Peter Odding 10 | " - Bart Kroon 11 | " Last Change: May 23, 2013 12 | " URL: https://github.com/tarmack/vim-python-ftplugin 13 | 14 | let g:python_ftplugin#version = '0.6.17' 15 | let s:profile_dir = expand(':p:h:h') 16 | 17 | function! s:infer_types(base) " {{{1 18 | " TODO This is a quick hack that should be refactored and cleaned up! 19 | if !exists('s:inference_loaded') 20 | python import vim 21 | let scriptfile = s:profile_dir . '/misc/python-ftplugin/inference.py' 22 | execute 'pyfile' fnameescape(scriptfile) 23 | let s:inference_loaded = 1 24 | endif 25 | let line = line('.') 26 | let column = col('.') 27 | let lines = getline(1, '$') 28 | let cline = lines[line - 1] 29 | let before = cline[: column-1] 30 | let after = cline[column :] 31 | let temp = substitute(a:base, '\.[^.]*$', '', '') 32 | let lines[line - 1] = before . temp . after 33 | " XXX Without this ast.parse() will fail with a syntax error :-\ 34 | let source = join(lines, "\n") . "\n" 35 | try 36 | redir => listing 37 | silent python complete_inferred_types() 38 | redir END 39 | catch 40 | redir END 41 | return [] 42 | endtry 43 | let candidates = [] 44 | let pattern = '^' . python_ftplugin#misc#escape#pattern(a:base) 45 | for line in split(listing, '\n') 46 | let fields = split(line, '|') 47 | let word = temp . '.' . remove(fields, 0) 48 | if word =~ pattern 49 | call add(candidates, {'word': word, 'menu': '(' . join(sort(fields), ', ') . ')'}) 50 | endif 51 | endfor 52 | return candidates 53 | endfunction 54 | 55 | function! python_ftplugin#fold_text() " {{{1 56 | let line = getline(v:foldstart) 57 | if line =~ '^\s*#' 58 | " Comment block. 59 | let text = ['#'] 60 | for line in getline(v:foldstart, v:foldend) 61 | call extend(text, split(line)[1:]) 62 | endfor 63 | else 64 | let text = [] 65 | let lnum = v:foldstart 66 | 67 | " Prepend decorator names to foldtext. 68 | while line =~ '^\s*@' 69 | " If there are multiple decorators separate them with commas. 70 | if !empty(text) 71 | let text[-1] = text[-1] . "," 72 | endif 73 | if python_ftplugin#misc#option#get('python_decorators_in_foldtext', 1) 74 | let decorator = matchstr(line, '@[A-Za-z0-9_]*') 75 | let decorator_labels = python_ftplugin#misc#option#get('python_decorator_labels', {}) 76 | if has_key(decorator_labels, decorator[1:]) 77 | let decorator = '@' . decorator_labels[decorator[1:]] 78 | endif 79 | call add(text, decorator) 80 | endif 81 | let lnum += 1 82 | let line = getline(lnum) 83 | endwhile 84 | 85 | if line =~ '^\s*\(def\|class\)\>' 86 | " Class or function body. 87 | let line = python_ftplugin#misc#str#trim(line) 88 | call add(text, substitute(line, ':$', '', '')) 89 | " Fall through. 90 | let lnum += 1 91 | elseif line =~ '\S.*\("""\|''\{3}\)' 92 | " Multiline string. Include the code before the string. 93 | call add(text, matchstr(line, '\s*\zs.*\%("""\|''\{3}\)\ze')) 94 | endif 95 | if python_ftplugin#misc#option#get('python_docstring_in_foldtext', 1) 96 | " Show joined lines from docstring in fold text (can be slow). 97 | let haystack = join(getline(lnum, v:foldend)) 98 | let docstr = matchstr(haystack, '\("""\|''\{3}\)\zs\_.\{-}\ze\1') 99 | if docstr =~ '\S' 100 | if lnum > v:foldstart 101 | call add(text, '-') 102 | endif 103 | call extend(text, split(docstr)) 104 | endif 105 | else 106 | " Show first actual line of docstring. 107 | for line in getline(lnum, lnum + 5) 108 | if line =~ '\w' 109 | call add(text, python_ftplugin#misc#str#trim(line)) 110 | break 111 | endif 112 | endfor 113 | endif 114 | endif 115 | let numlines = v:foldend - v:foldstart + 1 116 | let format = "+%s %" . len(line('$')) . "d lines: %s " 117 | return printf(format, v:folddashes, numlines, join(text)) 118 | endfunction 119 | 120 | function! python_ftplugin#syntax_check() " {{{1 121 | if python_ftplugin#misc#option#get('python_check_syntax', 1) 122 | " Enable the user to override python_makeprg and python_error_format. 123 | let makeprg = python_ftplugin#misc#option#get('python_makeprg', '') 124 | let error_format = python_ftplugin#misc#option#get('python_error_format', '') 125 | " Use reasonable defaults for python_makeprg and python_error_format. 126 | if makeprg == '' || error_format == '' 127 | " Use pyflakes when available, fall-back to the Python compiler. 128 | if executable('pyflakes') 129 | let makeprg = 'pyflakes "%:p"' 130 | let error_format = '%A%f:%l: %m,%C%s,%Z%p^,%f:%l: %m' 131 | else 132 | let makeprg = 'python -c "import os, sys, py_compile; sys.stderr = sys.stdout; py_compile.compile(r''%:p''); os.path.isfile(''%:pc'') and os.unlink(''%:pc'')"' 133 | let error_format = "SyntaxError: ('%m'\\, ('%f'\\, %l\\, %c\\, '%s'))" 134 | endif 135 | endif 136 | " Make sure the syntax checker is installed. 137 | let progname = matchstr(makeprg, '^\S\+') 138 | if !executable(progname) 139 | let message = "python.vim %s: The configured syntax checker" 140 | let message .= " doesn't seem to be available! I'm disabling" 141 | let message .= " automatic syntax checking for Python scripts." 142 | if exists('b:python_makeprg') && b:python_makeprg == makeprg 143 | let b:python_check_syntax = 0 144 | else 145 | let g:python_check_syntax = 0 146 | endif 147 | call python_ftplugin#misc#msg#warn(message, g:python_ftplugin#version) 148 | else 149 | let mp_save = &makeprg 150 | let efm_save = &errorformat 151 | try 152 | let &makeprg = makeprg 153 | let &errorformat = error_format 154 | let winnr = winnr() 155 | call python_ftplugin#misc#msg#info('python.vim %s: Checking Python script syntax ..', g:python_ftplugin#version) 156 | execute 'silent make!' 157 | cwindow 158 | if winnr() != winnr 159 | let w:quickfix_title = 'Issues reported by ' . progname 160 | execute winnr . 'wincmd w' 161 | endif 162 | redraw 163 | echo '' 164 | finally 165 | let &makeprg = mp_save 166 | let &errorformat = efm_save 167 | endtry 168 | endif 169 | endif 170 | endfunction 171 | 172 | function! python_ftplugin#jump(motion) range " {{{1 173 | let cnt = v:count1 174 | let save = @/ " save last search pattern 175 | mark ' 176 | while cnt > 0 177 | silent! exe a:motion 178 | let cnt = cnt - 1 179 | endwhile 180 | call histdel('/', -1) 181 | let @/ = save " restore last search pattern 182 | endfun 183 | 184 | function! python_ftplugin#include_expr(fname) " {{{1 185 | call s:load_python_script() 186 | redir => output 187 | silent python find_module_path(vim.eval('a:fname')) 188 | redir END 189 | return python_ftplugin#misc#str#trim(output) 190 | endfunction 191 | 192 | function! python_ftplugin#omni_complete(findstart, base) " {{{1 193 | if a:findstart 194 | return s:find_start('variable') 195 | else 196 | let starttime = python_ftplugin#misc#timer#start() 197 | let candidates = [] 198 | if s:do_variable_completion(a:base[-1:]) 199 | let base = a:base 200 | if match(s:get_continued_line(), '\') >= 0 201 | let from = s:get_base_module() 202 | if !empty(from) 203 | let base = from . '.' . a:base 204 | endif 205 | else 206 | let imports = s:get_imports(a:base) 207 | endif 208 | call s:load_python_script() 209 | redir => listing 210 | silent python complete_variables(vim.eval('base')) 211 | redir END 212 | let completes = split(listing, '\n') 213 | let pattern = '^' . python_ftplugin#misc#escape#pattern(base) 214 | call filter(completes, 'v:val =~# pattern') 215 | if exists('from') && !empty(from) 216 | call map(completes, 'v:val[len(from) + 1 :]') 217 | endif 218 | call extend(candidates, completes) 219 | if exists('imports') 220 | redir => listing 221 | let base = a:base[stridx(a:base, '.')+1 :] 222 | for module in keys(imports) 223 | let module = imports[module] . '.' . base 224 | silent python complete_variables(vim.eval('module')) 225 | endfor 226 | redir END 227 | let completes = split(listing, '\n') 228 | for module in keys(imports) 229 | let pattern = '^' . python_ftplugin#misc#escape#pattern(imports[module]) . '\.' 230 | let index = len(imports[module]) 231 | for compl in completes 232 | if compl =~# pattern 233 | call add(candidates, module . '.' . compl[index+1:]) 234 | endif 235 | endfor 236 | endfor 237 | endif 238 | endif 239 | if s:do_module_completion(a:base[-1:]) 240 | if !exists('imports') 241 | let imports = s:get_imports(a:base) 242 | endif 243 | call extend(candidates, s:add_modules(a:base, imports)) 244 | endif 245 | " Filter the completion candidates according to the given base. 246 | let pattern = '^' . python_ftplugin#misc#escape#pattern(a:base) 247 | call filter(candidates, 'v:val =~# pattern') 248 | let pattern = pattern . '\.' 249 | call filter(candidates, 'v:val !~ pattern') 250 | " Convert the completion candidates to dictionaries to make them 251 | " compatible with the candidates generated by the type inference engine. 252 | call map(candidates, '{"word": v:val}') 253 | " Add the completion candidates suggested by the type inference engine. 254 | call extend(candidates, s:infer_types(a:base)) 255 | " Sort the completion candidates. 256 | call sort(candidates, 's:friendly_sort') 257 | " Provide some feedback in case of :verbose. 258 | call python_ftplugin#misc#timer#stop("python.vim %s: Found %s completion candidates in %s.", g:python_ftplugin#version, len(candidates), starttime) 259 | return candidates 260 | endif 261 | endfunction 262 | 263 | function! s:friendly_sort(a, b) " {{{1 264 | let a = substitute(tolower(a:a['word']), '_', '\~', 'g') 265 | let b = substitute(tolower(a:b['word']), '_', '\~', 'g') 266 | return a < b ? -1 : a > b ? 1 : 0 267 | endfunction 268 | 269 | function! s:add_modules(base, imports) " {{{1 270 | " Returns a list of completion candidates for base. Takes the imports 271 | " mapping as created by s:get_imports(). 272 | let candidates = s:find_modules(a:base) 273 | if !empty(a:imports) 274 | let base = a:base[stridx(a:base, '.')+1 :] 275 | for name in keys(a:imports) 276 | let module = a:imports[name] . '.' . base 277 | let completes = s:find_modules(module) 278 | let pattern = '^' . python_ftplugin#misc#escape#pattern(a:imports[name]) . '\.' 279 | let index = len(a:imports[name]) 280 | for compl in completes 281 | if compl =~# pattern 282 | call add(candidates, name . '.' . compl[index+1:]) 283 | endif 284 | endfor 285 | endfor 286 | endif 287 | return candidates 288 | endfunction 289 | 290 | function! s:find_modules(base) " {{{1 291 | let start_load = python_ftplugin#misc#timer#start() 292 | let todo = [] 293 | let fromstr = s:get_base_module() 294 | let from = split(fromstr, '\.') 295 | call extend(todo, from) 296 | call extend(todo, split(a:base, '\.')) 297 | let done = [] 298 | let items = [] 299 | let node = python_ftplugin#get_modules([], s:module_completion_cache) 300 | while !empty(todo) 301 | if len(todo) == 1 302 | " Include items from the containing node that start with the last item. 303 | let keys = keys(node) 304 | let pattern = '^' . python_ftplugin#misc#escape#pattern(todo[0]) 305 | call filter(keys, "v:val =~# pattern") 306 | for key in keys 307 | call add(items, join(done + [key], '.')) 308 | endfor 309 | endif 310 | if has_key(node, todo[0]) 311 | let name = remove(todo, 0) 312 | call add(done, name) 313 | let node = python_ftplugin#get_modules(done, node[name]) 314 | else 315 | break 316 | endif 317 | endwhile 318 | if len(from) > len(done) 319 | let node = {} 320 | endif 321 | let done = done[len(from) :] 322 | let keys = keys(node) 323 | for key in keys 324 | call add(items, join(done + [key], '.')) 325 | endfor 326 | call python_ftplugin#misc#timer#stop("python.vim %s: Found %i module names in %s.", g:python_ftplugin#version, len(items), start_load) 327 | return items 328 | endfunction 329 | 330 | function! s:get_base_module() " {{{1 331 | return matchstr(s:get_continued_line(), '\<\(from\s\+\)\@<=[A-Za-z0-9_.]\+\(\s\+import\s\+\([A-Za-z0-9_,]\s*\)*\)\@=') 332 | endfunction 333 | 334 | function! s:get_imports(base) " {{{1 335 | " When completing regular code search for 'from import' and 'import as' lines 336 | " that match the base we are looking for. 337 | if empty(a:base) || a:base =~ '^\.\+$' 338 | return {} 339 | endif 340 | let i = stridx(a:base, '.') 341 | let base = i >= 0 ? a:base[: i] : a:base 342 | let imports = {} 343 | let cursor_save = getpos('.') 344 | call cursor(1, 1) 345 | let base = python_ftplugin#misc#escape#pattern(split(a:base, '\.')[0]) 346 | while search('\', 'cW', 0, 500) && s:syntax_is_code() 347 | " Get the full import line and remove any trailing comments. 348 | let line_no = line('.') 349 | let line = split(getline(line_no), '\s*#')[0] 350 | while match(line, '\\$') >= 0 351 | let line_no += 1 352 | let line = line[0 : -2] . get(split(getline(line_no), '\s*#'), 0, '') 353 | endwhile 354 | if match(line, '\.\{-}\<' . base) >= 0 355 | 356 | " Get the from part if it is pressent. 357 | let from = matchstr(line, '\<\%(from\s\+\)\@<=[A-Za-z0-9_.]\+\%(\s\+import\>\)\@=') 358 | " Get the module if this is a 'import as' line. 359 | let module = matchstr(line, '\<\%(import\s\+\)\@<=[A-Za-z0-9_.]\+\%(\s\+as\s\+' . base . '\)\@=') 360 | if !empty(module) 361 | " If the line is a 'import as' line, get the name it is imported as. 362 | let names = [split(line, '\s*as\s*')[-1]] 363 | else 364 | " If there is no 'as' get the list of imported names. 365 | let names = split(line, ',\s*') 366 | let names[0] = split(names[0], '\s\+')[-1] 367 | endif 368 | 369 | for name in filter(names, 'v:val =~ "^' . base . '"') 370 | if empty(module) 371 | let imports[name] = from . '.' . name 372 | elseif empty(from) 373 | let imports[name] = module 374 | else 375 | let imports[name] = from . '.' . module 376 | endif 377 | endfor 378 | unlet from module names line 379 | endif 380 | call cursor(line_no + 1, 1) 381 | endwhile 382 | call setpos('.', cursor_save) 383 | return imports 384 | endfunction 385 | 386 | function! python_ftplugin#aaa(base) 387 | return s:get_imports(a:base) 388 | endfunction 389 | 390 | function! python_ftplugin#auto_complete(chr) " {{{1 391 | if a:chr == '.' && search('\%#[A-Za-z0-9._]', 'cn', line('.')) 392 | " Don't auto complete when typing in between parts of code. 393 | return a:chr 394 | elseif a:chr == ' ' 395 | \ && search('\ call s:restore_completeopt() 425 | augroup END 426 | " Enter character and start completion. 427 | return a:chr . result 428 | endif 429 | return a:chr 430 | endfunction 431 | 432 | function! s:do_module_completion(chr) " {{{1 433 | let chr = a:chr 434 | if chr == '' 435 | let chr = ' ' 436 | endif 437 | let line = s:get_continued_line() 438 | 439 | let complete = s:do_completion_always(chr, line) 440 | if complete != -1 441 | return complete 442 | 443 | " Complete module names in the first part of a from XX import YY line. 444 | elseif match(line, '\= 0 445 | " When a space is typed after the module name do not complete. 446 | if chr == ' ' && match(line, '\= 0 447 | return 0 448 | endif 449 | return 1 450 | 451 | " Complete modules after an import statement not part of a from XX import YY 452 | " line. But only when the last non whitespace character after a preceding 453 | " name is a comma. 454 | elseif match(line, '\= 0 455 | \ && match(line, '\= 0 480 | return 1 481 | 482 | " Don't complete variables when from or import is the only keyword preceding 483 | " the cursor on the line. 484 | elseif match(line, '\<\(from\|import\).*$') >= 0 485 | return 0 486 | endif 487 | return 1 488 | endfunction 489 | 490 | function! s:do_completion_always(chr, line) " {{{1 491 | " Function to check if completion should be started regardless of type. 492 | " Returns 1 when completion should be started. 493 | " Returns 0 if it should definitely not be started. 494 | " Returns -1 when no conclusion can be drawn. (i.e. completion for only one type should 495 | " start.) 496 | 497 | " Do not complete when typing a comment or string literal. 498 | if !s:syntax_is_code() 499 | return 0 500 | 501 | " Don't complete after 'self'. 502 | elseif a:line =~ '\= 0 507 | " When a space is typed check for comma separator. 508 | if a:chr == ' ' && match(a:line, '\(\= 0 531 | let line_no -= 1 532 | let line = getline(line_no)[0 : -2] . line 533 | endwhile 534 | return line 535 | endfunction 536 | 537 | function! s:find_start(type) " {{{1 538 | let prefix = getline('.')[0 : col('.')-2] 539 | let ident = matchstr(prefix, '[A-Za-z0-9_.]\+$') 540 | call python_ftplugin#misc#msg#debug("python.vim %s: Completing %s `%s'.", g:python_ftplugin#version, a:type, ident) 541 | return col('.') - len(ident) - 1 542 | endfunction 543 | 544 | function! python_ftplugin#get_modules(base, node) " {{{2 545 | if empty(a:node) 546 | call s:load_python_script() 547 | redir => listing 548 | silent python complete_modules(vim.eval("join(a:base, '.')")) 549 | redir END 550 | let lines = split(listing, '\n') 551 | for token in lines 552 | if !has_key(a:node, token) 553 | let a:node[token] = {} 554 | endif 555 | endfor 556 | endif 557 | return a:node 558 | endfunction 559 | 560 | let s:module_completion_cache = {} 561 | 562 | function! s:load_python_script() " {{{2 563 | if !exists('s:python_script_loaded') 564 | python import vim 565 | let scriptfile = s:profile_dir . '/misc/python-ftplugin/support.py' 566 | execute 'pyfile' fnameescape(scriptfile) 567 | let s:python_script_loaded = 1 568 | endif 569 | endfunction 570 | 571 | function! s:restore_completeopt() " {{{1 572 | " Restore the original value of &completeopt. 573 | if exists('b:python_cot_save') 574 | let &completeopt = b:python_cot_save 575 | unlet b:python_cot_save 576 | endif 577 | " Disable the automatic command that called us. 578 | augroup PluginFileTypePython 579 | autocmd! CursorHold,CursorHoldI 580 | augroup END 581 | endfunction 582 | 583 | " vim: ts=2 sw=2 sts=2 et 584 | --------------------------------------------------------------------------------