Mercurial > hg > CbC > CbC_llvm
view clang-tools-extra/clangd/CodeCompletionStrings.cpp @ 214:0cf2d4ade63d
...
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Tue, 13 Jul 2021 09:53:52 +0900 |
parents | 2e18cbf3894f |
children | c4bab56944e8 |
line wrap: on
line source
//===--- CodeCompletionStrings.cpp -------------------------------*- C++-*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "CodeCompletionStrings.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/RawCommentList.h" #include "clang/Basic/SourceManager.h" #include "clang/Sema/CodeCompleteConsumer.h" #include "llvm/Support/JSON.h" #include <limits> #include <utility> namespace clang { namespace clangd { namespace { bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) { return Chunk.Kind == CodeCompletionString::CK_Informative && llvm::StringRef(Chunk.Text).endswith("::"); } void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) { for (const auto Character : Text) { if (Character == '$' || Character == '}' || Character == '\\') Out->push_back('\\'); Out->push_back(Character); } } void appendOptionalChunk(const CodeCompletionString &CCS, std::string *Out) { for (const CodeCompletionString::Chunk &C : CCS) { switch (C.Kind) { case CodeCompletionString::CK_Optional: assert(C.Optional && "Expected the optional code completion string to be non-null."); appendOptionalChunk(*C.Optional, Out); break; default: *Out += C.Text; break; } } } bool looksLikeDocComment(llvm::StringRef CommentText) { // We don't report comments that only contain "special" chars. // This avoids reporting various delimiters, like: // ================= // ----------------- // ***************** return CommentText.find_first_not_of("/*-= \t\r\n") != llvm::StringRef::npos; } } // namespace std::string getDocComment(const ASTContext &Ctx, const CodeCompletionResult &Result, bool CommentsFromHeaders) { // FIXME: clang's completion also returns documentation for RK_Pattern if they // contain a pattern for ObjC properties. Unfortunately, there is no API to // get this declaration, so we don't show documentation in that case. if (Result.Kind != CodeCompletionResult::RK_Declaration) return ""; return Result.getDeclaration() ? getDeclComment(Ctx, *Result.getDeclaration()) : ""; } std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) { if (isa<NamespaceDecl>(Decl)) { // Namespaces often have too many redecls for any particular redecl comment // to be useful. Moreover, we often confuse file headers or generated // comments with namespace comments. Therefore we choose to just ignore // the comments for namespaces. return ""; } const RawComment *RC = getCompletionComment(Ctx, &Decl); if (!RC) return ""; // Sanity check that the comment does not come from the PCH. We choose to not // write them into PCH, because they are racy and slow to load. assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc())); std::string Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); if (!looksLikeDocComment(Doc)) return ""; // Clang requires source to be UTF-8, but doesn't enforce this in comments. if (!llvm::json::isUTF8(Doc)) Doc = llvm::json::fixUTF8(Doc); return Doc; } void getSignature(const CodeCompletionString &CCS, std::string *Signature, std::string *Snippet, std::string *RequiredQualifiers, bool CompletingPattern) { // Placeholder with this index will be ${0:…} to mark final cursor position. // Usually we do not add $0, so the cursor is placed at end of completed text. unsigned CursorSnippetArg = std::numeric_limits<unsigned>::max(); if (CompletingPattern) { // In patterns, it's best to place the cursor at the last placeholder, to // handle cases like // namespace ${1:name} { // ${0:decls} // } CursorSnippetArg = llvm::count_if(CCS, [](const CodeCompletionString::Chunk &C) { return C.Kind == CodeCompletionString::CK_Placeholder; }); } unsigned SnippetArg = 0; bool HadObjCArguments = false; bool HadInformativeChunks = false; 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 typed-text chunk is the actual name. We don't record this chunk. // C++: // In general our string looks like <qualifiers><name><signature>. // So once we see the name, any text we recorded so far should be // reclassified as qualifiers. // // Objective-C: // Objective-C methods expressions may have multiple typed-text chunks, // so we must treat them carefully. For Objective-C methods, all // typed-text and informative chunks will end in ':' (unless there are // no arguments, in which case we can safely treat them as C++). // // Completing a method declaration itself (not a method expression) is // similar except that we use the `RequiredQualifiers` to store the // text before the selector, e.g. `- (void)`. if (!llvm::StringRef(Chunk.Text).endswith(":")) { // Treat as C++. if (RequiredQualifiers) *RequiredQualifiers = std::move(*Signature); Signature->clear(); Snippet->clear(); } else { // Objective-C method with args. // If this is the first TypedText to the Objective-C method, discard any // text that we've previously seen (such as previous parameter selector, // which will be marked as Informative text). // // TODO: Make previous parameters part of the signature for Objective-C // methods. if (!HadObjCArguments) { HadObjCArguments = true; // If we have no previous informative chunks (informative selector // fragments in practice), we treat any previous chunks as // `RequiredQualifiers` so they will be added as a prefix during the // completion. // // e.g. to complete `- (void)doSomething:(id)argument`: // - Completion name: `doSomething:` // - RequiredQualifiers: `- (void)` // - Snippet/Signature suffix: `(id)argument` // // This differs from the case when we're completing a method // expression with a previous informative selector fragment. // // e.g. to complete `[self doSomething:nil ^somethingElse:(id)]`: // - Previous Informative Chunk: `doSomething:` // - Completion name: `somethingElse:` // - Snippet/Signature suffix: `(id)` if (!HadInformativeChunks) { if (RequiredQualifiers) *RequiredQualifiers = std::move(*Signature); Snippet->clear(); } Signature->clear(); } else { // Subsequent argument, considered part of snippet/signature. *Signature += Chunk.Text; *Snippet += Chunk.Text; } } break; case CodeCompletionString::CK_Text: *Signature += Chunk.Text; *Snippet += Chunk.Text; break; case CodeCompletionString::CK_Optional: assert(Chunk.Optional); // No need to create placeholders for default arguments in Snippet. appendOptionalChunk(*Chunk.Optional, Signature); break; case CodeCompletionString::CK_Placeholder: *Signature += Chunk.Text; ++SnippetArg; *Snippet += "${" + std::to_string(SnippetArg == CursorSnippetArg ? 0 : SnippetArg) + ':'; appendEscapeSnippet(Chunk.Text, Snippet); *Snippet += '}'; break; case CodeCompletionString::CK_Informative: HadInformativeChunks = true; // 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. *Signature += Chunk.Text; // Don't put the informative chunks in the snippet. break; case CodeCompletionString::CK_ResultType: // This is not part of the signature. break; case CodeCompletionString::CK_CurrentParameter: // 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: case CodeCompletionString::CK_RightParen: case CodeCompletionString::CK_LeftBracket: case CodeCompletionString::CK_RightBracket: case CodeCompletionString::CK_LeftBrace: case CodeCompletionString::CK_RightBrace: case CodeCompletionString::CK_LeftAngle: case CodeCompletionString::CK_RightAngle: case CodeCompletionString::CK_Comma: case CodeCompletionString::CK_Colon: case CodeCompletionString::CK_SemiColon: case CodeCompletionString::CK_Equal: case CodeCompletionString::CK_HorizontalSpace: *Signature += Chunk.Text; *Snippet += Chunk.Text; break; case CodeCompletionString::CK_VerticalSpace: *Snippet += Chunk.Text; // Don't even add a space to the signature. break; } } } std::string formatDocumentation(const CodeCompletionString &CCS, llvm::StringRef DocComment) { // 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 (!DocComment.empty()) { 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 += DocComment; } return Result; } std::string getReturnType(const CodeCompletionString &CCS) { for (const auto &Chunk : CCS) if (Chunk.Kind == CodeCompletionString::CK_ResultType) return Chunk.Text; return ""; } } // namespace clangd } // namespace clang