├── README.rst ├── plugin └── yapf_format.vim └── python └── yapf_format.py /README.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | vim-yapf-format 3 | =============== 4 | 5 | VIM integration for YAPF_. 6 | 7 | 8 | Installation 9 | ============ 10 | 11 | If you use Vundle_, add to your ``.vimrc``: 12 | 13 | .. code:: 14 | 15 | Plugin 'pignacio/vim-yapf-format' 16 | 17 | or with pathogen.vim_: 18 | 19 | .. code:: sh 20 | 21 | cd ~/.vim/bundle 22 | git clone git://github.com/pignacio/vim-yapf-format 23 | 24 | Requirements 25 | ============ 26 | 27 | * YAPF_ 28 | 29 | **NOTE:** 30 | 31 | If ``YAPF`` is not in your ``sys.path`` you have to add the following line in 32 | your ``.vimrc``: 33 | 34 | .. code:: vim 35 | 36 | let g:yapf_format_yapf_location = '/path/to/yapf' 37 | 38 | Usage 39 | ===== 40 | 41 | * The ``YapfFullFormat`` command formats the whole file and tries to restore 42 | the cursor position after formatting. 43 | 44 | **NOTE:** Right now we can't do much about the cursor, so ``YapfFullFormat`` 45 | just tries to restore it to the same line and column it was before formatting. 46 | Maybe if ``yapf`` implements something like ``clang-format.py`` ``--cursor`` 47 | argument, we can improve this. 48 | 49 | 50 | * Use the ``YapfFormat`` command to format the current line or range. The 51 | cursor is reset to the beggining of the formatted range. Some examples: 52 | 53 | .. code:: vim 54 | 55 | " Reformat current line 56 | :YapfFormat 57 | 58 | " Reformat current visual range 59 | :'<,'>YapfFormat 60 | 61 | " In general, reformat 62 | :YapfFormat 63 | 64 | " Don't do this, use YapFullFormat! 65 | :%YapfFormat 66 | 67 | 68 | The style used to format the buffer is checking the following in order: 69 | 70 | * ``b:yapf_format_style`` variable. 71 | 72 | * Local ``.yapf.style`` for the file's project (or current directory for 73 | unsaved files). 74 | 75 | * ``g:yapf_format_style`` variable. 76 | 77 | * ``pep8`` 78 | 79 | Configuration 80 | ============= 81 | 82 | * **Out of range changes:** 83 | 84 | YAPF fixes code (indentation fixes, for example) outside of the ``--lines`` 85 | range. 86 | 87 | This produces unexpected changes when using the VISUAL reformat, and makes 88 | editing and partially reformatting a non-YAPF-compliant file vey cumbersome. 89 | 90 | We avoid this behaviour by default, applying changes only in the direct 91 | proximity of the selected range. 92 | 93 | If you like the original behaviour, you can restore it setting 94 | 95 | .. code:: vim 96 | 97 | let g:yapf_format_allow_out_of_range_changes = 1 98 | 99 | * **Moving cursor to errors:** 100 | 101 | YAPF fails when the buffer contains syntax errors. When that happens during a 102 | reformat, the cursor is moved to the error location. To disable this behaviour, 103 | you can set: 104 | 105 | .. code:: vim 106 | 107 | let g:yapf_format_move_to_error = 0 108 | 109 | Key Bindings 110 | ============ 111 | 112 | I use the following key bindings to reformat the whole file in normal mode, 113 | the current line in insert mode and the current range in visual mode: 114 | 115 | .. code:: vim 116 | 117 | map :YapfFullFormat 118 | imap :YapfFormati 119 | vmap :YapfFormat 120 | 121 | Of course, the ```` can be changed to any key you like ;) 122 | 123 | 124 | Credits 125 | ======= 126 | 127 | This script is heavily inspired by clang-format.py_ 128 | 129 | 130 | .. _YAPF: https://github.com/google/yapf 131 | .. _Vundle: https://github.com/gmarik/vundle 132 | .. _pathogen.vim: https://github.com/tpope/vim-pathogen 133 | .. _clang-format.py: 134 | https://llvm.org/svn/llvm-project/cfe/trunk/tools/clang-format/clang-format.py 135 | -------------------------------------------------------------------------------- /plugin/yapf_format.vim: -------------------------------------------------------------------------------- 1 | " yapf_format.vim - YAPF vim integration 2 | " Author: Ignacio Rossi 3 | " Version: 0.0.1 4 | 5 | if exists("g:yapf_format_loaded") 6 | finish 7 | endif 8 | 9 | if ! has('python3') 10 | echohl WarningMsg | 11 | \ echomsg "vim-yapf-format requires vim compiled with python support" | 12 | \ echohl None 13 | finish 14 | endif 15 | 16 | function! GetVar(...) 17 | let l:varName = a:1 18 | let l:varValue = a:2 19 | if exists('b:yapf_format_' . l:varName) 20 | exe "let l:varValue = b:yapf_format_" . l:varName 21 | elseif exists('g:yapf_format_' . l:varName) 22 | exe "let l:varValue = g:yapf_format_" . l:varName 23 | endif 24 | return l:varValue 25 | endfunction 26 | 27 | let g:yapf_format_loaded = 1 28 | 29 | let s:script_folder_path = escape( expand( ':p:h' ), '\' ) 30 | let s:yapf_format_script = s:script_folder_path . '/../python/yapf_format.py' 31 | let s:appended_yapf_path = 0 32 | 33 | command! -range YapfFormat ,call YapfFormat() 34 | function! YapfFormat() range 35 | if ! s:appended_yapf_path 36 | if exists("g:yapf_format_yapf_location") 37 | py3 import sys 38 | exe 'py3 sys.path.append("' . expand(g:yapf_format_yapf_location) . '")' 39 | endif 40 | let s:appended_yapf_path = 1 41 | endif 42 | 43 | if exists('b:yapf_format_style') 44 | let l:buffer_style = b:yapf_format_style 45 | else 46 | let l:buffer_style = '' 47 | endif 48 | 49 | if exists('g:yapf_format_style') 50 | let l:global_style = g:yapf_format_style 51 | else 52 | let l:global_style = 'pep8' 53 | endif 54 | 55 | let l:allow_out_of_range = exists('g:yapf_format_allow_out_of_range_changes')? 56 | \ !!g:yapf_format_allow_out_of_range_changes : 57 | \ 0 58 | 59 | exe a:firstline . ',' . a:lastline . 'py3file ' . s:yapf_format_script 60 | 61 | 62 | if exists('l:error_type') 63 | let l:error_line = l:error_position[0] 64 | let l:error_column = l:error_position[1] 65 | echohl ErrorMsg | 66 | \ echon " " . l:error_type . " @ (" . l:error_line . ', ' . 67 | \ l:error_column . ')' | echohl None 68 | if GetVar('move_to_error', 1) == 1 69 | let l:position = getpos('.') 70 | let l:position[1] = l:error_line 71 | let l:position[2] = l:error_column 72 | call setpos('.', l:position) 73 | endif 74 | return 1 75 | endif 76 | echon "Used style: " . l:used_style . " " 77 | return 0 78 | endfunction 79 | 80 | command! YapfFullFormat call YapfFullFormat() 81 | function! YapfFullFormat() 82 | let l:cursor_pos = getpos(".") 83 | redir => l:message 84 | %YapfFormat 85 | redir END 86 | if l:message !~ '' || GetVar('move_to_error', 1) != 1 87 | call setpos(".", l:cursor_pos) 88 | endif 89 | endfunction 90 | -------------------------------------------------------------------------------- /python/yapf_format.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals, division 2 | 3 | import difflib 4 | import sys 5 | import os 6 | import vim 7 | 8 | try: 9 | from yapf.yapflib import yapf_api, file_resources, style 10 | _YAPF_IMPORTED = True 11 | except ImportError: 12 | sys.stderr.write("Could not import yapf_api. Have you set " 13 | "g:yapf_format_yapf_location correctly?") 14 | _YAPF_IMPORTED = False 15 | 16 | 17 | def _get_file_style(): 18 | path = vim.current.buffer.name or os.getcwd() 19 | project_style = file_resources.GetDefaultStyleForDir(path) 20 | if project_style == style.DEFAULT_STYLE: 21 | return None 22 | return project_style 23 | 24 | 25 | def _get_style(): 26 | return (vim.eval('l:buffer_style') or _get_file_style() or 27 | vim.eval('l:global_style')) 28 | 29 | 30 | def main(): 31 | if not _YAPF_IMPORTED: 32 | return 33 | 34 | encoding = vim.eval('&encoding') 35 | buf = vim.current.buffer 36 | text = '\n'.join(buf) 37 | buf_range = (vim.current.range.start, vim.current.range.end) 38 | lines_range = [pos + 1 for pos in buf_range] 39 | style_config = _get_style() 40 | vim.command('let l:used_style = "{}"'.format(style_config)) 41 | try: 42 | formatted = yapf_api.FormatCode(text, 43 | filename='', 44 | style_config=style_config, 45 | lines=[lines_range], 46 | verify=False) 47 | except (SyntaxError, IndentationError) as err: 48 | vim.command('let l:error_type = "{}"'.format(type(err).__name__)) 49 | vim.command('let l:error_position = [{}, {}]'.format(err.lineno, 50 | err.offset)) 51 | return 52 | 53 | if isinstance(formatted, tuple): 54 | formatted = formatted[0] 55 | 56 | lines = formatted.rstrip('\n').split('\n') 57 | sequence = difflib.SequenceMatcher(None, buf, lines) 58 | 59 | allow_out_of_range = vim.eval("l:allow_out_of_range") != "0" 60 | 61 | for op in reversed(sequence.get_opcodes()): 62 | if op[0] == 'equal': 63 | continue 64 | # buf_range is [closed, closed], and op is [closed,open) 65 | # so we must offset buf_range[1] 66 | in_range = max(buf_range[0], op[1]) <= min(buf_range[1] + 1, op[2]) 67 | if in_range or allow_out_of_range: 68 | vim.current.buffer[op[1]:op[2]] = [l.encode(encoding) 69 | for l in lines[op[3]:op[4]]] 70 | 71 | 72 | if __name__ == '__main__': 73 | main() 74 | --------------------------------------------------------------------------------