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
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")
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
62 if vim
.eval('exists("g:clang_format_fallback_style")') == "1":
63 fallback_style
= vim
.eval("g:clang_format_fallback_style")
66 def get_buffer(encoding
):
67 if platform
.python_version_tuple()[0] == "3":
68 return vim
.current
.buffer
69 return [line
.decode(encoding
) for line
in vim
.current
.buffer]
73 # Get the current text.
74 encoding
= vim
.eval("&encoding")
75 buf
= get_buffer(encoding
)
76 # Join the buffer into a single string with a terminating newline
77 text
= ("\n".join(buf
) + "\n").encode(encoding
)
79 # Determine range to format.
80 if vim
.eval('exists("l:lines")') == "1":
81 lines
= ["--lines", vim
.eval("l:lines")]
82 elif vim
.eval('exists("l:formatdiff")') == "1" and os
.path
.exists(
83 vim
.current
.buffer.name
85 with
open(vim
.current
.buffer.name
, "r") as f
:
86 ondisk
= f
.read().splitlines()
87 sequence
= difflib
.SequenceMatcher(None, ondisk
, vim
.current
.buffer)
89 for op
in reversed(sequence
.get_opcodes()):
90 if op
[0] not in ["equal", "delete"]:
91 lines
+= ["--lines", "%s:%s" % (op
[3] + 1, op
[4])]
97 "%s:%s" % (vim
.current
.range.start
+ 1, vim
.current
.range.end
+ 1),
100 # Convert cursor (line, col) to bytes.
101 # Don't use line2byte: https://github.com/vim/vim/issues/5930
102 _
, cursor_line
, cursor_col
, _
= vim
.eval('getpos(".")') # 1-based
104 for line
in text
.split(b
"\n")[: int(cursor_line
) - 1]:
105 cursor_byte
+= len(line
) + 1
106 cursor_byte
+= int(cursor_col
) - 1
108 print("Couldn't determine cursor position. Is your file empty?")
111 # Avoid flashing an ugly, ugly cmd prompt on Windows when invoking clang-format.
113 if sys
.platform
.startswith("win32"):
114 startupinfo
= subprocess
.STARTUPINFO()
115 startupinfo
.dwFlags |
= subprocess
.STARTF_USESHOWWINDOW
116 startupinfo
.wShowWindow
= subprocess
.SW_HIDE
119 command
= [binary
, "--cursor", str(cursor_byte
)]
120 if lines
!= ["--lines", "all"]:
123 command
.extend(["--style", style
])
125 command
.extend(["--fallback-style", fallback_style
])
126 if vim
.current
.buffer.name
:
127 command
.extend(["--assume-filename", vim
.current
.buffer.name
])
128 p
= subprocess
.Popen(
130 stdout
=subprocess
.PIPE
,
131 stderr
=subprocess
.PIPE
,
132 stdin
=subprocess
.PIPE
,
133 startupinfo
=startupinfo
,
135 stdout
, stderr
= p
.communicate(input=text
)
137 # If successful, replace buffer contents.
143 "No output from clang-format (crashed?).\n"
144 "Please report to bugs.llvm.org."
147 header
, content
= stdout
.split(b
"\n", 1)
148 header
= json
.loads(header
.decode("utf-8"))
149 # Strip off the trailing newline (added above).
150 # This maintains trailing empty lines present in the buffer if
151 # the -lines specification requests them to remain unchanged.
152 lines
= content
.decode(encoding
).split("\n")[:-1]
153 sequence
= difflib
.SequenceMatcher(None, buf
, lines
)
154 for op
in reversed(sequence
.get_opcodes()):
156 vim
.current
.buffer[op
[1] : op
[2]] = lines
[op
[3] : op
[4]]
157 if header
.get("IncompleteFormat"):
158 print("clang-format: incomplete (syntax errors)")
159 # Convert cursor bytes to (line, col)
160 # Don't use goto: https://github.com/vim/vim/issues/5930
161 cursor_byte
= int(header
["Cursor"])
162 prefix
= content
[0:cursor_byte
]
163 cursor_line
= 1 + prefix
.count(b
"\n")
164 cursor_column
= 1 + len(prefix
.rsplit(b
"\n", 1)[-1])
165 vim
.command("call cursor(%d, %d)" % (cursor_line
, cursor_column
))