Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -7,6 +7,8 @@ ClangdServer.cpp ClangdUnit.cpp ClangdUnitStore.cpp + CodeComplete.cpp + Compiler.cpp DraftStore.cpp FuzzyMatch.cpp GlobalCompilationDatabase.cpp Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -20,6 +20,7 @@ #include "llvm/ADT/StringRef.h" #include "ClangdUnit.h" +#include "CodeComplete.h" #include "Function.h" #include "Protocol.h" Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -256,56 +256,6 @@ clangd::Logger &Logger; }; -struct CodeCompleteOptions { - /// Returns options that can be passed to clang's completion engine. - clang::CodeCompleteOptions getClangCompleteOpts() const; - - /// When true, completion items will contain expandable code snippets in - /// completion (e.g. `return ${1:expression}` or `foo(${1:int a}, ${2:int - /// b})). - bool EnableSnippets = false; - - /// Add code patterns to completion results. - /// If EnableSnippets is false, this options is ignored and code patterns will - /// always be omitted. - bool IncludeCodePatterns = true; - - /// Add macros to code completion results. - bool IncludeMacros = true; - - /// Add globals to code completion results. - bool IncludeGlobals = true; - - /// Add brief comments to completion items, if available. - /// FIXME(ibiryukov): it looks like turning this option on significantly slows - /// down completion, investigate if it can be made faster. - bool IncludeBriefComments = true; - - /// Include results that are not legal completions in the current context. - /// For example, private members are usually inaccessible. - bool IncludeIneligibleResults = false; - - /// Limit the number of results returned (0 means no limit). - /// If more results are available, we set CompletionList.isIncomplete. - size_t Limit = 0; -}; - -/// Get code completions at a specified \p Pos in \p FileName. -CompletionList -codeComplete(PathRef FileName, const tooling::CompileCommand &Command, - PrecompiledPreamble const *Preamble, StringRef Contents, - Position Pos, IntrusiveRefCntPtr VFS, - std::shared_ptr PCHs, - clangd::CodeCompleteOptions Opts, clangd::Logger &Logger); - -/// Get signature help at a specified \p Pos in \p FileName. -SignatureHelp signatureHelp(PathRef FileName, - const tooling::CompileCommand &Command, - PrecompiledPreamble const *Preamble, - StringRef Contents, Position Pos, - IntrusiveRefCntPtr VFS, - std::shared_ptr PCHs, - clangd::Logger &Logger); /// Get the beginning SourceLocation at a specified \p Pos. SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos, Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -9,6 +9,7 @@ #include "ClangdUnit.h" +#include "Compiler.h" #include "Logger.h" #include "Trace.h" #include "clang/Frontend/CompilerInstance.h" @@ -20,7 +21,6 @@ #include "clang/Lex/Lexer.h" #include "clang/Lex/MacroInfo.h" #include "clang/Lex/Preprocessor.h" -#include "clang/Lex/PreprocessorOptions.h" #include "clang/Sema/Sema.h" #include "clang/Serialization/ASTWriter.h" #include "clang/Tooling/CompilationDatabase.h" @@ -120,44 +120,6 @@ llvm_unreachable("Unknown diagnostic level!"); } -/// Get the optional chunk as a string. This function is possibly recursive. -/// -/// The parameter info for each parameter is appended to the Parameters. -std::string -getOptionalParameters(const CodeCompletionString &CCS, - std::vector &Parameters) { - std::string Result; - for (const auto &Chunk : CCS) { - switch (Chunk.Kind) { - case CodeCompletionString::CK_Optional: - assert(Chunk.Optional && - "Expected the optional code completion string to be non-null."); - Result += getOptionalParameters(*Chunk.Optional, Parameters); - break; - case CodeCompletionString::CK_VerticalSpace: - break; - case CodeCompletionString::CK_Placeholder: - // A string that acts as a placeholder for, e.g., a function call - // argument. - // Intentional fallthrough here. - case CodeCompletionString::CK_CurrentParameter: { - // A piece of text that describes the parameter that corresponds to - // the code-completion location within a function call, message send, - // macro invocation, etc. - Result += Chunk.Text; - ParameterInformation Info; - Info.label = Chunk.Text; - Parameters.push_back(std::move(Info)); - break; - } - default: - Result += Chunk.Text; - break; - } - } - return Result; -} - llvm::Optional toClangdDiag(const StoredDiagnostic &D) { auto Location = D.getLocation(); if (!Location.isValid() || !Location.getManager().isInMainFile(Location)) @@ -193,722 +155,12 @@ std::vector &Output; }; -class EmptyDiagsConsumer : public DiagnosticConsumer { -public: - void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, - const clang::Diagnostic &Info) override {} -}; - -std::unique_ptr -createCompilerInvocation(ArrayRef ArgList, - IntrusiveRefCntPtr Diags, - IntrusiveRefCntPtr VFS) { - auto CI = createInvocationFromCommandLine(ArgList, std::move(Diags), - std::move(VFS)); - // We rely on CompilerInstance to manage the resource (i.e. free them on - // EndSourceFile), but that won't happen if DisableFree is set to true. - // Since createInvocationFromCommandLine sets it to true, we have to override - // it. - CI->getFrontendOpts().DisableFree = false; - return CI; -} - -/// Creates a CompilerInstance from \p CI, with main buffer overriden to \p -/// Buffer and arguments to read the PCH from \p Preamble, if \p Preamble is not -/// null. Note that vfs::FileSystem inside returned instance may differ from \p -/// VFS if additional file remapping were set in command-line arguments. -/// On some errors, returns null. When non-null value is returned, it's expected -/// to be consumed by the FrontendAction as it will have a pointer to the \p -/// Buffer that will only be deleted if BeginSourceFile is called. -std::unique_ptr -prepareCompilerInstance(std::unique_ptr CI, - const PrecompiledPreamble *Preamble, - std::unique_ptr Buffer, - std::shared_ptr PCHs, - IntrusiveRefCntPtr VFS, - DiagnosticConsumer &DiagsClient) { - assert(VFS && "VFS is null"); - assert(!CI->getPreprocessorOpts().RetainRemappedFileBuffers && - "Setting RetainRemappedFileBuffers to true will cause a memory leak " - "of ContentsBuffer"); - - // NOTE: we use Buffer.get() when adding remapped files, so we have to make - // sure it will be released if no error is emitted. - if (Preamble) { - Preamble->AddImplicitPreamble(*CI, VFS, Buffer.get()); - } else { - CI->getPreprocessorOpts().addRemappedFile( - CI->getFrontendOpts().Inputs[0].getFile(), Buffer.get()); - } - - auto Clang = llvm::make_unique(PCHs); - Clang->setInvocation(std::move(CI)); - Clang->createDiagnostics(&DiagsClient, false); - - if (auto VFSWithRemapping = createVFSFromCompilerInvocation( - Clang->getInvocation(), Clang->getDiagnostics(), VFS)) - VFS = VFSWithRemapping; - Clang->setVirtualFileSystem(VFS); - - Clang->setTarget(TargetInfo::CreateTargetInfo( - Clang->getDiagnostics(), Clang->getInvocation().TargetOpts)); - if (!Clang->hasTarget()) - return nullptr; - - // RemappedFileBuffers will handle the lifetime of the Buffer pointer, - // release it. - Buffer.release(); - return Clang; -} - template bool futureIsReady(std::shared_future const &Future) { return Future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } } // namespace -namespace { - -CompletionItemKind getKindOfDecl(CXCursorKind CursorKind) { - switch (CursorKind) { - case CXCursor_MacroInstantiation: - case CXCursor_MacroDefinition: - return CompletionItemKind::Text; - case CXCursor_CXXMethod: - return CompletionItemKind::Method; - case CXCursor_FunctionDecl: - case CXCursor_FunctionTemplate: - return CompletionItemKind::Function; - case CXCursor_Constructor: - case CXCursor_Destructor: - return CompletionItemKind::Constructor; - case CXCursor_FieldDecl: - return CompletionItemKind::Field; - case CXCursor_VarDecl: - case CXCursor_ParmDecl: - return CompletionItemKind::Variable; - case CXCursor_ClassDecl: - case CXCursor_StructDecl: - case CXCursor_UnionDecl: - case CXCursor_ClassTemplate: - case CXCursor_ClassTemplatePartialSpecialization: - return CompletionItemKind::Class; - case CXCursor_Namespace: - case CXCursor_NamespaceAlias: - case CXCursor_NamespaceRef: - return CompletionItemKind::Module; - case CXCursor_EnumConstantDecl: - return CompletionItemKind::Value; - case CXCursor_EnumDecl: - return CompletionItemKind::Enum; - case CXCursor_TypeAliasDecl: - case CXCursor_TypeAliasTemplateDecl: - case CXCursor_TypedefDecl: - case CXCursor_MemberRef: - case CXCursor_TypeRef: - return CompletionItemKind::Reference; - default: - return CompletionItemKind::Missing; - } -} - -CompletionItemKind getKind(CodeCompletionResult::ResultKind ResKind, - CXCursorKind CursorKind) { - switch (ResKind) { - case CodeCompletionResult::RK_Declaration: - return getKindOfDecl(CursorKind); - case CodeCompletionResult::RK_Keyword: - return CompletionItemKind::Keyword; - case CodeCompletionResult::RK_Macro: - return CompletionItemKind::Text; // unfortunately, there's no 'Macro' - // completion items in LSP. - case CodeCompletionResult::RK_Pattern: - return CompletionItemKind::Snippet; - } - llvm_unreachable("Unhandled CodeCompletionResult::ResultKind."); -} - -std::string escapeSnippet(const llvm::StringRef Text) { - std::string Result; - Result.reserve(Text.size()); // Assume '$', '}' and '\\' are rare. - for (const auto Character : Text) { - if (Character == '$' || Character == '}' || Character == '\\') - Result.push_back('\\'); - Result.push_back(Character); - } - return Result; -} - -std::string getDocumentation(const CodeCompletionString &CCS) { - // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this - // information in the documentation field. - std::string Result; - const unsigned AnnotationCount = CCS.getAnnotationCount(); - if (AnnotationCount > 0) { - Result += "Annotation"; - if (AnnotationCount == 1) { - Result += ": "; - } else /* AnnotationCount > 1 */ { - Result += "s: "; - } - for (unsigned I = 0; I < AnnotationCount; ++I) { - Result += CCS.getAnnotation(I); - Result.push_back(I == AnnotationCount - 1 ? '\n' : ' '); - } - } - // Add brief documentation (if there is any). - if (CCS.getBriefComment() != nullptr) { - if (!Result.empty()) { - // This means we previously added annotations. Add an extra newline - // character to make the annotations stand out. - Result.push_back('\n'); - } - Result += CCS.getBriefComment(); - } - return Result; -} - -/// A scored code completion result. -/// It may be promoted to a CompletionItem if it's among the top-ranked results. -struct CompletionCandidate { - CompletionCandidate(CodeCompletionResult &Result) - : Result(&Result), Score(score(Result)) {} - - CodeCompletionResult *Result; - float Score; // 0 to 1, higher is better. - - // Comparison reflects rank: better candidates are smaller. - bool operator<(const CompletionCandidate &C) const { - if (Score != C.Score) - return Score > C.Score; - return *Result < *C.Result; - } - - // Returns a string that sorts in the same order as operator<, for LSP. - // Conceptually, this is [-Score, Name]. We convert -Score to an integer, and - // hex-encode it for readability. Example: [0.5, "foo"] -> "41000000foo" - std::string sortText() const { - std::string S, NameStorage; - llvm::raw_string_ostream OS(S); - write_hex(OS, encodeFloat(-Score), llvm::HexPrintStyle::Lower, - /*Width=*/2 * sizeof(Score)); - OS << Result->getOrderedName(NameStorage); - return OS.str(); - } - -private: - static float score(const CodeCompletionResult &Result) { - // Priority 80 is a really bad score. - float Score = 1 - std::min(80, Result.Priority) / 80; - - switch (static_cast(Result.Availability)) { - case CXAvailability_Available: - // No penalty. - break; - case CXAvailability_Deprecated: - Score *= 0.1f; - break; - case CXAvailability_NotAccessible: - case CXAvailability_NotAvailable: - Score = 0; - break; - } - return Score; - } - - // Produces an integer that sorts in the same order as F. - // That is: a < b <==> encodeFloat(a) < encodeFloat(b). - static uint32_t encodeFloat(float F) { - static_assert(std::numeric_limits::is_iec559, ""); - static_assert(sizeof(float) == sizeof(uint32_t), ""); - constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1); - - // Get the bits of the float. Endianness is the same as for integers. - uint32_t U; - memcpy(&U, &F, sizeof(float)); - // IEEE 754 floats compare like sign-magnitude integers. - if (U & TopBit) // Negative float. - return 0 - U; // Map onto the low half of integers, order reversed. - return U + TopBit; // Positive floats map onto the high half of integers. - } -}; - -class CompletionItemsCollector : public CodeCompleteConsumer { -public: - CompletionItemsCollector(const clangd::CodeCompleteOptions &CodeCompleteOpts, - CompletionList &Items) - : CodeCompleteConsumer(CodeCompleteOpts.getClangCompleteOpts(), - /*OutputIsBinary=*/false), - ClangdOpts(CodeCompleteOpts), Items(Items), - Allocator(std::make_shared()), - CCTUInfo(Allocator) {} - - void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, - CodeCompletionResult *Results, - unsigned NumResults) override final { - StringRef Filter = S.getPreprocessor().getCodeCompletionFilter(); - std::priority_queue Candidates; - for (unsigned I = 0; I < NumResults; ++I) { - auto &Result = Results[I]; - if (!ClangdOpts.IncludeIneligibleResults && - (Result.Availability == CXAvailability_NotAvailable || - Result.Availability == CXAvailability_NotAccessible)) - continue; - if (!Filter.empty() && !fuzzyMatch(S, Context, Filter, Result)) - continue; - Candidates.emplace(Result); - if (ClangdOpts.Limit && Candidates.size() > ClangdOpts.Limit) { - Candidates.pop(); - Items.isIncomplete = true; - } - } - while (!Candidates.empty()) { - auto &Candidate = Candidates.top(); - const auto *CCS = Candidate.Result->CreateCodeCompletionString( - S, Context, *Allocator, CCTUInfo, - CodeCompleteOpts.IncludeBriefComments); - assert(CCS && "Expected the CodeCompletionString to be non-null"); - Items.items.push_back(ProcessCodeCompleteResult(Candidate, *CCS)); - Candidates.pop(); - } - std::reverse(Items.items.begin(), Items.items.end()); - } - - GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; } - - CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } - -private: - bool fuzzyMatch(Sema &S, const CodeCompletionContext &CCCtx, StringRef Filter, - CodeCompletionResult Result) { - switch (Result.Kind) { - case CodeCompletionResult::RK_Declaration: - if (auto *ID = Result.Declaration->getIdentifier()) - return fuzzyMatch(Filter, ID->getName()); - break; - case CodeCompletionResult::RK_Keyword: - return fuzzyMatch(Filter, Result.Keyword); - case CodeCompletionResult::RK_Macro: - return fuzzyMatch(Filter, Result.Macro->getName()); - case CodeCompletionResult::RK_Pattern: - return fuzzyMatch(Filter, Result.Pattern->getTypedText()); - } - auto *CCS = Result.CreateCodeCompletionString( - S, CCCtx, *Allocator, CCTUInfo, /*IncludeBriefComments=*/false); - return fuzzyMatch(Filter, CCS->getTypedText()); - } - - // Checks whether Target matches the Filter. - // Currently just requires a case-insensitive subsequence match. - // FIXME: make stricter and word-based: 'unique_ptr' should not match 'que'. - // FIXME: return a score to be incorporated into ranking. - static bool fuzzyMatch(StringRef Filter, StringRef Target) { - size_t TPos = 0; - for (char C : Filter) { - TPos = Target.find_lower(C, TPos); - if (TPos == StringRef::npos) - return false; - } - return true; - } - - CompletionItem - ProcessCodeCompleteResult(const CompletionCandidate &Candidate, - const CodeCompletionString &CCS) const { - - // Adjust this to InsertTextFormat::Snippet iff we encounter a - // CK_Placeholder chunk in SnippetCompletionItemsCollector. - CompletionItem Item; - Item.insertTextFormat = InsertTextFormat::PlainText; - - Item.documentation = getDocumentation(CCS); - Item.sortText = Candidate.sortText(); - - // Fill in the label, detail, insertText and filterText fields of the - // CompletionItem. - ProcessChunks(CCS, Item); - - // Fill in the kind field of the CompletionItem. - Item.kind = getKind(Candidate.Result->Kind, Candidate.Result->CursorKind); - - return Item; - } - - virtual void ProcessChunks(const CodeCompletionString &CCS, - CompletionItem &Item) const = 0; - - clangd::CodeCompleteOptions ClangdOpts; - CompletionList &Items; - std::shared_ptr Allocator; - CodeCompletionTUInfo CCTUInfo; - -}; // CompletionItemsCollector - -bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) { - return Chunk.Kind == CodeCompletionString::CK_Informative && - StringRef(Chunk.Text).endswith("::"); -} - -class PlainTextCompletionItemsCollector final - : public CompletionItemsCollector { - -public: - PlainTextCompletionItemsCollector( - const clangd::CodeCompleteOptions &CodeCompleteOpts, - CompletionList &Items) - : CompletionItemsCollector(CodeCompleteOpts, Items) {} - -private: - void ProcessChunks(const CodeCompletionString &CCS, - CompletionItem &Item) const override { - for (const auto &Chunk : CCS) { - // Informative qualifier chunks only clutter completion results, skip - // them. - if (isInformativeQualifierChunk(Chunk)) - continue; - - switch (Chunk.Kind) { - case CodeCompletionString::CK_TypedText: - // There's always exactly one CK_TypedText chunk. - Item.insertText = Item.filterText = Chunk.Text; - Item.label += Chunk.Text; - break; - case CodeCompletionString::CK_ResultType: - assert(Item.detail.empty() && "Unexpected extraneous CK_ResultType"); - Item.detail = Chunk.Text; - break; - case CodeCompletionString::CK_Optional: - break; - default: - Item.label += Chunk.Text; - break; - } - } - } -}; // PlainTextCompletionItemsCollector - -class SnippetCompletionItemsCollector final : public CompletionItemsCollector { - -public: - SnippetCompletionItemsCollector( - const clangd::CodeCompleteOptions &CodeCompleteOpts, - CompletionList &Items) - : CompletionItemsCollector(CodeCompleteOpts, Items) {} - -private: - void ProcessChunks(const CodeCompletionString &CCS, - CompletionItem &Item) const override { - unsigned ArgCount = 0; - for (const auto &Chunk : CCS) { - // Informative qualifier chunks only clutter completion results, skip - // them. - if (isInformativeQualifierChunk(Chunk)) - continue; - - switch (Chunk.Kind) { - case CodeCompletionString::CK_TypedText: - // The piece of text that the user is expected to type to match - // the code-completion string, typically a keyword or the name of - // a declarator or macro. - Item.filterText = Chunk.Text; - LLVM_FALLTHROUGH; - case CodeCompletionString::CK_Text: - // A piece of text that should be placed in the buffer, - // e.g., parentheses or a comma in a function call. - Item.label += Chunk.Text; - Item.insertText += Chunk.Text; - break; - case CodeCompletionString::CK_Optional: - // A code completion string that is entirely optional. - // For example, an optional code completion string that - // describes the default arguments in a function call. - - // FIXME: Maybe add an option to allow presenting the optional chunks? - break; - case CodeCompletionString::CK_Placeholder: - // A string that acts as a placeholder for, e.g., a function call - // argument. - ++ArgCount; - Item.insertText += "${" + std::to_string(ArgCount) + ':' + - escapeSnippet(Chunk.Text) + '}'; - Item.label += Chunk.Text; - Item.insertTextFormat = InsertTextFormat::Snippet; - break; - case CodeCompletionString::CK_Informative: - // A piece of text that describes something about the result - // but should not be inserted into the buffer. - // For example, the word "const" for a const method, or the name of - // the base class for methods that are part of the base class. - Item.label += Chunk.Text; - // Don't put the informative chunks in the insertText. - break; - case CodeCompletionString::CK_ResultType: - // A piece of text that describes the type of an entity or, - // for functions and methods, the return type. - assert(Item.detail.empty() && "Unexpected extraneous CK_ResultType"); - Item.detail = Chunk.Text; - break; - case CodeCompletionString::CK_CurrentParameter: - // A piece of text that describes the parameter that corresponds to - // the code-completion location within a function call, message send, - // macro invocation, etc. - // - // This should never be present while collecting completion items, - // only while collecting overload candidates. - llvm_unreachable("Unexpected CK_CurrentParameter while collecting " - "CompletionItems"); - break; - case CodeCompletionString::CK_LeftParen: - // A left parenthesis ('('). - case CodeCompletionString::CK_RightParen: - // A right parenthesis (')'). - case CodeCompletionString::CK_LeftBracket: - // A left bracket ('['). - case CodeCompletionString::CK_RightBracket: - // A right bracket (']'). - case CodeCompletionString::CK_LeftBrace: - // A left brace ('{'). - case CodeCompletionString::CK_RightBrace: - // A right brace ('}'). - case CodeCompletionString::CK_LeftAngle: - // A left angle bracket ('<'). - case CodeCompletionString::CK_RightAngle: - // A right angle bracket ('>'). - case CodeCompletionString::CK_Comma: - // A comma separator (','). - case CodeCompletionString::CK_Colon: - // A colon (':'). - case CodeCompletionString::CK_SemiColon: - // A semicolon (';'). - case CodeCompletionString::CK_Equal: - // An '=' sign. - case CodeCompletionString::CK_HorizontalSpace: - // Horizontal whitespace (' '). - Item.insertText += Chunk.Text; - Item.label += Chunk.Text; - break; - case CodeCompletionString::CK_VerticalSpace: - // Vertical whitespace ('\n' or '\r\n', depending on the - // platform). - Item.insertText += Chunk.Text; - // Don't even add a space to the label. - break; - } - } - } -}; // SnippetCompletionItemsCollector - -class SignatureHelpCollector final : public CodeCompleteConsumer { - -public: - SignatureHelpCollector(const clang::CodeCompleteOptions &CodeCompleteOpts, - SignatureHelp &SigHelp) - : CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false), - SigHelp(SigHelp), - Allocator(std::make_shared()), - CCTUInfo(Allocator) {} - - void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, - OverloadCandidate *Candidates, - unsigned NumCandidates) override { - SigHelp.signatures.reserve(NumCandidates); - // FIXME(rwols): How can we determine the "active overload candidate"? - // Right now the overloaded candidates seem to be provided in a "best fit" - // order, so I'm not too worried about this. - SigHelp.activeSignature = 0; - assert(CurrentArg <= (unsigned)std::numeric_limits::max() && - "too many arguments"); - SigHelp.activeParameter = static_cast(CurrentArg); - for (unsigned I = 0; I < NumCandidates; ++I) { - const auto &Candidate = Candidates[I]; - const auto *CCS = Candidate.CreateSignatureString( - CurrentArg, S, *Allocator, CCTUInfo, true); - assert(CCS && "Expected the CodeCompletionString to be non-null"); - SigHelp.signatures.push_back(ProcessOverloadCandidate(Candidate, *CCS)); - } - } - - GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; } - - CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } - -private: - SignatureInformation - ProcessOverloadCandidate(const OverloadCandidate &Candidate, - const CodeCompletionString &CCS) const { - SignatureInformation Result; - const char *ReturnType = nullptr; - - Result.documentation = getDocumentation(CCS); - - for (const auto &Chunk : CCS) { - switch (Chunk.Kind) { - case CodeCompletionString::CK_ResultType: - // A piece of text that describes the type of an entity or, - // for functions and methods, the return type. - assert(!ReturnType && "Unexpected CK_ResultType"); - ReturnType = Chunk.Text; - break; - case CodeCompletionString::CK_Placeholder: - // A string that acts as a placeholder for, e.g., a function call - // argument. - // Intentional fallthrough here. - case CodeCompletionString::CK_CurrentParameter: { - // A piece of text that describes the parameter that corresponds to - // the code-completion location within a function call, message send, - // macro invocation, etc. - Result.label += Chunk.Text; - ParameterInformation Info; - Info.label = Chunk.Text; - Result.parameters.push_back(std::move(Info)); - break; - } - case CodeCompletionString::CK_Optional: { - // The rest of the parameters are defaulted/optional. - assert(Chunk.Optional && - "Expected the optional code completion string to be non-null."); - Result.label += - getOptionalParameters(*Chunk.Optional, Result.parameters); - break; - } - case CodeCompletionString::CK_VerticalSpace: - break; - default: - Result.label += Chunk.Text; - break; - } - } - if (ReturnType) { - Result.label += " -> "; - Result.label += ReturnType; - } - return Result; - } - - SignatureHelp &SigHelp; - std::shared_ptr Allocator; - CodeCompletionTUInfo CCTUInfo; - -}; // SignatureHelpCollector - -bool invokeCodeComplete(std::unique_ptr Consumer, - const clang::CodeCompleteOptions &Options, - PathRef FileName, - const tooling::CompileCommand &Command, - PrecompiledPreamble const *Preamble, StringRef Contents, - Position Pos, IntrusiveRefCntPtr VFS, - std::shared_ptr PCHs, - clangd::Logger &Logger) { - std::vector ArgStrs; - for (const auto &S : Command.CommandLine) - ArgStrs.push_back(S.c_str()); - - VFS->setCurrentWorkingDirectory(Command.Directory); - - std::unique_ptr CI; - EmptyDiagsConsumer DummyDiagsConsumer; - { - IntrusiveRefCntPtr CommandLineDiagsEngine = - CompilerInstance::createDiagnostics(new DiagnosticOptions, - &DummyDiagsConsumer, false); - CI = createCompilerInvocation(ArgStrs, CommandLineDiagsEngine, VFS); - } - assert(CI && "Couldn't create CompilerInvocation"); - - std::unique_ptr ContentsBuffer = - llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName); - - // Attempt to reuse the PCH from precompiled preamble, if it was built. - if (Preamble) { - auto Bounds = - ComputePreambleBounds(*CI->getLangOpts(), ContentsBuffer.get(), 0); - if (!Preamble->CanReuse(*CI, ContentsBuffer.get(), Bounds, VFS.get())) - Preamble = nullptr; - } - - auto Clang = prepareCompilerInstance( - std::move(CI), Preamble, std::move(ContentsBuffer), std::move(PCHs), - std::move(VFS), DummyDiagsConsumer); - auto &DiagOpts = Clang->getDiagnosticOpts(); - DiagOpts.IgnoreWarnings = true; - - auto &FrontendOpts = Clang->getFrontendOpts(); - FrontendOpts.SkipFunctionBodies = true; - FrontendOpts.CodeCompleteOpts = Options; - FrontendOpts.CodeCompletionAt.FileName = FileName; - FrontendOpts.CodeCompletionAt.Line = Pos.line + 1; - FrontendOpts.CodeCompletionAt.Column = Pos.character + 1; - - Clang->setCodeCompletionConsumer(Consumer.release()); - - SyntaxOnlyAction Action; - if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) { - Logger.log("BeginSourceFile() failed when running codeComplete for " + - FileName); - return false; - } - if (!Action.Execute()) { - Logger.log("Execute() failed when running codeComplete for " + FileName); - return false; - } - - Action.EndSourceFile(); - - return true; -} - -} // namespace - -clang::CodeCompleteOptions -clangd::CodeCompleteOptions::getClangCompleteOpts() const { - clang::CodeCompleteOptions Result; - Result.IncludeCodePatterns = EnableSnippets && IncludeCodePatterns; - Result.IncludeMacros = IncludeMacros; - Result.IncludeGlobals = IncludeGlobals; - Result.IncludeBriefComments = IncludeBriefComments; - - return Result; -} - -CompletionList -clangd::codeComplete(PathRef FileName, const tooling::CompileCommand &Command, - PrecompiledPreamble const *Preamble, StringRef Contents, - Position Pos, IntrusiveRefCntPtr VFS, - std::shared_ptr PCHs, - clangd::CodeCompleteOptions Opts, clangd::Logger &Logger) { - CompletionList Results; - std::unique_ptr Consumer; - if (Opts.EnableSnippets) { - Consumer = - llvm::make_unique(Opts, Results); - } else { - Consumer = - llvm::make_unique(Opts, Results); - } - invokeCodeComplete(std::move(Consumer), Opts.getClangCompleteOpts(), FileName, - Command, Preamble, Contents, Pos, std::move(VFS), - std::move(PCHs), Logger); - return Results; -} - -SignatureHelp -clangd::signatureHelp(PathRef FileName, const tooling::CompileCommand &Command, - PrecompiledPreamble const *Preamble, StringRef Contents, - Position Pos, IntrusiveRefCntPtr VFS, - std::shared_ptr PCHs, - clangd::Logger &Logger) { - SignatureHelp Result; - clang::CodeCompleteOptions Options; - Options.IncludeGlobals = false; - Options.IncludeMacros = false; - Options.IncludeCodePatterns = false; - Options.IncludeBriefComments = true; - invokeCodeComplete(llvm::make_unique(Options, Result), - Options, FileName, Command, Preamble, Contents, Pos, - std::move(VFS), std::move(PCHs), Logger); - return Result; -} - void clangd::dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) { AST.getASTContext().getTranslationUnitDecl()->dump(OS, true); } @@ -946,7 +198,7 @@ // UnitDiagsConsumer is local, we can not store it in CompilerInstance that // has a longer lifetime. - Clang->getDiagnostics().setClient(new EmptyDiagsConsumer); + Clang->getDiagnostics().setClient(new IgnoreDiagnostics); std::vector ParsedDecls = Action->takeTopLevelDecls(); return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action), @@ -1302,11 +554,14 @@ { // FIXME(ibiryukov): store diagnostics from CommandLine when we start // reporting them. - EmptyDiagsConsumer CommandLineDiagsConsumer; + IgnoreDiagnostics IgnoreDiagnostics; IntrusiveRefCntPtr CommandLineDiagsEngine = CompilerInstance::createDiagnostics(new DiagnosticOptions, - &CommandLineDiagsConsumer, false); - CI = createCompilerInvocation(ArgStrs, CommandLineDiagsEngine, VFS); + &IgnoreDiagnostics, false); + CI = + createInvocationFromCommandLine(ArgStrs, CommandLineDiagsEngine, VFS); + // createInvocationFromCommandLine sets DisableFree. + CI->getFrontendOpts().DisableFree = false; } assert(CI && "Couldn't create CompilerInvocation"); Index: clangd/CodeComplete.h =================================================================== --- clangd/CodeComplete.h +++ clangd/CodeComplete.h @@ -0,0 +1,82 @@ +//===--- CodeComplete.h -----------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// Code completion provides suggestions for what the user might type next. +// After "std::string S; S." we might suggest members of std::string. +// Signature help describes the parameters of a function as you type them. +// +//===---------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETE_H + +#include "Logger.h" +#include "Path.h" +#include "Protocol.h" +#include "clang/Frontend/PrecompiledPreamble.h" +#include "clang/Sema/CodeCompleteOptions.h" +#include "clang/Tooling/CompilationDatabase.h" + +namespace clang { +class PCHContainerOperations; +namespace clangd { + +struct CodeCompleteOptions { + /// Returns options that can be passed to clang's completion engine. + clang::CodeCompleteOptions getClangCompleteOpts() const; + + /// When true, completion items will contain expandable code snippets in + /// completion (e.g. `return ${1:expression}` or `foo(${1:int a}, ${2:int + /// b})). + bool EnableSnippets = false; + + /// Add code patterns to completion results. + /// If EnableSnippets is false, this options is ignored and code patterns will + /// always be omitted. + bool IncludeCodePatterns = true; + + /// Add macros to code completion results. + bool IncludeMacros = true; + + /// Add globals to code completion results. + bool IncludeGlobals = true; + + /// Add brief comments to completion items, if available. + /// FIXME(ibiryukov): it looks like turning this option on significantly slows + /// down completion, investigate if it can be made faster. + bool IncludeBriefComments = true; + + /// Include results that are not legal completions in the current context. + /// For example, private members are usually inaccessible. + bool IncludeIneligibleResults = false; + + /// Limit the number of results returned (0 means no limit). + /// If more results are available, we set CompletionList.isIncomplete. + size_t Limit = 0; +}; + +/// Get code completions at a specified \p Pos in \p FileName. +CompletionList codeComplete(PathRef FileName, + const tooling::CompileCommand &Command, + PrecompiledPreamble const *Preamble, + StringRef Contents, Position Pos, + IntrusiveRefCntPtr VFS, + std::shared_ptr PCHs, + CodeCompleteOptions Opts, Logger &Logger); + +/// Get signature help at a specified \p Pos in \p FileName. +SignatureHelp +signatureHelp(PathRef FileName, const tooling::CompileCommand &Command, + PrecompiledPreamble const *Preamble, StringRef Contents, + Position Pos, IntrusiveRefCntPtr VFS, + std::shared_ptr PCHs, Logger &Logger); + +} // namespace clangd +} // namespace clang + +#endif Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -0,0 +1,706 @@ +//===--- CodeComplete.cpp ---------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// AST-based completions are provided using the completion hooks in Sema. +// +// Signature help works in a similar way as code completion, but it is simpler +// as there are typically fewer candidates. +// +//===---------------------------------------------------------------------===// + +#include "CodeComplete.h" +#include "Compiler.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Sema/CodeCompleteConsumer.h" +#include "clang/Sema/Sema.h" +#include + +namespace clang { +namespace clangd { +namespace { + +CompletionItemKind getKindOfDecl(CXCursorKind CursorKind) { + switch (CursorKind) { + case CXCursor_MacroInstantiation: + case CXCursor_MacroDefinition: + return CompletionItemKind::Text; + case CXCursor_CXXMethod: + return CompletionItemKind::Method; + case CXCursor_FunctionDecl: + case CXCursor_FunctionTemplate: + return CompletionItemKind::Function; + case CXCursor_Constructor: + case CXCursor_Destructor: + return CompletionItemKind::Constructor; + case CXCursor_FieldDecl: + return CompletionItemKind::Field; + case CXCursor_VarDecl: + case CXCursor_ParmDecl: + return CompletionItemKind::Variable; + case CXCursor_ClassDecl: + case CXCursor_StructDecl: + case CXCursor_UnionDecl: + case CXCursor_ClassTemplate: + case CXCursor_ClassTemplatePartialSpecialization: + return CompletionItemKind::Class; + case CXCursor_Namespace: + case CXCursor_NamespaceAlias: + case CXCursor_NamespaceRef: + return CompletionItemKind::Module; + case CXCursor_EnumConstantDecl: + return CompletionItemKind::Value; + case CXCursor_EnumDecl: + return CompletionItemKind::Enum; + case CXCursor_TypeAliasDecl: + case CXCursor_TypeAliasTemplateDecl: + case CXCursor_TypedefDecl: + case CXCursor_MemberRef: + case CXCursor_TypeRef: + return CompletionItemKind::Reference; + default: + return CompletionItemKind::Missing; + } +} + +CompletionItemKind getKind(CodeCompletionResult::ResultKind ResKind, + CXCursorKind CursorKind) { + switch (ResKind) { + case CodeCompletionResult::RK_Declaration: + return getKindOfDecl(CursorKind); + case CodeCompletionResult::RK_Keyword: + return CompletionItemKind::Keyword; + case CodeCompletionResult::RK_Macro: + return CompletionItemKind::Text; // unfortunately, there's no 'Macro' + // completion items in LSP. + case CodeCompletionResult::RK_Pattern: + return CompletionItemKind::Snippet; + } + llvm_unreachable("Unhandled CodeCompletionResult::ResultKind."); +} + +std::string escapeSnippet(const llvm::StringRef Text) { + std::string Result; + Result.reserve(Text.size()); // Assume '$', '}' and '\\' are rare. + for (const auto Character : Text) { + if (Character == '$' || Character == '}' || Character == '\\') + Result.push_back('\\'); + Result.push_back(Character); + } + return Result; +} + +std::string getDocumentation(const CodeCompletionString &CCS) { + // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this + // information in the documentation field. + std::string Result; + const unsigned AnnotationCount = CCS.getAnnotationCount(); + if (AnnotationCount > 0) { + Result += "Annotation"; + if (AnnotationCount == 1) { + Result += ": "; + } else /* AnnotationCount > 1 */ { + Result += "s: "; + } + for (unsigned I = 0; I < AnnotationCount; ++I) { + Result += CCS.getAnnotation(I); + Result.push_back(I == AnnotationCount - 1 ? '\n' : ' '); + } + } + // Add brief documentation (if there is any). + if (CCS.getBriefComment() != nullptr) { + if (!Result.empty()) { + // This means we previously added annotations. Add an extra newline + // character to make the annotations stand out. + Result.push_back('\n'); + } + Result += CCS.getBriefComment(); + } + return Result; +} + +/// Get the optional chunk as a string. This function is possibly recursive. +/// +/// The parameter info for each parameter is appended to the Parameters. +std::string +getOptionalParameters(const CodeCompletionString &CCS, + std::vector &Parameters) { + std::string Result; + for (const auto &Chunk : CCS) { + switch (Chunk.Kind) { + case CodeCompletionString::CK_Optional: + assert(Chunk.Optional && + "Expected the optional code completion string to be non-null."); + Result += getOptionalParameters(*Chunk.Optional, Parameters); + break; + case CodeCompletionString::CK_VerticalSpace: + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + // Intentional fallthrough here. + case CodeCompletionString::CK_CurrentParameter: { + // A piece of text that describes the parameter that corresponds to + // the code-completion location within a function call, message send, + // macro invocation, etc. + Result += Chunk.Text; + ParameterInformation Info; + Info.label = Chunk.Text; + Parameters.push_back(std::move(Info)); + break; + } + default: + Result += Chunk.Text; + break; + } + } + return Result; +} + + +/// A scored code completion result. +/// It may be promoted to a CompletionItem if it's among the top-ranked results. +struct CompletionCandidate { + CompletionCandidate(CodeCompletionResult &Result) + : Result(&Result), Score(score(Result)) {} + + CodeCompletionResult *Result; + float Score; // 0 to 1, higher is better. + + // Comparison reflects rank: better candidates are smaller. + bool operator<(const CompletionCandidate &C) const { + if (Score != C.Score) + return Score > C.Score; + return *Result < *C.Result; + } + + // Returns a string that sorts in the same order as operator<, for LSP. + // Conceptually, this is [-Score, Name]. We convert -Score to an integer, and + // hex-encode it for readability. Example: [0.5, "foo"] -> "41000000foo" + std::string sortText() const { + std::string S, NameStorage; + llvm::raw_string_ostream OS(S); + write_hex(OS, encodeFloat(-Score), llvm::HexPrintStyle::Lower, + /*Width=*/2 * sizeof(Score)); + OS << Result->getOrderedName(NameStorage); + return OS.str(); + } + +private: + static float score(const CodeCompletionResult &Result) { + // Priority 80 is a really bad score. + float Score = 1 - std::min(80, Result.Priority) / 80; + + switch (static_cast(Result.Availability)) { + case CXAvailability_Available: + // No penalty. + break; + case CXAvailability_Deprecated: + Score *= 0.1f; + break; + case CXAvailability_NotAccessible: + case CXAvailability_NotAvailable: + Score = 0; + break; + } + return Score; + } + + // Produces an integer that sorts in the same order as F. + // That is: a < b <==> encodeFloat(a) < encodeFloat(b). + static uint32_t encodeFloat(float F) { + static_assert(std::numeric_limits::is_iec559, ""); + static_assert(sizeof(float) == sizeof(uint32_t), ""); + constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1); + + // Get the bits of the float. Endianness is the same as for integers. + uint32_t U; + memcpy(&U, &F, sizeof(float)); + // IEEE 754 floats compare like sign-magnitude integers. + if (U & TopBit) // Negative float. + return 0 - U; // Map onto the low half of integers, order reversed. + return U + TopBit; // Positive floats map onto the high half of integers. + } +}; + +class CompletionItemsCollector : public CodeCompleteConsumer { +public: + CompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts, + CompletionList &Items) + : CodeCompleteConsumer(CodeCompleteOpts.getClangCompleteOpts(), + /*OutputIsBinary=*/false), + ClangdOpts(CodeCompleteOpts), Items(Items), + Allocator(std::make_shared()), + CCTUInfo(Allocator) {} + + void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, + CodeCompletionResult *Results, + unsigned NumResults) override final { + StringRef Filter = S.getPreprocessor().getCodeCompletionFilter(); + std::priority_queue Candidates; + for (unsigned I = 0; I < NumResults; ++I) { + auto &Result = Results[I]; + if (!ClangdOpts.IncludeIneligibleResults && + (Result.Availability == CXAvailability_NotAvailable || + Result.Availability == CXAvailability_NotAccessible)) + continue; + if (!Filter.empty() && !fuzzyMatch(S, Context, Filter, Result)) + continue; + Candidates.emplace(Result); + if (ClangdOpts.Limit && Candidates.size() > ClangdOpts.Limit) { + Candidates.pop(); + Items.isIncomplete = true; + } + } + while (!Candidates.empty()) { + auto &Candidate = Candidates.top(); + const auto *CCS = Candidate.Result->CreateCodeCompletionString( + S, Context, *Allocator, CCTUInfo, + CodeCompleteOpts.IncludeBriefComments); + assert(CCS && "Expected the CodeCompletionString to be non-null"); + Items.items.push_back(ProcessCodeCompleteResult(Candidate, *CCS)); + Candidates.pop(); + } + std::reverse(Items.items.begin(), Items.items.end()); + } + + GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; } + + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } + +private: + bool fuzzyMatch(Sema &S, const CodeCompletionContext &CCCtx, StringRef Filter, + CodeCompletionResult Result) { + switch (Result.Kind) { + case CodeCompletionResult::RK_Declaration: + if (auto *ID = Result.Declaration->getIdentifier()) + return fuzzyMatch(Filter, ID->getName()); + break; + case CodeCompletionResult::RK_Keyword: + return fuzzyMatch(Filter, Result.Keyword); + case CodeCompletionResult::RK_Macro: + return fuzzyMatch(Filter, Result.Macro->getName()); + case CodeCompletionResult::RK_Pattern: + return fuzzyMatch(Filter, Result.Pattern->getTypedText()); + } + auto *CCS = Result.CreateCodeCompletionString( + S, CCCtx, *Allocator, CCTUInfo, /*IncludeBriefComments=*/false); + return fuzzyMatch(Filter, CCS->getTypedText()); + } + + // Checks whether Target matches the Filter. + // Currently just requires a case-insensitive subsequence match. + // FIXME: make stricter and word-based: 'unique_ptr' should not match 'que'. + // FIXME: return a score to be incorporated into ranking. + static bool fuzzyMatch(StringRef Filter, StringRef Target) { + size_t TPos = 0; + for (char C : Filter) { + TPos = Target.find_lower(C, TPos); + if (TPos == StringRef::npos) + return false; + } + return true; + } + + CompletionItem + ProcessCodeCompleteResult(const CompletionCandidate &Candidate, + const CodeCompletionString &CCS) const { + + // Adjust this to InsertTextFormat::Snippet iff we encounter a + // CK_Placeholder chunk in SnippetCompletionItemsCollector. + CompletionItem Item; + Item.insertTextFormat = InsertTextFormat::PlainText; + + Item.documentation = getDocumentation(CCS); + Item.sortText = Candidate.sortText(); + + // Fill in the label, detail, insertText and filterText fields of the + // CompletionItem. + ProcessChunks(CCS, Item); + + // Fill in the kind field of the CompletionItem. + Item.kind = getKind(Candidate.Result->Kind, Candidate.Result->CursorKind); + + return Item; + } + + virtual void ProcessChunks(const CodeCompletionString &CCS, + CompletionItem &Item) const = 0; + + CodeCompleteOptions ClangdOpts; + CompletionList &Items; + std::shared_ptr Allocator; + CodeCompletionTUInfo CCTUInfo; + +}; // CompletionItemsCollector + +bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) { + return Chunk.Kind == CodeCompletionString::CK_Informative && + StringRef(Chunk.Text).endswith("::"); +} + +class PlainTextCompletionItemsCollector final + : public CompletionItemsCollector { + +public: + PlainTextCompletionItemsCollector( + const CodeCompleteOptions &CodeCompleteOpts, + CompletionList &Items) + : CompletionItemsCollector(CodeCompleteOpts, Items) {} + +private: + void ProcessChunks(const CodeCompletionString &CCS, + CompletionItem &Item) const override { + for (const auto &Chunk : CCS) { + // Informative qualifier chunks only clutter completion results, skip + // them. + if (isInformativeQualifierChunk(Chunk)) + continue; + + switch (Chunk.Kind) { + case CodeCompletionString::CK_TypedText: + // There's always exactly one CK_TypedText chunk. + Item.insertText = Item.filterText = Chunk.Text; + Item.label += Chunk.Text; + break; + case CodeCompletionString::CK_ResultType: + assert(Item.detail.empty() && "Unexpected extraneous CK_ResultType"); + Item.detail = Chunk.Text; + break; + case CodeCompletionString::CK_Optional: + break; + default: + Item.label += Chunk.Text; + break; + } + } + } +}; // PlainTextCompletionItemsCollector + +class SnippetCompletionItemsCollector final : public CompletionItemsCollector { + +public: + SnippetCompletionItemsCollector( + const CodeCompleteOptions &CodeCompleteOpts, + CompletionList &Items) + : CompletionItemsCollector(CodeCompleteOpts, Items) {} + +private: + void ProcessChunks(const CodeCompletionString &CCS, + CompletionItem &Item) const override { + unsigned ArgCount = 0; + for (const auto &Chunk : CCS) { + // Informative qualifier chunks only clutter completion results, skip + // them. + if (isInformativeQualifierChunk(Chunk)) + continue; + + switch (Chunk.Kind) { + case CodeCompletionString::CK_TypedText: + // The piece of text that the user is expected to type to match + // the code-completion string, typically a keyword or the name of + // a declarator or macro. + Item.filterText = Chunk.Text; + LLVM_FALLTHROUGH; + case CodeCompletionString::CK_Text: + // A piece of text that should be placed in the buffer, + // e.g., parentheses or a comma in a function call. + Item.label += Chunk.Text; + Item.insertText += Chunk.Text; + break; + case CodeCompletionString::CK_Optional: + // A code completion string that is entirely optional. + // For example, an optional code completion string that + // describes the default arguments in a function call. + + // FIXME: Maybe add an option to allow presenting the optional chunks? + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + ++ArgCount; + Item.insertText += "${" + std::to_string(ArgCount) + ':' + + escapeSnippet(Chunk.Text) + '}'; + Item.label += Chunk.Text; + Item.insertTextFormat = InsertTextFormat::Snippet; + break; + case CodeCompletionString::CK_Informative: + // A piece of text that describes something about the result + // but should not be inserted into the buffer. + // For example, the word "const" for a const method, or the name of + // the base class for methods that are part of the base class. + Item.label += Chunk.Text; + // Don't put the informative chunks in the insertText. + break; + case CodeCompletionString::CK_ResultType: + // A piece of text that describes the type of an entity or, + // for functions and methods, the return type. + assert(Item.detail.empty() && "Unexpected extraneous CK_ResultType"); + Item.detail = Chunk.Text; + break; + case CodeCompletionString::CK_CurrentParameter: + // A piece of text that describes the parameter that corresponds to + // the code-completion location within a function call, message send, + // macro invocation, etc. + // + // This should never be present while collecting completion items, + // only while collecting overload candidates. + llvm_unreachable("Unexpected CK_CurrentParameter while collecting " + "CompletionItems"); + break; + case CodeCompletionString::CK_LeftParen: + // A left parenthesis ('('). + case CodeCompletionString::CK_RightParen: + // A right parenthesis (')'). + case CodeCompletionString::CK_LeftBracket: + // A left bracket ('['). + case CodeCompletionString::CK_RightBracket: + // A right bracket (']'). + case CodeCompletionString::CK_LeftBrace: + // A left brace ('{'). + case CodeCompletionString::CK_RightBrace: + // A right brace ('}'). + case CodeCompletionString::CK_LeftAngle: + // A left angle bracket ('<'). + case CodeCompletionString::CK_RightAngle: + // A right angle bracket ('>'). + case CodeCompletionString::CK_Comma: + // A comma separator (','). + case CodeCompletionString::CK_Colon: + // A colon (':'). + case CodeCompletionString::CK_SemiColon: + // A semicolon (';'). + case CodeCompletionString::CK_Equal: + // An '=' sign. + case CodeCompletionString::CK_HorizontalSpace: + // Horizontal whitespace (' '). + Item.insertText += Chunk.Text; + Item.label += Chunk.Text; + break; + case CodeCompletionString::CK_VerticalSpace: + // Vertical whitespace ('\n' or '\r\n', depending on the + // platform). + Item.insertText += Chunk.Text; + // Don't even add a space to the label. + break; + } + } + } +}; // SnippetCompletionItemsCollector + +class SignatureHelpCollector final : public CodeCompleteConsumer { + +public: + SignatureHelpCollector(const clang::CodeCompleteOptions &CodeCompleteOpts, + SignatureHelp &SigHelp) + : CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false), + SigHelp(SigHelp), + Allocator(std::make_shared()), + CCTUInfo(Allocator) {} + + void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates) override { + SigHelp.signatures.reserve(NumCandidates); + // FIXME(rwols): How can we determine the "active overload candidate"? + // Right now the overloaded candidates seem to be provided in a "best fit" + // order, so I'm not too worried about this. + SigHelp.activeSignature = 0; + assert(CurrentArg <= (unsigned)std::numeric_limits::max() && + "too many arguments"); + SigHelp.activeParameter = static_cast(CurrentArg); + for (unsigned I = 0; I < NumCandidates; ++I) { + const auto &Candidate = Candidates[I]; + const auto *CCS = Candidate.CreateSignatureString( + CurrentArg, S, *Allocator, CCTUInfo, true); + assert(CCS && "Expected the CodeCompletionString to be non-null"); + SigHelp.signatures.push_back(ProcessOverloadCandidate(Candidate, *CCS)); + } + } + + GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; } + + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } + +private: + SignatureInformation + ProcessOverloadCandidate(const OverloadCandidate &Candidate, + const CodeCompletionString &CCS) const { + SignatureInformation Result; + const char *ReturnType = nullptr; + + Result.documentation = getDocumentation(CCS); + + for (const auto &Chunk : CCS) { + switch (Chunk.Kind) { + case CodeCompletionString::CK_ResultType: + // A piece of text that describes the type of an entity or, + // for functions and methods, the return type. + assert(!ReturnType && "Unexpected CK_ResultType"); + ReturnType = Chunk.Text; + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + // Intentional fallthrough here. + case CodeCompletionString::CK_CurrentParameter: { + // A piece of text that describes the parameter that corresponds to + // the code-completion location within a function call, message send, + // macro invocation, etc. + Result.label += Chunk.Text; + ParameterInformation Info; + Info.label = Chunk.Text; + Result.parameters.push_back(std::move(Info)); + break; + } + case CodeCompletionString::CK_Optional: { + // The rest of the parameters are defaulted/optional. + assert(Chunk.Optional && + "Expected the optional code completion string to be non-null."); + Result.label += + getOptionalParameters(*Chunk.Optional, Result.parameters); + break; + } + case CodeCompletionString::CK_VerticalSpace: + break; + default: + Result.label += Chunk.Text; + break; + } + } + if (ReturnType) { + Result.label += " -> "; + Result.label += ReturnType; + } + return Result; + } + + SignatureHelp &SigHelp; + std::shared_ptr Allocator; + CodeCompletionTUInfo CCTUInfo; + +}; // SignatureHelpCollector + +bool invokeCodeComplete(std::unique_ptr Consumer, + const clang::CodeCompleteOptions &Options, + PathRef FileName, + const tooling::CompileCommand &Command, + PrecompiledPreamble const *Preamble, StringRef Contents, + Position Pos, IntrusiveRefCntPtr VFS, + std::shared_ptr PCHs, + Logger &Logger) { + std::vector ArgStrs; + for (const auto &S : Command.CommandLine) + ArgStrs.push_back(S.c_str()); + + VFS->setCurrentWorkingDirectory(Command.Directory); + + IgnoreDiagnostics DummyDiagsConsumer; + auto CI = createInvocationFromCommandLine( + ArgStrs, + CompilerInstance::createDiagnostics(new DiagnosticOptions, + &DummyDiagsConsumer, false), + VFS); + assert(CI && "Couldn't create CompilerInvocation"); + + std::unique_ptr ContentsBuffer = + llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName); + + // Attempt to reuse the PCH from precompiled preamble, if it was built. + if (Preamble) { + auto Bounds = + ComputePreambleBounds(*CI->getLangOpts(), ContentsBuffer.get(), 0); + if (!Preamble->CanReuse(*CI, ContentsBuffer.get(), Bounds, VFS.get())) + Preamble = nullptr; + } + + auto Clang = prepareCompilerInstance( + std::move(CI), Preamble, std::move(ContentsBuffer), std::move(PCHs), + std::move(VFS), DummyDiagsConsumer); + auto &DiagOpts = Clang->getDiagnosticOpts(); + DiagOpts.IgnoreWarnings = true; + + auto &FrontendOpts = Clang->getFrontendOpts(); + FrontendOpts.SkipFunctionBodies = true; + FrontendOpts.CodeCompleteOpts = Options; + FrontendOpts.CodeCompletionAt.FileName = FileName; + FrontendOpts.CodeCompletionAt.Line = Pos.line + 1; + FrontendOpts.CodeCompletionAt.Column = Pos.character + 1; + + Clang->setCodeCompletionConsumer(Consumer.release()); + + SyntaxOnlyAction Action; + if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) { + Logger.log("BeginSourceFile() failed when running codeComplete for " + + FileName); + return false; + } + if (!Action.Execute()) { + Logger.log("Execute() failed when running codeComplete for " + FileName); + return false; + } + + Action.EndSourceFile(); + + return true; +} + +} // namespace + +clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const { + clang::CodeCompleteOptions Result; + Result.IncludeCodePatterns = EnableSnippets && IncludeCodePatterns; + Result.IncludeMacros = IncludeMacros; + Result.IncludeGlobals = IncludeGlobals; + Result.IncludeBriefComments = IncludeBriefComments; + + return Result; +} + +CompletionList codeComplete(PathRef FileName, + const tooling::CompileCommand &Command, + PrecompiledPreamble const *Preamble, + StringRef Contents, Position Pos, + IntrusiveRefCntPtr VFS, + std::shared_ptr PCHs, + CodeCompleteOptions Opts, Logger &Logger) { + CompletionList Results; + std::unique_ptr Consumer; + if (Opts.EnableSnippets) { + Consumer = + llvm::make_unique(Opts, Results); + } else { + Consumer = + llvm::make_unique(Opts, Results); + } + invokeCodeComplete(std::move(Consumer), Opts.getClangCompleteOpts(), FileName, + Command, Preamble, Contents, Pos, std::move(VFS), + std::move(PCHs), Logger); + return Results; +} + +SignatureHelp +signatureHelp(PathRef FileName, const tooling::CompileCommand &Command, + PrecompiledPreamble const *Preamble, StringRef Contents, + Position Pos, IntrusiveRefCntPtr VFS, + std::shared_ptr PCHs, Logger &Logger) { + SignatureHelp Result; + clang::CodeCompleteOptions Options; + Options.IncludeGlobals = false; + Options.IncludeMacros = false; + Options.IncludeCodePatterns = false; + Options.IncludeBriefComments = true; + invokeCodeComplete(llvm::make_unique(Options, Result), + Options, FileName, Command, Preamble, Contents, Pos, + std::move(VFS), std::move(PCHs), Logger); + return Result; +} + +} // namespace clangd +} // namespace clang Index: clangd/Compiler.h =================================================================== --- clangd/Compiler.h +++ clangd/Compiler.h @@ -0,0 +1,46 @@ +//===--- Compiler.h ---------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// Shared utilities for invoking the clang compiler. +// ClangdUnit takes care of much of this, but some features like CodeComplete +// run their own compile actions that share logic. +// +//===---------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILER_H +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/PrecompiledPreamble.h" + +namespace clang { +namespace clangd { + +class IgnoreDiagnostics : public DiagnosticConsumer { +public: + void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) override {} +}; + +/// Creates a CompilerInstance with the main file contens overridden. +/// The preamble will be reused unless it is null. +/// Note that the vfs::FileSystem inside returned instance may differ if +/// additional file remappings occur in command-line arguments. +/// On some errors, returns null. When non-null value is returned, it's expected +/// to be consumed by the FrontendAction as it will have a pointer to the +/// MainFile buffer that will only be deleted if BeginSourceFile is called. +std::unique_ptr prepareCompilerInstance( + std::unique_ptr, const PrecompiledPreamble *, + std::unique_ptr MainFile, + std::shared_ptr, + IntrusiveRefCntPtr, DiagnosticConsumer &); + +} // namespace clangd +} // namespace clang + +#endif Index: clangd/Compiler.cpp =================================================================== --- clangd/Compiler.cpp +++ clangd/Compiler.cpp @@ -0,0 +1,65 @@ +//===--- Compiler.cpp -------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +#include "Compiler.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Lex/PreprocessorOptions.h" + +namespace clang { +namespace clangd { + +/// Creates a CompilerInstance from \p CI, with main buffer overriden to \p +/// Buffer and arguments to read the PCH from \p Preamble, if \p Preamble is not +/// null. Note that vfs::FileSystem inside returned instance may differ from \p +/// VFS if additional file remapping were set in command-line arguments. +/// On some errors, returns null. When non-null value is returned, it's expected +/// to be consumed by the FrontendAction as it will have a pointer to the \p +/// Buffer that will only be deleted if BeginSourceFile is called. +std::unique_ptr +prepareCompilerInstance(std::unique_ptr CI, + const PrecompiledPreamble *Preamble, + std::unique_ptr Buffer, + std::shared_ptr PCHs, + IntrusiveRefCntPtr VFS, + DiagnosticConsumer &DiagsClient) { + assert(VFS && "VFS is null"); + assert(!CI->getPreprocessorOpts().RetainRemappedFileBuffers && + "Setting RetainRemappedFileBuffers to true will cause a memory leak " + "of ContentsBuffer"); + + // NOTE: we use Buffer.get() when adding remapped files, so we have to make + // sure it will be released if no error is emitted. + if (Preamble) { + Preamble->AddImplicitPreamble(*CI, VFS, Buffer.get()); + } else { + CI->getPreprocessorOpts().addRemappedFile( + CI->getFrontendOpts().Inputs[0].getFile(), Buffer.get()); + } + + auto Clang = llvm::make_unique(PCHs); + Clang->setInvocation(std::move(CI)); + Clang->createDiagnostics(&DiagsClient, false); + + if (auto VFSWithRemapping = createVFSFromCompilerInvocation( + Clang->getInvocation(), Clang->getDiagnostics(), VFS)) + VFS = VFSWithRemapping; + Clang->setVirtualFileSystem(VFS); + + Clang->setTarget(TargetInfo::CreateTargetInfo( + Clang->getDiagnostics(), Clang->getInvocation().TargetOpts)); + if (!Clang->hasTarget()) + return nullptr; + + // RemappedFileBuffers will handle the lifetime of the Buffer pointer, + // release it. + Buffer.release(); + return Clang; +} + +} // namespace clangd +} // namespace clang