1 //===--- CodeCompletionStrings.cpp -------------------------------*- C++-*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "CodeCompletionStrings.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/AST/DeclObjC.h" 12 #include "clang/AST/RawCommentList.h" 13 #include "clang/Basic/SourceManager.h" 14 #include "clang/Sema/CodeCompleteConsumer.h" 15 #include <limits> 16 #include <utility> 17 18 namespace clang { 19 namespace clangd { 20 namespace { 21 22 bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) { 23 return Chunk.Kind == CodeCompletionString::CK_Informative && 24 llvm::StringRef(Chunk.Text).endswith("::"); 25 } 26 27 void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) { 28 for (const auto Character : Text) { 29 if (Character == '$' || Character == '}' || Character == '\\') 30 Out->push_back('\\'); 31 Out->push_back(Character); 32 } 33 } 34 35 bool looksLikeDocComment(llvm::StringRef CommentText) { 36 // We don't report comments that only contain "special" chars. 37 // This avoids reporting various delimiters, like: 38 // ================= 39 // ----------------- 40 // ***************** 41 return CommentText.find_first_not_of("/*-= \t\r\n") != llvm::StringRef::npos; 42 } 43 44 } // namespace 45 46 std::string getDocComment(const ASTContext &Ctx, 47 const CodeCompletionResult &Result, 48 bool CommentsFromHeaders) { 49 // FIXME: clang's completion also returns documentation for RK_Pattern if they 50 // contain a pattern for ObjC properties. Unfortunately, there is no API to 51 // get this declaration, so we don't show documentation in that case. 52 if (Result.Kind != CodeCompletionResult::RK_Declaration) 53 return ""; 54 return Result.getDeclaration() ? getDeclComment(Ctx, *Result.getDeclaration()) 55 : ""; 56 } 57 58 std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) { 59 if (isa<NamespaceDecl>(Decl)) { 60 // Namespaces often have too many redecls for any particular redecl comment 61 // to be useful. Moreover, we often confuse file headers or generated 62 // comments with namespace comments. Therefore we choose to just ignore 63 // the comments for namespaces. 64 return ""; 65 } 66 const RawComment *RC = getCompletionComment(Ctx, &Decl); 67 if (!RC) 68 return ""; 69 // Sanity check that the comment does not come from the PCH. We choose to not 70 // write them into PCH, because they are racy and slow to load. 71 assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc())); 72 std::string Doc = 73 RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); 74 return looksLikeDocComment(Doc) ? Doc : ""; 75 } 76 77 void getSignature(const CodeCompletionString &CCS, std::string *Signature, 78 std::string *Snippet, std::string *RequiredQualifiers, 79 bool CompletingPattern) { 80 // Placeholder with this index will be ${0:…} to mark final cursor position. 81 // Usually we do not add $0, so the cursor is placed at end of completed text. 82 unsigned CursorSnippetArg = std::numeric_limits<unsigned>::max(); 83 if (CompletingPattern) { 84 // In patterns, it's best to place the cursor at the last placeholder, to 85 // handle cases like 86 // namespace ${1:name} { 87 // ${0:decls} 88 // } 89 CursorSnippetArg = 90 llvm::count_if(CCS, [](const CodeCompletionString::Chunk &C) { 91 return C.Kind == CodeCompletionString::CK_Placeholder; 92 }); 93 } 94 unsigned SnippetArg = 0; 95 bool HadObjCArguments = false; 96 for (const auto &Chunk : CCS) { 97 // Informative qualifier chunks only clutter completion results, skip 98 // them. 99 if (isInformativeQualifierChunk(Chunk)) 100 continue; 101 102 switch (Chunk.Kind) { 103 case CodeCompletionString::CK_TypedText: 104 // The typed-text chunk is the actual name. We don't record this chunk. 105 // C++: 106 // In general our string looks like <qualifiers><name><signature>. 107 // So once we see the name, any text we recorded so far should be 108 // reclassified as qualifiers. 109 // 110 // Objective-C: 111 // Objective-C methods may have multiple typed-text chunks, so we must 112 // treat them carefully. For Objective-C methods, all typed-text chunks 113 // will end in ':' (unless there are no arguments, in which case we 114 // can safely treat them as C++). 115 if (!llvm::StringRef(Chunk.Text).endswith(":")) { // Treat as C++. 116 if (RequiredQualifiers) 117 *RequiredQualifiers = std::move(*Signature); 118 Signature->clear(); 119 Snippet->clear(); 120 } else { // Objective-C method with args. 121 // If this is the first TypedText to the Objective-C method, discard any 122 // text that we've previously seen (such as previous parameter selector, 123 // which will be marked as Informative text). 124 // 125 // TODO: Make previous parameters part of the signature for Objective-C 126 // methods. 127 if (!HadObjCArguments) { 128 HadObjCArguments = true; 129 Signature->clear(); 130 } else { // Subsequent argument, considered part of snippet/signature. 131 *Signature += Chunk.Text; 132 *Snippet += Chunk.Text; 133 } 134 } 135 break; 136 case CodeCompletionString::CK_Text: 137 *Signature += Chunk.Text; 138 *Snippet += Chunk.Text; 139 break; 140 case CodeCompletionString::CK_Optional: 141 break; 142 case CodeCompletionString::CK_Placeholder: 143 *Signature += Chunk.Text; 144 ++SnippetArg; 145 *Snippet += 146 "${" + 147 std::to_string(SnippetArg == CursorSnippetArg ? 0 : SnippetArg) + ':'; 148 appendEscapeSnippet(Chunk.Text, Snippet); 149 *Snippet += '}'; 150 break; 151 case CodeCompletionString::CK_Informative: 152 // For example, the word "const" for a const method, or the name of 153 // the base class for methods that are part of the base class. 154 *Signature += Chunk.Text; 155 // Don't put the informative chunks in the snippet. 156 break; 157 case CodeCompletionString::CK_ResultType: 158 // This is not part of the signature. 159 break; 160 case CodeCompletionString::CK_CurrentParameter: 161 // This should never be present while collecting completion items, 162 // only while collecting overload candidates. 163 llvm_unreachable("Unexpected CK_CurrentParameter while collecting " 164 "CompletionItems"); 165 break; 166 case CodeCompletionString::CK_LeftParen: 167 case CodeCompletionString::CK_RightParen: 168 case CodeCompletionString::CK_LeftBracket: 169 case CodeCompletionString::CK_RightBracket: 170 case CodeCompletionString::CK_LeftBrace: 171 case CodeCompletionString::CK_RightBrace: 172 case CodeCompletionString::CK_LeftAngle: 173 case CodeCompletionString::CK_RightAngle: 174 case CodeCompletionString::CK_Comma: 175 case CodeCompletionString::CK_Colon: 176 case CodeCompletionString::CK_SemiColon: 177 case CodeCompletionString::CK_Equal: 178 case CodeCompletionString::CK_HorizontalSpace: 179 *Signature += Chunk.Text; 180 *Snippet += Chunk.Text; 181 break; 182 case CodeCompletionString::CK_VerticalSpace: 183 *Snippet += Chunk.Text; 184 // Don't even add a space to the signature. 185 break; 186 } 187 } 188 } 189 190 std::string formatDocumentation(const CodeCompletionString &CCS, 191 llvm::StringRef DocComment) { 192 // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this 193 // information in the documentation field. 194 std::string Result; 195 const unsigned AnnotationCount = CCS.getAnnotationCount(); 196 if (AnnotationCount > 0) { 197 Result += "Annotation"; 198 if (AnnotationCount == 1) { 199 Result += ": "; 200 } else /* AnnotationCount > 1 */ { 201 Result += "s: "; 202 } 203 for (unsigned I = 0; I < AnnotationCount; ++I) { 204 Result += CCS.getAnnotation(I); 205 Result.push_back(I == AnnotationCount - 1 ? '\n' : ' '); 206 } 207 } 208 // Add brief documentation (if there is any). 209 if (!DocComment.empty()) { 210 if (!Result.empty()) { 211 // This means we previously added annotations. Add an extra newline 212 // character to make the annotations stand out. 213 Result.push_back('\n'); 214 } 215 Result += DocComment; 216 } 217 return Result; 218 } 219 220 std::string getReturnType(const CodeCompletionString &CCS) { 221 for (const auto &Chunk : CCS) 222 if (Chunk.Kind == CodeCompletionString::CK_ResultType) 223 return Chunk.Text; 224 return ""; 225 } 226 227 } // namespace clangd 228 } // namespace clang 229