Index: docs/include-fixer.rst =================================================================== --- docs/include-fixer.rst +++ docs/include-fixer.rst @@ -51,6 +51,20 @@ $ clang-include-fixer -db=yaml path/to/file/with/missing/include.cpp Added #include "foo.h" +Integrate with Vim +------------------- +To run `clang-include-fixer` on a potentially unsaved buffer in Vim. Add the +following key binding to your ``.vimrc``: + +.. code-block:: console + + map ,cf :pyf path/to/llvm/source/tools/clang/tools/extra/include-fixer/tool/clang-include-fixer.py + +This enables `clang-include-fixer` for NORMAL and VISUAL mode. Change ``,cf`` to +another binding if you need clang-include-fixer on a different key. + +See ``clang-include-fixer.py`` for more details. + How it Works ============ Index: include-fixer/tool/ClangIncludeFixer.cpp =================================================================== --- include-fixer/tool/ClangIncludeFixer.cpp +++ include-fixer/tool/ClangIncludeFixer.cpp @@ -47,11 +47,40 @@ cl::opt Quiet("q", cl::desc("Reduce terminal output"), cl::init(false), cl::cat(IncludeFixerCategory)); +cl::opt + STDINMode("stdin", + cl::desc("Override source file's content (in the overlaying\n" + "virtual file system) with input from and run\n" + "the tool on the new content with the compilation\n" + "options of the source file. This mode is currently\n" + "used for editor integration."), + cl::init(false), cl::cat(IncludeFixerCategory)); + int includeFixerMain(int argc, const char **argv) { tooling::CommonOptionsParser options(argc, argv, IncludeFixerCategory); tooling::ClangTool tool(options.getCompilations(), options.getSourcePathList()); + // 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. + std::unique_ptr Code; + if (STDINMode) { + assert(options.getSourcePathList().size() == 1 && + "Expect exactly one file path in STDINMode."); + llvm::ErrorOr> CodeOrErr = + MemoryBuffer::getSTDIN(); + if (std::error_code EC = CodeOrErr.getError()) { + errs() << EC.message() << "\n"; + return 1; + } + Code = std::move(CodeOrErr.get()); + if (Code->getBufferSize() == 0) + return 0; // Skip empty files. + + tool.mapVirtualFile(options.getSourcePathList().front(), Code->getBuffer()); + } + // Set up data source. auto SymbolIndexMgr = llvm::make_unique(); switch (DatabaseFormat) { @@ -121,6 +150,15 @@ SourceManager SM(Diagnostics, tool.getFiles()); Diagnostics.setClient(&DiagnosticPrinter, false); + if (STDINMode) { + for (const tooling::Replacement &Replacement : Replacements) { + FileID ID = SM.getMainFileID(); + unsigned LineNum = SM.getLineNumber(ID, Replacement.getOffset()); + llvm::outs() << LineNum << "," << Replacement.getReplacementText(); + } + return 0; + } + // Write replacements to disk. Rewriter Rewrites(SM, LangOptions()); tooling::applyAllReplacements(Replacements, Rewrites); Index: include-fixer/tool/clang-include-fixer.py =================================================================== --- /dev/null +++ include-fixer/tool/clang-include-fixer.py @@ -0,0 +1,65 @@ +# This file is a minimal clang-include-fixer vim-integration. To install: +# - Change 'binary' if clang-include-fixer is not on the path (see below). +# - Add to your .vimrc: +# +# map ,cf :pyf path/to/llvm/source/tools/clang/tools/extra/include-fixer/tool/clang-include-fixer.py +# +# This enables clang-include-fixer for NORMAL and VISUAL mode. Change ",cf" to +# another binding if you need clang-include-fixer on a different key. +# +# To set up clang-include-fixer, see http://clang.llvm.org/extra/include-fixer.html +# +# With this integration you can press the bound key and clang-include-fixer will +# be run on the current buffer. +# +# It operates on the current, potentially unsaved buffer and does not create +# or save any files. To revert a fix, just undo. + +import argparse +import subprocess +import sys +import vim + +# set g:clang_include_fixer_path to the path to clang-include-fixer if it is not +# on the path. +# Change this to the full path if clang-include-fixer is not on the path. +binary = 'clang-include-fixer' +if vim.eval('exists("g:clang_include_fixer_path")') == "1": + binary = vim.eval('g:clang_include_fixer_path') + +def main(): + parser = argparse.ArgumentParser( + description='Vim integration for clang-include-fixer') + parser.add_argument('-db', default='yaml', + help='clang-include-fixer input format.') + parser.add_argument('-input', default='', + help='String to initialize the database.') + args = parser.parse_args() + + # Get the current text. + buf = vim.current.buffer + text = '\n'.join(buf) + + # Call clang-include-fixer. + command = [binary, "-stdin", "-db="+args.db, "-input="+args.input, + vim.current.buffer.name] + p = subprocess.Popen(command, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + stdin=subprocess.PIPE) + stdout, stderr = p.communicate(input=text) + + # If successful, replace buffer contents. + if stderr: + print stderr + + if stdout: + lines = stdout.splitlines() + for line in lines: + line_num, text = line.split(",") + # clang-include-fixer provides 1-based line number + line_num = int(line_num) - 1 + print 'Inserting "{0}" at line {1}'.format(text, line_num) + vim.current.buffer[line_num:line_num] = [text] + +if __name__ == '__main__': + main()