1 # This file is a minimal clang-include-fixer vim-integration. To install:
2 # - Change 'binary' if clang-include-fixer is not on the path (see below).
3 # - Add to your .vimrc:
5 # noremap <leader>cf :pyf path/to/llvm/source/tools/clang/tools/extra/clang-include-fixer/tool/clang-include-fixer.py<cr>
7 # This enables clang-include-fixer for NORMAL and VISUAL mode. Change
8 # "<leader>cf" to another binding if you need clang-include-fixer on a
11 # To set up clang-include-fixer, see
12 # http://clang.llvm.org/extra/clang-include-fixer.html
14 # With this integration you can press the bound key and clang-include-fixer will
15 # be run on the current buffer.
17 # It operates on the current, potentially unsaved buffer and does not create
18 # or save any files. To revert a fix, just undo.
20 from __future__
import print_function
28 # set g:clang_include_fixer_path to the path to clang-include-fixer if it is not
30 # Change this to the full path if clang-include-fixer is not on the path.
31 binary
= "clang-include-fixer"
32 if vim
.eval('exists("g:clang_include_fixer_path")') == "1":
33 binary
= vim
.eval("g:clang_include_fixer_path")
35 maximum_suggested_headers
= 3
36 if vim
.eval('exists("g:clang_include_fixer_maximum_suggested_headers")') == "1":
37 maximum_suggested_headers
= max(
38 1, vim
.eval("g:clang_include_fixer_maximum_suggested_headers")
42 if vim
.eval('exists("g:clang_include_fixer_increment_num")') == "1":
43 increment_num
= max(1, vim
.eval("g:clang_include_fixer_increment_num"))
45 jump_to_include
= False
46 if vim
.eval('exists("g:clang_include_fixer_jump_to_include")') == "1":
47 jump_to_include
= vim
.eval("g:clang_include_fixer_jump_to_include") != "0"
50 if vim
.eval('exists("g:clang_include_fixer_query_mode")') == "1":
51 query_mode
= vim
.eval("g:clang_include_fixer_query_mode") != "0"
54 def GetUserSelection(message
, headers
, maximum_suggested_headers
):
55 eval_message
= message
+ "\n"
56 for idx
, header
in enumerate(headers
[0:maximum_suggested_headers
]):
57 eval_message
+= "({0}). {1}\n".format(idx
+ 1, header
)
58 eval_message
+= "Enter (q) to quit;"
59 if maximum_suggested_headers
< len(headers
):
60 eval_message
+= " (m) to show {0} more candidates.".format(
61 min(increment_num
, len(headers
) - maximum_suggested_headers
)
64 eval_message
+= "\nSelect (default 1): "
65 res
= vim
.eval("input('{0}')".format(eval_message
))
67 # choose the top ranked header by default
70 raise Exception(" Insertion cancelled...")
72 return GetUserSelection(
73 message
, headers
, maximum_suggested_headers
+ increment_num
78 if idx
<= 0 or idx
> len(headers
):
81 # Show a new prompt on invalid option instead of aborting so that users
82 # don't need to wait for another clang-include-fixer run.
83 print("Invalid option: {}".format(res
), file=sys
.stderr
)
84 return GetUserSelection(message
, headers
, maximum_suggested_headers
)
85 return headers
[idx
- 1]
88 def execute(command
, text
):
89 # Avoid flashing a cmd prompt on Windows.
91 if sys
.platform
.startswith("win32"):
92 startupinfo
= subprocess
.STARTUPINFO()
93 startupinfo
.dwFlags |
= subprocess
.STARTF_USESHOWWINDOW
94 startupinfo
.wShowWindow
= subprocess
.SW_HIDE
98 stdout
=subprocess
.PIPE
,
99 stderr
=subprocess
.PIPE
,
100 stdin
=subprocess
.PIPE
,
101 startupinfo
=startupinfo
,
103 return p
.communicate(input=text
.encode("utf-8"))
106 def InsertHeaderToVimBuffer(header
, text
):
110 "-insert-header=" + json
.dumps(header
),
111 vim
.current
.buffer.name
,
113 stdout
, stderr
= execute(command
, text
)
115 raise Exception(stderr
)
117 lines
= stdout
.splitlines()
118 sequence
= difflib
.SequenceMatcher(None, vim
.current
.buffer, lines
)
120 for op
in reversed(sequence
.get_opcodes()):
122 vim
.current
.buffer[op
[1] : op
[2]] = lines
[op
[3] : op
[4]]
123 if op
[0] == "insert":
124 # line_num in vim is 1-based.
127 if jump_to_include
and line_num
:
128 vim
.current
.window
.cursor
= (line_num
, 0)
131 # The vim internal implementation (expand("cword"/"cWORD")) doesn't support
132 # our use case very well, we re-implement our own one.
133 def get_symbol_under_cursor():
134 line
= vim
.eval('line(".")')
135 # column number in vim is 1-based.
136 col
= int(vim
.eval('col(".")')) - 1
137 line_text
= vim
.eval("getline({0})".format(line
))
138 if len(line_text
) == 0:
140 symbol_pos_begin
= col
141 p
= re
.compile("[a-zA-Z0-9:_]")
142 while symbol_pos_begin
>= 0 and p
.match(line_text
[symbol_pos_begin
]):
143 symbol_pos_begin
-= 1
146 while symbol_pos_end
< len(line_text
) and p
.match(line_text
[symbol_pos_end
]):
148 return line_text
[symbol_pos_begin
+ 1 : symbol_pos_end
]
152 parser
= argparse
.ArgumentParser(
153 description
="Vim integration for clang-include-fixer"
155 parser
.add_argument("-db", default
="yaml", help="clang-include-fixer input format.")
156 parser
.add_argument("-input", default
="", help="String to initialize the database.")
157 # Don't throw exception when parsing unknown arguments to make the script
159 # Neovim (at least v0.2.1) somehow mangles the sys.argv in a weird way: it
160 # will pass additional arguments (e.g. "-c script_host.py") to sys.argv,
161 # which makes the script fail.
162 args
, _
= parser
.parse_known_args()
164 # Get the current text.
165 buf
= vim
.current
.buffer
166 text
= "\n".join(buf
)
169 symbol
= get_symbol_under_cursor()
171 print("Skip querying empty symbol.")
176 "-query-symbol=" + get_symbol_under_cursor(),
178 "-input=" + args
.input,
179 vim
.current
.buffer.name
,
182 # Run command to get all headers.
188 "-input=" + args
.input,
189 vim
.current
.buffer.name
,
191 stdout
, stderr
= execute(command
, text
)
194 "Error while running clang-include-fixer: {}".format(stderr
),
199 include_fixer_context
= json
.loads(stdout
)
200 query_symbol_infos
= include_fixer_context
["QuerySymbolInfos"]
201 if not query_symbol_infos
:
202 print("The file is fine, no need to add a header.")
204 symbol
= query_symbol_infos
[0]["RawIdentifier"]
205 # The header_infos is already sorted by clang-include-fixer.
206 header_infos
= include_fixer_context
["HeaderInfos"]
207 # Deduplicate headers while keeping the order, so that the same header would
208 # not be suggested twice.
211 for header_info
in header_infos
:
212 header
= header_info
["Header"]
213 if header
not in seen
:
215 unique_headers
.append(header
)
217 if not unique_headers
:
218 print("Couldn't find a header for {0}.".format(symbol
))
222 selected
= unique_headers
[0]
223 inserted_header_infos
= header_infos
224 if len(unique_headers
) > 1:
225 selected
= GetUserSelection(
226 "choose a header file for {0}.".format(symbol
),
228 maximum_suggested_headers
,
230 inserted_header_infos
= [
231 header
for header
in header_infos
if header
["Header"] == selected
233 include_fixer_context
["HeaderInfos"] = inserted_header_infos
235 InsertHeaderToVimBuffer(include_fixer_context
, text
)
236 print("Added #include {0} for {1}.".format(selected
, symbol
))
237 except Exception as error
:
238 print(error
, file=sys
.stderr
)
242 if __name__
== "__main__":