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 absolute_import, division, 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 = ['-lines', vim.eval('l:lines')] |
66 | elif vim.eval('exists("l:formatdiff")') == '1': |
67 | with open(vim.current.buffer.name, 'r') as f: |
68 | ondisk = f.read().splitlines(); |
69 | sequence = difflib.SequenceMatcher(None, ondisk, vim.current.buffer) |
70 | lines = [] |
71 | for op in reversed(sequence.get_opcodes()): |
72 | if op[0] not in ['equal', 'delete']: |
73 | lines += ['-lines', '%s:%s' % (op[3] + 1, op[4])] |
74 | if lines == []: |
75 | return |
76 | else: |
77 | lines = ['-lines', '%s:%s' % (vim.current.range.start + 1, |
78 | vim.current.range.end + 1)] |
79 | |
80 | # Determine the cursor position. |
81 | cursor = int(vim.eval('line2byte(line("."))+col(".")')) - 2 |
82 | if cursor < 0: |
83 | print('Couldn\'t determine cursor position. Is your file empty?') |
84 | return |
85 | |
86 | # Avoid flashing an ugly, ugly cmd prompt on Windows when invoking clang-format. |
87 | startupinfo = None |
88 | if sys.platform.startswith('win32'): |
89 | startupinfo = subprocess.STARTUPINFO() |
90 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW |
91 | startupinfo.wShowWindow = subprocess.SW_HIDE |
92 | |
93 | # Call formatter. |
94 | command = [binary, '-style', style, '-cursor', str(cursor)] |
95 | if lines != ['-lines', 'all']: |
96 | command += lines |
97 | if fallback_style: |
98 | command.extend(['-fallback-style', fallback_style]) |
99 | if vim.current.buffer.name: |
100 | command.extend(['-assume-filename', vim.current.buffer.name]) |
101 | p = subprocess.Popen(command, |
102 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
103 | stdin=subprocess.PIPE, startupinfo=startupinfo) |
104 | stdout, stderr = p.communicate(input=text.encode(encoding)) |
105 | |
106 | # If successful, replace buffer contents. |
107 | if stderr: |
108 | print(stderr) |
109 | |
110 | if not stdout: |
111 | print( |
112 | 'No output from clang-format (crashed?).\n' |
113 | 'Please report to bugs.llvm.org.' |
114 | ) |
115 | else: |
116 | lines = stdout.decode(encoding).split('\n') |
117 | output = json.loads(lines[0]) |
118 | lines = lines[1:] |
119 | sequence = difflib.SequenceMatcher(None, buf, lines) |
120 | for op in reversed(sequence.get_opcodes()): |
121 | if op[0] is not 'equal': |
122 | vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]] |
123 | if output.get('IncompleteFormat'): |
124 | print('clang-format: incomplete (syntax errors)') |
125 | vim.command('goto %d' % (output['Cursor'] + 1)) |
126 | |
127 | main() |
128 | |