1e5dd7070Spatrick# This file is a minimal clang-format vim-integration. To install: 2e5dd7070Spatrick# - Change 'binary' if clang-format is not on the path (see below). 3e5dd7070Spatrick# - Add to your .vimrc: 4e5dd7070Spatrick# 5e5dd7070Spatrick# if has('python') 6e5dd7070Spatrick# map <C-I> :pyf <path-to-this-file>/clang-format.py<cr> 7e5dd7070Spatrick# imap <C-I> <c-o>:pyf <path-to-this-file>/clang-format.py<cr> 8e5dd7070Spatrick# elseif has('python3') 9e5dd7070Spatrick# map <C-I> :py3f <path-to-this-file>/clang-format.py<cr> 10e5dd7070Spatrick# imap <C-I> <c-o>:py3f <path-to-this-file>/clang-format.py<cr> 11e5dd7070Spatrick# endif 12e5dd7070Spatrick# 13e5dd7070Spatrick# The if-elseif-endif conditional should pick either the python3 or python2 14e5dd7070Spatrick# integration depending on your vim setup. 15e5dd7070Spatrick# 16e5dd7070Spatrick# The first mapping enables clang-format for NORMAL and VISUAL mode, the second 17e5dd7070Spatrick# mapping adds support for INSERT mode. Change "C-I" to another binding if you 18e5dd7070Spatrick# need clang-format on a different key (C-I stands for Ctrl+i). 19e5dd7070Spatrick# 20e5dd7070Spatrick# With this integration you can press the bound key and clang-format will 21e5dd7070Spatrick# format the current line in NORMAL and INSERT mode or the selected region in 22e5dd7070Spatrick# VISUAL mode. The line or region is extended to the next bigger syntactic 23e5dd7070Spatrick# entity. 24e5dd7070Spatrick# 25e5dd7070Spatrick# You can also pass in the variable "l:lines" to choose the range for 26e5dd7070Spatrick# formatting. This variable can either contain "<start line>:<end line>" or 27e5dd7070Spatrick# "all" to format the full file. So, to format the full file, write a function 28e5dd7070Spatrick# like: 29e5dd7070Spatrick# :function FormatFile() 30e5dd7070Spatrick# : let l:lines="all" 31e5dd7070Spatrick# : if has('python') 32e5dd7070Spatrick# : pyf <path-to-this-file>/clang-format.py 33e5dd7070Spatrick# : elseif has('python3') 34e5dd7070Spatrick# : py3f <path-to-this-file>/clang-format.py 35e5dd7070Spatrick# : endif 36e5dd7070Spatrick# :endfunction 37e5dd7070Spatrick# 38e5dd7070Spatrick# It operates on the current, potentially unsaved buffer and does not create 39e5dd7070Spatrick# or save any files. To revert a formatting, just undo. 40e5dd7070Spatrickfrom __future__ import absolute_import, division, print_function 41e5dd7070Spatrick 42e5dd7070Spatrickimport difflib 43e5dd7070Spatrickimport json 44*12c85518Srobertimport os.path 45e5dd7070Spatrickimport platform 46e5dd7070Spatrickimport subprocess 47e5dd7070Spatrickimport sys 48e5dd7070Spatrickimport vim 49e5dd7070Spatrick 50e5dd7070Spatrick# set g:clang_format_path to the path to clang-format if it is not on the path 51e5dd7070Spatrick# Change this to the full path if clang-format is not on the path. 52e5dd7070Spatrickbinary = 'clang-format' 53e5dd7070Spatrickif vim.eval('exists("g:clang_format_path")') == "1": 54e5dd7070Spatrick binary = vim.eval('g:clang_format_path') 55e5dd7070Spatrick 56e5dd7070Spatrick# Change this to format according to other formatting styles. See the output of 57e5dd7070Spatrick# 'clang-format --help' for a list of supported styles. The default looks for 58e5dd7070Spatrick# a '.clang-format' or '_clang-format' file to indicate the style that should be 59e5dd7070Spatrick# used. 60e5dd7070Spatrickstyle = None 61e5dd7070Spatrickfallback_style = None 62e5dd7070Spatrickif vim.eval('exists("g:clang_format_fallback_style")') == "1": 63e5dd7070Spatrick fallback_style = vim.eval('g:clang_format_fallback_style') 64e5dd7070Spatrick 65e5dd7070Spatrickdef get_buffer(encoding): 66e5dd7070Spatrick if platform.python_version_tuple()[0] == '3': 67e5dd7070Spatrick return vim.current.buffer 68e5dd7070Spatrick return [ line.decode(encoding) for line in vim.current.buffer ] 69e5dd7070Spatrick 70e5dd7070Spatrickdef main(): 71e5dd7070Spatrick # Get the current text. 72e5dd7070Spatrick encoding = vim.eval("&encoding") 73e5dd7070Spatrick buf = get_buffer(encoding) 74e5dd7070Spatrick # Join the buffer into a single string with a terminating newline 75ec727ea7Spatrick text = ('\n'.join(buf) + '\n').encode(encoding) 76e5dd7070Spatrick 77e5dd7070Spatrick # Determine range to format. 78e5dd7070Spatrick if vim.eval('exists("l:lines")') == '1': 79e5dd7070Spatrick lines = ['-lines', vim.eval('l:lines')] 80*12c85518Srobert elif vim.eval('exists("l:formatdiff")') == '1' and \ 81*12c85518Srobert os.path.exists(vim.current.buffer.name): 82e5dd7070Spatrick with open(vim.current.buffer.name, 'r') as f: 83e5dd7070Spatrick ondisk = f.read().splitlines(); 84e5dd7070Spatrick sequence = difflib.SequenceMatcher(None, ondisk, vim.current.buffer) 85e5dd7070Spatrick lines = [] 86e5dd7070Spatrick for op in reversed(sequence.get_opcodes()): 87e5dd7070Spatrick if op[0] not in ['equal', 'delete']: 88e5dd7070Spatrick lines += ['-lines', '%s:%s' % (op[3] + 1, op[4])] 89e5dd7070Spatrick if lines == []: 90e5dd7070Spatrick return 91e5dd7070Spatrick else: 92e5dd7070Spatrick lines = ['-lines', '%s:%s' % (vim.current.range.start + 1, 93e5dd7070Spatrick vim.current.range.end + 1)] 94e5dd7070Spatrick 95ec727ea7Spatrick # Convert cursor (line, col) to bytes. 96ec727ea7Spatrick # Don't use line2byte: https://github.com/vim/vim/issues/5930 97ec727ea7Spatrick _, cursor_line, cursor_col, _ = vim.eval('getpos(".")') # 1-based 98ec727ea7Spatrick cursor_byte = 0 99ec727ea7Spatrick for line in text.split(b'\n')[:int(cursor_line) - 1]: 100ec727ea7Spatrick cursor_byte += len(line) + 1 101ec727ea7Spatrick cursor_byte += int(cursor_col) - 1 102ec727ea7Spatrick if cursor_byte < 0: 103e5dd7070Spatrick print('Couldn\'t determine cursor position. Is your file empty?') 104e5dd7070Spatrick return 105e5dd7070Spatrick 106e5dd7070Spatrick # Avoid flashing an ugly, ugly cmd prompt on Windows when invoking clang-format. 107e5dd7070Spatrick startupinfo = None 108e5dd7070Spatrick if sys.platform.startswith('win32'): 109e5dd7070Spatrick startupinfo = subprocess.STARTUPINFO() 110e5dd7070Spatrick startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 111e5dd7070Spatrick startupinfo.wShowWindow = subprocess.SW_HIDE 112e5dd7070Spatrick 113e5dd7070Spatrick # Call formatter. 114ec727ea7Spatrick command = [binary, '-cursor', str(cursor_byte)] 115e5dd7070Spatrick if lines != ['-lines', 'all']: 116e5dd7070Spatrick command += lines 117e5dd7070Spatrick if style: 118e5dd7070Spatrick command.extend(['-style', style]) 119e5dd7070Spatrick if fallback_style: 120e5dd7070Spatrick command.extend(['-fallback-style', fallback_style]) 121e5dd7070Spatrick if vim.current.buffer.name: 122e5dd7070Spatrick command.extend(['-assume-filename', vim.current.buffer.name]) 123e5dd7070Spatrick p = subprocess.Popen(command, 124e5dd7070Spatrick stdout=subprocess.PIPE, stderr=subprocess.PIPE, 125e5dd7070Spatrick stdin=subprocess.PIPE, startupinfo=startupinfo) 126ec727ea7Spatrick stdout, stderr = p.communicate(input=text) 127e5dd7070Spatrick 128e5dd7070Spatrick # If successful, replace buffer contents. 129e5dd7070Spatrick if stderr: 130e5dd7070Spatrick print(stderr) 131e5dd7070Spatrick 132e5dd7070Spatrick if not stdout: 133e5dd7070Spatrick print( 134e5dd7070Spatrick 'No output from clang-format (crashed?).\n' 135e5dd7070Spatrick 'Please report to bugs.llvm.org.' 136e5dd7070Spatrick ) 137e5dd7070Spatrick else: 138ec727ea7Spatrick header, content = stdout.split(b'\n', 1) 139*12c85518Srobert header = json.loads(header.decode('utf-8')) 140e5dd7070Spatrick # Strip off the trailing newline (added above). 141e5dd7070Spatrick # This maintains trailing empty lines present in the buffer if 142e5dd7070Spatrick # the -lines specification requests them to remain unchanged. 143ec727ea7Spatrick lines = content.decode(encoding).split('\n')[:-1] 144e5dd7070Spatrick sequence = difflib.SequenceMatcher(None, buf, lines) 145e5dd7070Spatrick for op in reversed(sequence.get_opcodes()): 146e5dd7070Spatrick if op[0] != 'equal': 147e5dd7070Spatrick vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]] 148ec727ea7Spatrick if header.get('IncompleteFormat'): 149e5dd7070Spatrick print('clang-format: incomplete (syntax errors)') 150ec727ea7Spatrick # Convert cursor bytes to (line, col) 151ec727ea7Spatrick # Don't use goto: https://github.com/vim/vim/issues/5930 152ec727ea7Spatrick cursor_byte = int(header['Cursor']) 153ec727ea7Spatrick prefix = content[0:cursor_byte] 154ec727ea7Spatrick cursor_line = 1 + prefix.count(b'\n') 155ec727ea7Spatrick cursor_column = 1 + len(prefix.rsplit(b'\n', 1)[-1]) 156ec727ea7Spatrick vim.command('call cursor(%d, %d)' % (cursor_line, cursor_column)) 157e5dd7070Spatrick 158e5dd7070Spatrickmain() 159