Index: include-fixer/find-all-symbols/SymbolInfo.h =================================================================== --- include-fixer/find-all-symbols/SymbolInfo.h +++ include-fixer/find-all-symbols/SymbolInfo.h @@ -54,6 +54,8 @@ int LineNumber, const std::vector &Contexts, unsigned NumOccurrences = 0); + void SetFilePath(llvm::StringRef Path) { FilePath = Path; } + /// \brief Get symbol name. llvm::StringRef getName() const { return Name; } Index: include-fixer/tool/ClangIncludeFixer.cpp =================================================================== --- include-fixer/tool/ClangIncludeFixer.cpp +++ include-fixer/tool/ClangIncludeFixer.cpp @@ -98,6 +98,12 @@ cl::desc("String to initialize the database"), cl::cat(IncludeFixerCategory)); +cl::opt + QuerySymbol("query-symbol", + cl::desc("Query a given symbol (e.g. \"a::b::foo\") in\n" + "database directly without parsing the file."), + cl::cat(IncludeFixerCategory)); + cl::opt MinimizeIncludePaths("minimize-paths", cl::desc("Whether to minimize added include paths"), @@ -236,6 +242,7 @@ tooling::ClangTool tool(options.getCompilations(), options.getSourcePathList()); + llvm::StringRef SourceFilePath = options.getSourcePathList().front(); // In STDINMode, we override the file content with the input. // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of // the if-block so that `Code` is not released after the if-block. @@ -253,7 +260,7 @@ if (Code->getBufferSize() == 0) return 0; // Skip empty files. - tool.mapVirtualFile(options.getSourcePathList().front(), Code->getBuffer()); + tool.mapVirtualFile(SourceFilePath, Code->getBuffer()); } if (!InsertHeader.empty()) { @@ -314,10 +321,31 @@ // Set up data source. std::unique_ptr SymbolIndexMgr = - createSymbolIndexManager(options.getSourcePathList().front()); + createSymbolIndexManager(SourceFilePath); if (!SymbolIndexMgr) return 1; + // Query symbol mode. + if (!QuerySymbol.empty()) { + auto MatchedSymbols = SymbolIndexMgr->search(QuerySymbol); + for (auto &Symbol : MatchedSymbols) { + std::string HeaderPath = Symbol.getFilePath().str(); + Symbol.SetFilePath(((HeaderPath[0] == '"' || HeaderPath[0] == '<') + ? HeaderPath + : "\"" + HeaderPath + "\"")); + } + + // We leave an empty symbol range as we don't know the range of the symbol + // being queried in this mode. include-fixer won't add namespace qualifiers + // if the symbol range is empty, which also fits this case. + IncludeFixerContext::QuerySymbolInfo Symbol; + Symbol.RawIdentifier = QuerySymbol; + auto Context = + IncludeFixerContext(SourceFilePath, {Symbol}, MatchedSymbols); + writeToJson(llvm::outs(), Context); + return 0; + } + // Now run our tool. std::vector Contexts; include_fixer::IncludeFixerActionFactory Factory(*SymbolIndexMgr, Contexts, Index: include-fixer/tool/clang-include-fixer.el =================================================================== --- include-fixer/tool/clang-include-fixer.el +++ include-fixer/tool/clang-include-fixer.el @@ -36,11 +36,17 @@ (defcustom clang-include-fixer-init-string "" - "clang-include-fixer input format." + "clang-include-fixer init string." :group 'clang-include-fixer :type 'string :risky t) +(defcustom clang-include-fixer-query-mode + nil + "clang-include-fixer query mode." + :group 'clang-include-fixer + :type 'boolean + :risky t) (defun clang-include-fixer-call-executable (callee include-fixer-parameter-a @@ -197,11 +203,28 @@ (message (concat "Calling the include fixer. " "This might take some seconds. Please wait.")) - (clang-include-fixer-call-executable - 'clang-include-fixer-add-header - (concat "-db=" clang-include-fixer-input-format) - (concat "-input=" clang-include-fixer-init-string) - "-output-headers")) + (if clang-include-fixer-query-mode + (let (p1 p2) + (save-excursion + (skip-chars-backward "-a-zA-Z0-9_:") + (setq p1 (point)) + (skip-chars-forward "-a-zA-Z0-9_:") + (setq p2 (point)) + (setq query-symbol (buffer-substring-no-properties p1 p2)) + (if (string= "" query-symbol) + (message "Skip querying empty symbol.") + (clang-include-fixer-call-executable + 'clang-include-fixer-add-header + (concat "-db=" clang-include-fixer-input-format) + (concat "-input=" clang-include-fixer-init-string) + (concat "-query-symbol=" (thing-at-point 'symbol)) + )))) + (clang-include-fixer-call-executable + 'clang-include-fixer-add-header + (concat "-db=" clang-include-fixer-input-format) + (concat "-input=" clang-include-fixer-init-string) + "-output-headers")) + ) (provide 'clang-include-fixer) ;;; clang-include-fixer.el ends here Index: include-fixer/tool/clang-include-fixer.py =================================================================== --- include-fixer/tool/clang-include-fixer.py +++ include-fixer/tool/clang-include-fixer.py @@ -17,9 +17,10 @@ import argparse import difflib +import json +import re import subprocess import vim -import json # set g:clang_include_fixer_path to the path to clang-include-fixer if it is not # on the path. @@ -44,6 +45,10 @@ if vim.eval('exists("g:clang_include_fixer_jump_to_include")') == "1": jump_to_include = vim.eval('g:clang_include_fixer_jump_to_include') != "0" +query_mode = True +if vim.eval('exists("g:clang_include_fixer_query_mode")') == "1": + query_mode = vim.eval('g:clang_include_fixer_query_mode') != "0" + def GetUserSelection(message, headers, maximum_suggested_headers): eval_message = message + '\n' @@ -105,6 +110,25 @@ vim.current.window.cursor = (line_num, 0) +# The vim internal implementation (expand("cword"/"cWORD")) doesn't support +# our use case very well, we re-implement our own one. +def get_symbol_under_cursor(): + line = vim.eval("line(\".\")") + # column number in vim is 1-based. + col = int(vim.eval("col(\".\")")) - 1 + line_text = vim.eval("getline({0})".format(line)) + if len(line_text) == 0: return "" + symbol_pos_begin = col + p = re.compile('[a-zA-Z0-9:_]') + while symbol_pos_begin >= 0 and p.match(line_text[symbol_pos_begin]): + symbol_pos_begin -= 1 + + symbol_pos_end = col + while symbol_pos_end < len(line_text) and p.match(line_text[symbol_pos_end]): + symbol_pos_end += 1 + return line_text[symbol_pos_begin+1:symbol_pos_end] + + def main(): parser = argparse.ArgumentParser( description='Vim integration for clang-include-fixer') @@ -118,9 +142,18 @@ buf = vim.current.buffer text = '\n'.join(buf) - # Run command to get all headers. - command = [binary, "-stdin", "-output-headers", "-db=" + args.db, - "-input=" + args.input, vim.current.buffer.name] + if query_mode: + symbol = get_symbol_under_cursor() + if len(symbol) == 0: + print "Skip querying empty symbol." + return + command = [binary, "-stdin", "-query-symbol="+get_symbol_under_cursor(), + "-db=" + args.db, "-input=" + args.input, + vim.current.buffer.name] + else: + # Run command to get all headers. + command = [binary, "-stdin", "-output-headers", "-db=" + args.db, + "-input=" + args.input, vim.current.buffer.name] stdout, stderr = execute(command, text) if stderr: print >> sys.stderr, "Error while running clang-include-fixer: " + stderr Index: test/include-fixer/query_symbol.cpp =================================================================== --- /dev/null +++ test/include-fixer/query_symbol.cpp @@ -0,0 +1,13 @@ +// RUN: clang-include-fixer -db=fixed -input='foo= "foo.h","bar.h"' -query-symbol="foo" test.cpp -- | FileCheck %s + +// CHECK: "FilePath": "test.cpp", +// CHECK-NEXT:"QuerySymbolInfos": [ +// CHECK-NEXT: {"RawIdentifier": "foo", +// CHECK-NEXT: "Range":{"Offset":0,"Length":0}} +// CHECK-NEXT:], +// CHECK-NEXT:"HeaderInfos": [ +// CHECK-NEXT: {"Header": "\"foo.h\"", +// CHECK-NEXT: "QualifiedName": "foo"}, +// CHECK-NEXT: {"Header": "\"bar.h\"", +// CHECK-NEXT: "QualifiedName": "foo"} +// CHECK-NEXT:]