clang-tools  9.0.0
clang-include-fixer.py
Go to the documentation of this file.
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:
4 #
5 # noremap <leader>cf :pyf path/to/llvm/source/tools/clang/tools/extra/clang-include-fixer/tool/clang-include-fixer.py<cr>
6 #
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
9 # different key.
10 #
11 # To set up clang-include-fixer, see
12 # http://clang.llvm.org/extra/clang-include-fixer.html
13 #
14 # With this integration you can press the bound key and clang-include-fixer will
15 # be run on the current buffer.
16 #
17 # It operates on the current, potentially unsaved buffer and does not create
18 # or save any files. To revert a fix, just undo.
19 
20 import argparse
21 import difflib
22 import json
23 import re
24 import subprocess
25 import vim
26 
27 # set g:clang_include_fixer_path to the path to clang-include-fixer if it is not
28 # on the path.
29 # Change this to the full path if clang-include-fixer is not on the path.
30 binary = 'clang-include-fixer'
31 if vim.eval('exists("g:clang_include_fixer_path")') == "1":
32  binary = vim.eval('g:clang_include_fixer_path')
33 
34 maximum_suggested_headers = 3
35 if vim.eval('exists("g:clang_include_fixer_maximum_suggested_headers")') == "1":
36  maximum_suggested_headers = max(
37  1,
38  vim.eval('g:clang_include_fixer_maximum_suggested_headers'))
39 
40 increment_num = 5
41 if vim.eval('exists("g:clang_include_fixer_increment_num")') == "1":
42  increment_num = max(
43  1,
44  vim.eval('g:clang_include_fixer_increment_num'))
45 
46 jump_to_include = False
47 if vim.eval('exists("g:clang_include_fixer_jump_to_include")') == "1":
48  jump_to_include = vim.eval('g:clang_include_fixer_jump_to_include') != "0"
49 
50 query_mode = False
51 if vim.eval('exists("g:clang_include_fixer_query_mode")') == "1":
52  query_mode = vim.eval('g:clang_include_fixer_query_mode') != "0"
53 
54 
55 def GetUserSelection(message, headers, maximum_suggested_headers):
56  eval_message = message + '\n'
57  for idx, header in enumerate(headers[0:maximum_suggested_headers]):
58  eval_message += "({0}). {1}\n".format(idx + 1, header)
59  eval_message += "Enter (q) to quit;"
60  if maximum_suggested_headers < len(headers):
61  eval_message += " (m) to show {0} more candidates.".format(
62  min(increment_num, len(headers) - maximum_suggested_headers))
63 
64  eval_message += "\nSelect (default 1): "
65  res = vim.eval("input('{0}')".format(eval_message))
66  if res == '':
67  # choose the top ranked header by default
68  idx = 1
69  elif res == 'q':
70  raise Exception(' Insertion cancelled...')
71  elif res == 'm':
72  return GetUserSelection(message,
73  headers, maximum_suggested_headers + increment_num)
74  else:
75  try:
76  idx = int(res)
77  if idx <= 0 or idx > len(headers):
78  raise Exception()
79  except Exception:
80  # Show a new prompt on invalid option instead of aborting so that users
81  # don't need to wait for another clang-include-fixer run.
82  print >> sys.stderr, "Invalid option:", res
83  return GetUserSelection(message, headers, maximum_suggested_headers)
84  return headers[idx - 1]
85 
86 
87 def execute(command, text):
88  p = subprocess.Popen(command,
89  stdout=subprocess.PIPE, stderr=subprocess.PIPE,
90  stdin=subprocess.PIPE)
91  return p.communicate(input=text)
92 
93 
94 def InsertHeaderToVimBuffer(header, text):
95  command = [binary, "-stdin", "-insert-header=" + json.dumps(header),
96  vim.current.buffer.name]
97  stdout, stderr = execute(command, text)
98  if stderr:
99  raise Exception(stderr)
100  if stdout:
101  lines = stdout.splitlines()
102  sequence = difflib.SequenceMatcher(None, vim.current.buffer, lines)
103  line_num = None
104  for op in reversed(sequence.get_opcodes()):
105  if op[0] != 'equal':
106  vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]]
107  if op[0] == 'insert':
108  # line_num in vim is 1-based.
109  line_num = op[1] + 1
110 
111  if jump_to_include and line_num:
112  vim.current.window.cursor = (line_num, 0)
113 
114 
115 # The vim internal implementation (expand("cword"/"cWORD")) doesn't support
116 # our use case very well, we re-implement our own one.
118  line = vim.eval("line(\".\")")
119  # column number in vim is 1-based.
120  col = int(vim.eval("col(\".\")")) - 1
121  line_text = vim.eval("getline({0})".format(line))
122  if len(line_text) == 0: return ""
123  symbol_pos_begin = col
124  p = re.compile('[a-zA-Z0-9:_]')
125  while symbol_pos_begin >= 0 and p.match(line_text[symbol_pos_begin]):
126  symbol_pos_begin -= 1
127 
128  symbol_pos_end = col
129  while symbol_pos_end < len(line_text) and p.match(line_text[symbol_pos_end]):
130  symbol_pos_end += 1
131  return line_text[symbol_pos_begin+1:symbol_pos_end]
132 
133 
134 def main():
135  parser = argparse.ArgumentParser(
136  description='Vim integration for clang-include-fixer')
137  parser.add_argument('-db', default='yaml',
138  help='clang-include-fixer input format.')
139  parser.add_argument('-input', default='',
140  help='String to initialize the database.')
141  # Don't throw exception when parsing unknown arguements to make the script
142  # work in neovim.
143  # Neovim (at least v0.2.1) somehow mangles the sys.argv in a weird way: it
144  # will pass additional arguments (e.g. "-c script_host.py") to sys.argv,
145  # which makes the script fail.
146  args, _ = parser.parse_known_args()
147 
148  # Get the current text.
149  buf = vim.current.buffer
150  text = '\n'.join(buf)
151 
152  if query_mode:
153  symbol = get_symbol_under_cursor()
154  if len(symbol) == 0:
155  print "Skip querying empty symbol."
156  return
157  command = [binary, "-stdin", "-query-symbol="+get_symbol_under_cursor(),
158  "-db=" + args.db, "-input=" + args.input,
159  vim.current.buffer.name]
160  else:
161  # Run command to get all headers.
162  command = [binary, "-stdin", "-output-headers", "-db=" + args.db,
163  "-input=" + args.input, vim.current.buffer.name]
164  stdout, stderr = execute(command, text)
165  if stderr:
166  print >> sys.stderr, "Error while running clang-include-fixer: " + stderr
167  return
168 
169  include_fixer_context = json.loads(stdout)
170  query_symbol_infos = include_fixer_context["QuerySymbolInfos"]
171  if not query_symbol_infos:
172  print "The file is fine, no need to add a header."
173  return
174  symbol = query_symbol_infos[0]["RawIdentifier"]
175  # The header_infos is already sorted by clang-include-fixer.
176  header_infos = include_fixer_context["HeaderInfos"]
177  # Deduplicate headers while keeping the order, so that the same header would
178  # not be suggested twice.
179  unique_headers = []
180  seen = set()
181  for header_info in header_infos:
182  header = header_info["Header"]
183  if header not in seen:
184  seen.add(header)
185  unique_headers.append(header)
186 
187  if not unique_headers:
188  print "Couldn't find a header for {0}.".format(symbol)
189  return
190 
191  try:
192  selected = unique_headers[0]
193  inserted_header_infos = header_infos
194  if len(unique_headers) > 1:
195  selected = GetUserSelection(
196  "choose a header file for {0}.".format(symbol),
197  unique_headers, maximum_suggested_headers)
198  inserted_header_infos = [
199  header for header in header_infos if header["Header"] == selected]
200  include_fixer_context["HeaderInfos"] = inserted_header_infos
201 
202  InsertHeaderToVimBuffer(include_fixer_context, text)
203  print "Added #include {0} for {1}.".format(selected, symbol)
204  except Exception as error:
205  print >> sys.stderr, error.message
206  return
207 
208 
209 if __name__ == '__main__':
210  main()
def GetUserSelection(message, headers, maximum_suggested_headers)
def InsertHeaderToVimBuffer(header, text)
def execute(command, text)
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)