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:
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>
13 # The if-elseif-endif conditional should pick either the python3 or python2
14 # integration depending on your vim setup.
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).
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
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
29 # :function FormatFile()
32 # : pyf <path-to-this-file>/clang-format.py
33 # : elseif has('python3')
34 # : py3f <path-to-this-file>/clang-format.py
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
49 # set g:clang_format_path to the path to clang-format if it is not on the path
50 # Change this to the full path if clang-format is not on the path.
51 binary
= 'clang-format'
52 if vim
.eval('exists("g:clang_format_path")') == "1":
53 binary
= vim
.eval('g:clang_format_path')
55 # Change this to format according to other formatting styles. See the output of
56 # 'clang-format --help' for a list of supported styles. The default looks for
57 # a '.clang-format' or '_clang-format' file to indicate the style that should be
61 if vim
.eval('exists("g:clang_format_fallback_style")') == "1":
62 fallback_style
= vim
.eval('g:clang_format_fallback_style')
64 def get_buffer(encoding
):
65 if platform
.python_version_tuple()[0] == '3':
66 return vim
.current
.buffer
67 return [ line
.decode(encoding
) for line
in vim
.current
.buffer ]
70 # Get the current text.
71 encoding
= vim
.eval("&encoding")
72 buf
= get_buffer(encoding
)
73 # Join the buffer into a single string with a terminating newline
74 text
= ('\n'.join(buf
) + '\n').encode(encoding
)
76 # Determine range to format.
77 if vim
.eval('exists("l:lines")') == '1':
78 lines
= ['-lines', vim
.eval('l:lines')]
79 elif vim
.eval('exists("l:formatdiff")') == '1':
80 with
open(vim
.current
.buffer.name
, 'r') as f
:
81 ondisk
= f
.read().splitlines();
82 sequence
= difflib
.SequenceMatcher(None, ondisk
, vim
.current
.buffer)
84 for op
in reversed(sequence
.get_opcodes()):
85 if op
[0] not in ['equal', 'delete']:
86 lines
+= ['-lines', '%s:%s' % (op
[3] + 1, op
[4])]
90 lines
= ['-lines', '%s:%s' % (vim
.current
.range.start
+ 1,
91 vim
.current
.range.end
+ 1)]
93 # Convert cursor (line, col) to bytes.
94 # Don't use line2byte: https://github.com/vim/vim/issues/5930
95 _
, cursor_line
, cursor_col
, _
= vim
.eval('getpos(".")') # 1-based
97 for line
in text
.split(b
'\n')[:int(cursor_line
) - 1]:
98 cursor_byte
+= len(line
) + 1
99 cursor_byte
+= int(cursor_col
) - 1
101 print('Couldn\'t determine cursor position. Is your file empty?')
104 # Avoid flashing an ugly, ugly cmd prompt on Windows when invoking clang-format.
106 if sys
.platform
.startswith('win32'):
107 startupinfo
= subprocess
.STARTUPINFO()
108 startupinfo
.dwFlags |
= subprocess
.STARTF_USESHOWWINDOW
109 startupinfo
.wShowWindow
= subprocess
.SW_HIDE
112 command
= [binary
, '-cursor', str(cursor_byte
)]
113 if lines
!= ['-lines', 'all']:
116 command
.extend(['-style', style
])
118 command
.extend(['-fallback-style', fallback_style
])
119 if vim
.current
.buffer.name
:
120 command
.extend(['-assume-filename', vim
.current
.buffer.name
])
121 p
= subprocess
.Popen(command
,
122 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
123 stdin
=subprocess
.PIPE
, startupinfo
=startupinfo
)
124 stdout
, stderr
= p
.communicate(input=text
)
126 # If successful, replace buffer contents.
132 'No output from clang-format (crashed?).\n'
133 'Please report to bugs.llvm.org.'
136 header
, content
= stdout
.split(b
'\n', 1)
137 header
= json
.loads(header
)
138 # Strip off the trailing newline (added above).
139 # This maintains trailing empty lines present in the buffer if
140 # the -lines specification requests them to remain unchanged.
141 lines
= content
.decode(encoding
).split('\n')[:-1]
142 sequence
= difflib
.SequenceMatcher(None, buf
, lines
)
143 for op
in reversed(sequence
.get_opcodes()):
145 vim
.current
.buffer[op
[1]:op
[2]] = lines
[op
[3]:op
[4]]
146 if header
.get('IncompleteFormat'):
147 print('clang-format: incomplete (syntax errors)')
148 # Convert cursor bytes to (line, col)
149 # Don't use goto: https://github.com/vim/vim/issues/5930
150 cursor_byte
= int(header
['Cursor'])
151 prefix
= content
[0:cursor_byte
]
152 cursor_line
= 1 + prefix
.count(b
'\n')
153 cursor_column
= 1 + len(prefix
.rsplit(b
'\n', 1)[-1])
154 vim
.command('call cursor(%d, %d)' % (cursor_line
, cursor_column
))