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