1 # This file is a minimal clang-format vim-integration. To install: 2 # - Change 'binary' if clang-format is not on the path (see below). 3 # - Add to your .vimrc: 4 # 5 # map <C-I> :pyf <path-to-this-file>/clang-format.py<cr> 6 # imap <C-I> <c-o>:pyf <path-to-this-file>/clang-format.py<cr> 7 # 8 # The first line enables clang-format for NORMAL and VISUAL mode, the second 9 # line adds support for INSERT mode. Change "C-I" to another binding if you 10 # need clang-format on a different key (C-I stands for Ctrl+i). 11 # 12 # With this integration you can press the bound key and clang-format will 13 # format the current line in NORMAL and INSERT mode or the selected region in 14 # VISUAL mode. The line or region is extended to the next bigger syntactic 15 # entity. 16 # 17 # You can also pass in the variable "l:lines" to choose the range for 18 # formatting. This variable can either contain "<start line>:<end line>" or 19 # "all" to format the full file. So, to format the full file, write a function 20 # like: 21 # :function FormatFile() 22 # : let l:lines="all" 23 # : pyf <path-to-this-file>/clang-format.py 24 # :endfunction 25 # 26 # It operates on the current, potentially unsaved buffer and does not create 27 # or save any files. To revert a formatting, just undo. 28 from __future__ import print_function 29 30 import difflib 31 import json 32 import platform 33 import subprocess 34 import sys 35 import vim 36 37 # set g:clang_format_path to the path to clang-format if it is not on the path 38 # Change this to the full path if clang-format is not on the path. 39 binary = 'clang-format' 40 if vim.eval('exists("g:clang_format_path")') == "1": 41 binary = vim.eval('g:clang_format_path') 42 43 # Change this to format according to other formatting styles. See the output of 44 # 'clang-format --help' for a list of supported styles. The default looks for 45 # a '.clang-format' or '_clang-format' file to indicate the style that should be 46 # used. 47 style = 'file' 48 fallback_style = None 49 if vim.eval('exists("g:clang_format_fallback_style")') == "1": 50 fallback_style = vim.eval('g:clang_format_fallback_style') 51 52 def get_buffer(encoding): 53 if platform.python_version_tuple()[0] == '3': 54 return vim.current.buffer 55 return [ line.decode(encoding) for line in vim.current.buffer ] 56 57 def main(): 58 # Get the current text. 59 encoding = vim.eval("&encoding") 60 buf = get_buffer(encoding) 61 text = '\n'.join(buf) 62 63 # Determine range to format. 64 if vim.eval('exists("l:lines")') == '1': 65 lines = vim.eval('l:lines') 66 else: 67 lines = '%s:%s' % (vim.current.range.start + 1, vim.current.range.end + 1) 68 69 # Determine the cursor position. 70 cursor = int(vim.eval('line2byte(line("."))+col(".")')) - 2 71 if cursor < 0: 72 print('Couldn\'t determine cursor position. Is your file empty?') 73 return 74 75 # Avoid flashing an ugly, ugly cmd prompt on Windows when invoking clang-format. 76 startupinfo = None 77 if sys.platform.startswith('win32'): 78 startupinfo = subprocess.STARTUPINFO() 79 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 80 startupinfo.wShowWindow = subprocess.SW_HIDE 81 82 # Call formatter. 83 command = [binary, '-style', style, '-cursor', str(cursor)] 84 if lines != 'all': 85 command.extend(['-lines', lines]) 86 if fallback_style: 87 command.extend(['-fallback-style', fallback_style]) 88 if vim.current.buffer.name: 89 command.extend(['-assume-filename', vim.current.buffer.name]) 90 p = subprocess.Popen(command, 91 stdout=subprocess.PIPE, stderr=subprocess.PIPE, 92 stdin=subprocess.PIPE, startupinfo=startupinfo) 93 stdout, stderr = p.communicate(input=text.encode(encoding)) 94 95 # If successful, replace buffer contents. 96 if stderr: 97 print(stderr) 98 99 if not stdout: 100 print( 101 'No output from clang-format (crashed?).\n' 102 'Please report to bugs.llvm.org.' 103 ) 104 else: 105 lines = stdout.decode(encoding).split('\n') 106 output = json.loads(lines[0]) 107 lines = lines[1:] 108 sequence = difflib.SequenceMatcher(None, buf, lines) 109 for op in reversed(sequence.get_opcodes()): 110 if op[0] is not 'equal': 111 vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]] 112 if output.get('IncompleteFormat'): 113 print('clang-format: incomplete (syntax errors)') 114 vim.command('goto %d' % (output['Cursor'] + 1)) 115 116 main() 117