[clangd] Populate #include insertions as additional edits in completion items.
Summary:
o Remove IncludeInsertion LSP command.
o Populate include insertion edits synchromously in completion items.
o Share the code completion compiler instance and precompiled preamble to get existing inclusions in main file.
o Include insertion logic lives only in CodeComplete now.
o Use tooling::HeaderIncludes for inserting new includes.
o Refactored tests.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: klimek, ilya-biryukov, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D46497
llvm-svn: 332363
diff --git a/clang-tools-extra/clangd/Headers.cpp b/clang-tools-extra/clangd/Headers.cpp
index 58a81dc..c6f18fa 100644
--- a/clang-tools-extra/clangd/Headers.cpp
+++ b/clang-tools-extra/clangd/Headers.cpp
@@ -15,8 +15,6 @@
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/HeaderSearch.h"
-#include "clang/Lex/PreprocessorOptions.h"
-#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/Support/Path.h"
namespace clang {
@@ -74,64 +72,13 @@
/// FIXME(ioeric): we might not want to insert an absolute include path if the
/// path is not shortened.
-llvm::Expected<std::string>
-calculateIncludePath(llvm::StringRef File, llvm::StringRef Code,
- const HeaderFile &DeclaringHeader,
- const HeaderFile &InsertedHeader,
- const tooling::CompileCommand &CompileCommand,
- IntrusiveRefCntPtr<vfs::FileSystem> FS) {
- assert(llvm::sys::path::is_absolute(File));
+llvm::Expected<std::string> calculateIncludePath(
+ PathRef File, StringRef BuildDir, HeaderSearch &HeaderSearchInfo,
+ const std::vector<Inclusion> &Inclusions, const HeaderFile &DeclaringHeader,
+ const HeaderFile &InsertedHeader) {
assert(DeclaringHeader.valid() && InsertedHeader.valid());
if (File == DeclaringHeader.File || File == InsertedHeader.File)
return "";
- FS->setCurrentWorkingDirectory(CompileCommand.Directory);
-
- // Set up a CompilerInstance and create a preprocessor to collect existing
- // #include headers in \p Code. Preprocesor also provides HeaderSearch with
- // which we can calculate the shortest include path for \p Header.
- std::vector<const char *> Argv;
- for (const auto &S : CompileCommand.CommandLine)
- Argv.push_back(S.c_str());
- IgnoringDiagConsumer IgnoreDiags;
- auto CI = clang::createInvocationFromCommandLine(
- Argv,
- CompilerInstance::createDiagnostics(new DiagnosticOptions(), &IgnoreDiags,
- false),
- FS);
- if (!CI)
- return llvm::make_error<llvm::StringError>(
- "Failed to create a compiler instance for " + File,
- llvm::inconvertibleErrorCode());
- CI->getFrontendOpts().DisableFree = false;
- // Parse the main file to get all existing #includes in the file, and then we
- // can make sure the same header (even with different include path) is not
- // added more than once.
- CI->getPreprocessorOpts().SingleFileParseMode = true;
-
- // The diagnostic options must be set before creating a CompilerInstance.
- CI->getDiagnosticOpts().IgnoreWarnings = true;
- auto Clang = prepareCompilerInstance(
- std::move(CI), /*Preamble=*/nullptr,
- llvm::MemoryBuffer::getMemBuffer(Code, File),
- std::make_shared<PCHContainerOperations>(), FS, IgnoreDiags);
-
- if (Clang->getFrontendOpts().Inputs.empty())
- return llvm::make_error<llvm::StringError>(
- "Empty frontend action inputs empty for file " + File,
- llvm::inconvertibleErrorCode());
- PreprocessOnlyAction Action;
- if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]))
- return llvm::make_error<llvm::StringError>(
- "Failed to begin preprocessor only action for file " + File,
- llvm::inconvertibleErrorCode());
- std::vector<Inclusion> Inclusions;
- Clang->getPreprocessor().addPPCallbacks(collectInclusionsInMainFileCallback(
- Clang->getSourceManager(),
- [&Inclusions](Inclusion Inc) { Inclusions.push_back(std::move(Inc)); }));
- if (!Action.Execute())
- return llvm::make_error<llvm::StringError>(
- "Failed to execute preprocessor only action for file " + File,
- llvm::inconvertibleErrorCode());
llvm::StringSet<> IncludedHeaders;
for (const auto &Inc : Inclusions) {
IncludedHeaders.insert(Inc.Written);
@@ -144,14 +91,13 @@
if (Included(DeclaringHeader.File) || Included(InsertedHeader.File))
return "";
- auto &HeaderSearchInfo = Clang->getPreprocessor().getHeaderSearchInfo();
bool IsSystem = false;
if (InsertedHeader.Verbatim)
return InsertedHeader.File;
std::string Suggested = HeaderSearchInfo.suggestPathToFileForDiagnostics(
- InsertedHeader.File, CompileCommand.Directory, &IsSystem);
+ InsertedHeader.File, BuildDir, &IsSystem);
if (IsSystem)
Suggested = "<" + Suggested + ">";
else
@@ -161,5 +107,35 @@
return Suggested;
}
+Expected<Optional<TextEdit>>
+IncludeInserter::insert(const HeaderFile &DeclaringHeader,
+ const HeaderFile &InsertedHeader) const {
+ auto Validate = [](const HeaderFile &Header) {
+ return Header.valid()
+ ? llvm::Error::success()
+ : llvm::make_error<llvm::StringError>(
+ "Invalid HeaderFile: " + Header.File +
+ " (verbatim=" + std::to_string(Header.Verbatim) + ").",
+ llvm::inconvertibleErrorCode());
+ };
+ if (auto Err = Validate(DeclaringHeader))
+ return std::move(Err);
+ if (auto Err = Validate(InsertedHeader))
+ return std::move(Err);
+ auto Include =
+ calculateIncludePath(FileName, BuildDir, HeaderSearchInfo, Inclusions,
+ DeclaringHeader, InsertedHeader);
+ if (!Include)
+ return Include.takeError();
+ if (Include->empty())
+ return llvm::None;
+ StringRef IncludeRef = *Include;
+ auto Insertion =
+ Inserter.insert(IncludeRef.trim("\"<>"), IncludeRef.startswith("<"));
+ if (!Insertion)
+ return llvm::None;
+ return replacementToEdit(Code, *Insertion);
+}
+
} // namespace clangd
} // namespace clang