143356f56SNico Weber# This file is a minimal clang-include-fixer vim-integration. To install: 243356f56SNico Weber# - Change 'binary' if clang-include-fixer is not on the path (see below). 343356f56SNico Weber# - Add to your .vimrc: 443356f56SNico Weber# 543356f56SNico Weber# noremap <leader>cf :pyf path/to/llvm/source/tools/clang/tools/extra/clang-include-fixer/tool/clang-include-fixer.py<cr> 643356f56SNico Weber# 743356f56SNico Weber# This enables clang-include-fixer for NORMAL and VISUAL mode. Change 843356f56SNico Weber# "<leader>cf" to another binding if you need clang-include-fixer on a 943356f56SNico Weber# different key. 1043356f56SNico Weber# 1143356f56SNico Weber# To set up clang-include-fixer, see 1243356f56SNico Weber# http://clang.llvm.org/extra/clang-include-fixer.html 1343356f56SNico Weber# 1443356f56SNico Weber# With this integration you can press the bound key and clang-include-fixer will 1543356f56SNico Weber# be run on the current buffer. 1643356f56SNico Weber# 1743356f56SNico Weber# It operates on the current, potentially unsaved buffer and does not create 1843356f56SNico Weber# or save any files. To revert a fix, just undo. 1943356f56SNico Weber 2066237889SBenjamin Kramerfrom __future__ import print_function 2143356f56SNico Weberimport argparse 2243356f56SNico Weberimport difflib 2343356f56SNico Weberimport json 2443356f56SNico Weberimport re 2543356f56SNico Weberimport subprocess 2643356f56SNico Weberimport vim 2743356f56SNico Weber 2843356f56SNico Weber# set g:clang_include_fixer_path to the path to clang-include-fixer if it is not 2943356f56SNico Weber# on the path. 3043356f56SNico Weber# Change this to the full path if clang-include-fixer is not on the path. 31*dd3c26a0STobias Hietabinary = "clang-include-fixer" 3243356f56SNico Weberif vim.eval('exists("g:clang_include_fixer_path")') == "1": 33*dd3c26a0STobias Hieta binary = vim.eval("g:clang_include_fixer_path") 3443356f56SNico Weber 3543356f56SNico Webermaximum_suggested_headers = 3 3643356f56SNico Weberif vim.eval('exists("g:clang_include_fixer_maximum_suggested_headers")') == "1": 3743356f56SNico Weber maximum_suggested_headers = max( 38*dd3c26a0STobias Hieta 1, vim.eval("g:clang_include_fixer_maximum_suggested_headers") 39*dd3c26a0STobias Hieta ) 4043356f56SNico Weber 4143356f56SNico Weberincrement_num = 5 4243356f56SNico Weberif vim.eval('exists("g:clang_include_fixer_increment_num")') == "1": 43*dd3c26a0STobias Hieta increment_num = max(1, vim.eval("g:clang_include_fixer_increment_num")) 4443356f56SNico Weber 4543356f56SNico Weberjump_to_include = False 4643356f56SNico Weberif vim.eval('exists("g:clang_include_fixer_jump_to_include")') == "1": 47*dd3c26a0STobias Hieta jump_to_include = vim.eval("g:clang_include_fixer_jump_to_include") != "0" 4843356f56SNico Weber 4943356f56SNico Weberquery_mode = False 5043356f56SNico Weberif vim.eval('exists("g:clang_include_fixer_query_mode")') == "1": 51*dd3c26a0STobias Hieta query_mode = vim.eval("g:clang_include_fixer_query_mode") != "0" 5243356f56SNico Weber 5343356f56SNico Weber 5443356f56SNico Weberdef GetUserSelection(message, headers, maximum_suggested_headers): 55*dd3c26a0STobias Hieta eval_message = message + "\n" 5643356f56SNico Weber for idx, header in enumerate(headers[0:maximum_suggested_headers]): 5743356f56SNico Weber eval_message += "({0}). {1}\n".format(idx + 1, header) 5843356f56SNico Weber eval_message += "Enter (q) to quit;" 5943356f56SNico Weber if maximum_suggested_headers < len(headers): 6043356f56SNico Weber eval_message += " (m) to show {0} more candidates.".format( 61*dd3c26a0STobias Hieta min(increment_num, len(headers) - maximum_suggested_headers) 62*dd3c26a0STobias Hieta ) 6343356f56SNico Weber 6443356f56SNico Weber eval_message += "\nSelect (default 1): " 6543356f56SNico Weber res = vim.eval("input('{0}')".format(eval_message)) 66*dd3c26a0STobias Hieta if res == "": 6743356f56SNico Weber # choose the top ranked header by default 6843356f56SNico Weber idx = 1 69*dd3c26a0STobias Hieta elif res == "q": 70*dd3c26a0STobias Hieta raise Exception(" Insertion cancelled...") 71*dd3c26a0STobias Hieta elif res == "m": 72*dd3c26a0STobias Hieta return GetUserSelection( 73*dd3c26a0STobias Hieta message, headers, maximum_suggested_headers + increment_num 74*dd3c26a0STobias Hieta ) 7543356f56SNico Weber else: 7643356f56SNico Weber try: 7743356f56SNico Weber idx = int(res) 7843356f56SNico Weber if idx <= 0 or idx > len(headers): 7943356f56SNico Weber raise Exception() 8043356f56SNico Weber except Exception: 8143356f56SNico Weber # Show a new prompt on invalid option instead of aborting so that users 8243356f56SNico Weber # don't need to wait for another clang-include-fixer run. 8366237889SBenjamin Kramer print("Invalid option: {}".format(res), file=sys.stderr) 8443356f56SNico Weber return GetUserSelection(message, headers, maximum_suggested_headers) 8543356f56SNico Weber return headers[idx - 1] 8643356f56SNico Weber 8743356f56SNico Weber 8843356f56SNico Weberdef execute(command, text): 89e1e7b6f3SReid Kleckner # Avoid flashing a cmd prompt on Windows. 90e1e7b6f3SReid Kleckner startupinfo = None 91*dd3c26a0STobias Hieta if sys.platform.startswith("win32"): 92e1e7b6f3SReid Kleckner startupinfo = subprocess.STARTUPINFO() 93e1e7b6f3SReid Kleckner startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 94e1e7b6f3SReid Kleckner startupinfo.wShowWindow = subprocess.SW_HIDE 95e1e7b6f3SReid Kleckner 96*dd3c26a0STobias Hieta p = subprocess.Popen( 97*dd3c26a0STobias Hieta command, 98*dd3c26a0STobias Hieta stdout=subprocess.PIPE, 99*dd3c26a0STobias Hieta stderr=subprocess.PIPE, 100*dd3c26a0STobias Hieta stdin=subprocess.PIPE, 101*dd3c26a0STobias Hieta startupinfo=startupinfo, 102*dd3c26a0STobias Hieta ) 103*dd3c26a0STobias Hieta return p.communicate(input=text.encode("utf-8")) 10443356f56SNico Weber 10543356f56SNico Weber 10643356f56SNico Weberdef InsertHeaderToVimBuffer(header, text): 107*dd3c26a0STobias Hieta command = [ 108*dd3c26a0STobias Hieta binary, 109*dd3c26a0STobias Hieta "-stdin", 110*dd3c26a0STobias Hieta "-insert-header=" + json.dumps(header), 111*dd3c26a0STobias Hieta vim.current.buffer.name, 112*dd3c26a0STobias Hieta ] 11343356f56SNico Weber stdout, stderr = execute(command, text) 11443356f56SNico Weber if stderr: 11543356f56SNico Weber raise Exception(stderr) 11643356f56SNico Weber if stdout: 11743356f56SNico Weber lines = stdout.splitlines() 11843356f56SNico Weber sequence = difflib.SequenceMatcher(None, vim.current.buffer, lines) 11943356f56SNico Weber line_num = None 12043356f56SNico Weber for op in reversed(sequence.get_opcodes()): 121*dd3c26a0STobias Hieta if op[0] != "equal": 12243356f56SNico Weber vim.current.buffer[op[1] : op[2]] = lines[op[3] : op[4]] 123*dd3c26a0STobias Hieta if op[0] == "insert": 12443356f56SNico Weber # line_num in vim is 1-based. 12543356f56SNico Weber line_num = op[1] + 1 12643356f56SNico Weber 12743356f56SNico Weber if jump_to_include and line_num: 12843356f56SNico Weber vim.current.window.cursor = (line_num, 0) 12943356f56SNico Weber 13043356f56SNico Weber 13143356f56SNico Weber# The vim internal implementation (expand("cword"/"cWORD")) doesn't support 13243356f56SNico Weber# our use case very well, we re-implement our own one. 13343356f56SNico Weberdef get_symbol_under_cursor(): 134*dd3c26a0STobias Hieta line = vim.eval('line(".")') 13543356f56SNico Weber # column number in vim is 1-based. 136*dd3c26a0STobias Hieta col = int(vim.eval('col(".")')) - 1 13743356f56SNico Weber line_text = vim.eval("getline({0})".format(line)) 138*dd3c26a0STobias Hieta if len(line_text) == 0: 139*dd3c26a0STobias Hieta return "" 14043356f56SNico Weber symbol_pos_begin = col 141*dd3c26a0STobias Hieta p = re.compile("[a-zA-Z0-9:_]") 14243356f56SNico Weber while symbol_pos_begin >= 0 and p.match(line_text[symbol_pos_begin]): 14343356f56SNico Weber symbol_pos_begin -= 1 14443356f56SNico Weber 14543356f56SNico Weber symbol_pos_end = col 14643356f56SNico Weber while symbol_pos_end < len(line_text) and p.match(line_text[symbol_pos_end]): 14743356f56SNico Weber symbol_pos_end += 1 14843356f56SNico Weber return line_text[symbol_pos_begin + 1 : symbol_pos_end] 14943356f56SNico Weber 15043356f56SNico Weber 15143356f56SNico Weberdef main(): 15243356f56SNico Weber parser = argparse.ArgumentParser( 153*dd3c26a0STobias Hieta description="Vim integration for clang-include-fixer" 154*dd3c26a0STobias Hieta ) 155*dd3c26a0STobias Hieta parser.add_argument("-db", default="yaml", help="clang-include-fixer input format.") 156*dd3c26a0STobias Hieta parser.add_argument("-input", default="", help="String to initialize the database.") 157dd5571d5SKazuaki Ishizaki # Don't throw exception when parsing unknown arguments to make the script 15843356f56SNico Weber # work in neovim. 15943356f56SNico Weber # Neovim (at least v0.2.1) somehow mangles the sys.argv in a weird way: it 16043356f56SNico Weber # will pass additional arguments (e.g. "-c script_host.py") to sys.argv, 16143356f56SNico Weber # which makes the script fail. 16243356f56SNico Weber args, _ = parser.parse_known_args() 16343356f56SNico Weber 16443356f56SNico Weber # Get the current text. 16543356f56SNico Weber buf = vim.current.buffer 166*dd3c26a0STobias Hieta text = "\n".join(buf) 16743356f56SNico Weber 16843356f56SNico Weber if query_mode: 16943356f56SNico Weber symbol = get_symbol_under_cursor() 17043356f56SNico Weber if len(symbol) == 0: 17166237889SBenjamin Kramer print("Skip querying empty symbol.") 17243356f56SNico Weber return 173*dd3c26a0STobias Hieta command = [ 174*dd3c26a0STobias Hieta binary, 175*dd3c26a0STobias Hieta "-stdin", 176*dd3c26a0STobias Hieta "-query-symbol=" + get_symbol_under_cursor(), 177*dd3c26a0STobias Hieta "-db=" + args.db, 178*dd3c26a0STobias Hieta "-input=" + args.input, 179*dd3c26a0STobias Hieta vim.current.buffer.name, 180*dd3c26a0STobias Hieta ] 18143356f56SNico Weber else: 18243356f56SNico Weber # Run command to get all headers. 183*dd3c26a0STobias Hieta command = [ 184*dd3c26a0STobias Hieta binary, 185*dd3c26a0STobias Hieta "-stdin", 186*dd3c26a0STobias Hieta "-output-headers", 187*dd3c26a0STobias Hieta "-db=" + args.db, 188*dd3c26a0STobias Hieta "-input=" + args.input, 189*dd3c26a0STobias Hieta vim.current.buffer.name, 190*dd3c26a0STobias Hieta ] 19143356f56SNico Weber stdout, stderr = execute(command, text) 19243356f56SNico Weber if stderr: 193*dd3c26a0STobias Hieta print( 194*dd3c26a0STobias Hieta "Error while running clang-include-fixer: {}".format(stderr), 195*dd3c26a0STobias Hieta file=sys.stderr, 196*dd3c26a0STobias Hieta ) 19743356f56SNico Weber return 19843356f56SNico Weber 19943356f56SNico Weber include_fixer_context = json.loads(stdout) 20043356f56SNico Weber query_symbol_infos = include_fixer_context["QuerySymbolInfos"] 20143356f56SNico Weber if not query_symbol_infos: 20266237889SBenjamin Kramer print("The file is fine, no need to add a header.") 20343356f56SNico Weber return 20443356f56SNico Weber symbol = query_symbol_infos[0]["RawIdentifier"] 20543356f56SNico Weber # The header_infos is already sorted by clang-include-fixer. 20643356f56SNico Weber header_infos = include_fixer_context["HeaderInfos"] 20743356f56SNico Weber # Deduplicate headers while keeping the order, so that the same header would 20843356f56SNico Weber # not be suggested twice. 20943356f56SNico Weber unique_headers = [] 21043356f56SNico Weber seen = set() 21143356f56SNico Weber for header_info in header_infos: 21243356f56SNico Weber header = header_info["Header"] 21343356f56SNico Weber if header not in seen: 21443356f56SNico Weber seen.add(header) 21543356f56SNico Weber unique_headers.append(header) 21643356f56SNico Weber 21743356f56SNico Weber if not unique_headers: 21866237889SBenjamin Kramer print("Couldn't find a header for {0}.".format(symbol)) 21943356f56SNico Weber return 22043356f56SNico Weber 22143356f56SNico Weber try: 22243356f56SNico Weber selected = unique_headers[0] 22343356f56SNico Weber inserted_header_infos = header_infos 22443356f56SNico Weber if len(unique_headers) > 1: 22543356f56SNico Weber selected = GetUserSelection( 22643356f56SNico Weber "choose a header file for {0}.".format(symbol), 227*dd3c26a0STobias Hieta unique_headers, 228*dd3c26a0STobias Hieta maximum_suggested_headers, 229*dd3c26a0STobias Hieta ) 23043356f56SNico Weber inserted_header_infos = [ 231*dd3c26a0STobias Hieta header for header in header_infos if header["Header"] == selected 232*dd3c26a0STobias Hieta ] 23343356f56SNico Weber include_fixer_context["HeaderInfos"] = inserted_header_infos 23443356f56SNico Weber 23543356f56SNico Weber InsertHeaderToVimBuffer(include_fixer_context, text) 23666237889SBenjamin Kramer print("Added #include {0} for {1}.".format(selected, symbol)) 23743356f56SNico Weber except Exception as error: 238aa189ed2SYannick Brehon print(error, file=sys.stderr) 23943356f56SNico Weber return 24043356f56SNico Weber 24143356f56SNico Weber 242*dd3c26a0STobias Hietaif __name__ == "__main__": 24343356f56SNico Weber main() 244