1 //===--- CodeCompletionStrings.cpp -------------------------------*- C++-*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===---------------------------------------------------------------------===// 9 10 #include "CodeCompletionStrings.h" 11 #include "clang/AST/ASTContext.h" 12 #include "clang/AST/DeclObjC.h" 13 #include "clang/AST/RawCommentList.h" 14 #include "clang/Basic/SourceManager.h" 15 #include <utility> 16 17 namespace clang { 18 namespace clangd { 19 20 namespace { 21 22 bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) { 23 return Chunk.Kind == CodeCompletionString::CK_Informative && 24 StringRef(Chunk.Text).endswith("::"); 25 } 26 27 void processPlainTextChunks(const CodeCompletionString &CCS, 28 std::string *LabelOut, std::string *InsertTextOut) { 29 std::string &Label = *LabelOut; 30 std::string &InsertText = *InsertTextOut; 31 for (const auto &Chunk : CCS) { 32 // Informative qualifier chunks only clutter completion results, skip 33 // them. 34 if (isInformativeQualifierChunk(Chunk)) 35 continue; 36 37 switch (Chunk.Kind) { 38 case CodeCompletionString::CK_ResultType: 39 case CodeCompletionString::CK_Optional: 40 break; 41 case CodeCompletionString::CK_TypedText: 42 InsertText += Chunk.Text; 43 Label += Chunk.Text; 44 break; 45 default: 46 Label += Chunk.Text; 47 break; 48 } 49 } 50 } 51 52 void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) { 53 for (const auto Character : Text) { 54 if (Character == '$' || Character == '}' || Character == '\\') 55 Out->push_back('\\'); 56 Out->push_back(Character); 57 } 58 } 59 60 void processSnippetChunks(const CodeCompletionString &CCS, 61 std::string *LabelOut, std::string *InsertTextOut) { 62 std::string &Label = *LabelOut; 63 std::string &InsertText = *InsertTextOut; 64 65 unsigned ArgCount = 0; 66 for (const auto &Chunk : CCS) { 67 // Informative qualifier chunks only clutter completion results, skip 68 // them. 69 if (isInformativeQualifierChunk(Chunk)) 70 continue; 71 72 switch (Chunk.Kind) { 73 case CodeCompletionString::CK_TypedText: 74 case CodeCompletionString::CK_Text: 75 Label += Chunk.Text; 76 InsertText += Chunk.Text; 77 break; 78 case CodeCompletionString::CK_Optional: 79 // FIXME: Maybe add an option to allow presenting the optional chunks? 80 break; 81 case CodeCompletionString::CK_Placeholder: 82 ++ArgCount; 83 InsertText += "${" + std::to_string(ArgCount) + ':'; 84 appendEscapeSnippet(Chunk.Text, &InsertText); 85 InsertText += '}'; 86 Label += Chunk.Text; 87 break; 88 case CodeCompletionString::CK_Informative: 89 // For example, the word "const" for a const method, or the name of 90 // the base class for methods that are part of the base class. 91 Label += Chunk.Text; 92 // Don't put the informative chunks in the insertText. 93 break; 94 case CodeCompletionString::CK_ResultType: 95 // This is retrieved as detail. 96 break; 97 case CodeCompletionString::CK_CurrentParameter: 98 // This should never be present while collecting completion items, 99 // only while collecting overload candidates. 100 llvm_unreachable("Unexpected CK_CurrentParameter while collecting " 101 "CompletionItems"); 102 break; 103 case CodeCompletionString::CK_LeftParen: 104 case CodeCompletionString::CK_RightParen: 105 case CodeCompletionString::CK_LeftBracket: 106 case CodeCompletionString::CK_RightBracket: 107 case CodeCompletionString::CK_LeftBrace: 108 case CodeCompletionString::CK_RightBrace: 109 case CodeCompletionString::CK_LeftAngle: 110 case CodeCompletionString::CK_RightAngle: 111 case CodeCompletionString::CK_Comma: 112 case CodeCompletionString::CK_Colon: 113 case CodeCompletionString::CK_SemiColon: 114 case CodeCompletionString::CK_Equal: 115 case CodeCompletionString::CK_HorizontalSpace: 116 InsertText += Chunk.Text; 117 Label += Chunk.Text; 118 break; 119 case CodeCompletionString::CK_VerticalSpace: 120 InsertText += Chunk.Text; 121 // Don't even add a space to the label. 122 break; 123 } 124 } 125 } 126 127 bool canRequestComment(const ASTContext &Ctx, const NamedDecl &D, 128 bool CommentsFromHeaders) { 129 if (CommentsFromHeaders) 130 return true; 131 auto &SourceMgr = Ctx.getSourceManager(); 132 // Accessing comments for decls from invalid preamble can lead to crashes. 133 // So we only return comments from the main file when doing code completion. 134 // For indexing, we still read all the comments. 135 // FIXME: find a better fix, e.g. store file contents in the preamble or get 136 // doc comments from the index. 137 auto canRequestForDecl = [&](const NamedDecl &D) -> bool { 138 for (auto *Redecl : D.redecls()) { 139 auto Loc = SourceMgr.getSpellingLoc(Redecl->getLocation()); 140 if (!SourceMgr.isWrittenInMainFile(Loc)) 141 return false; 142 } 143 return true; 144 }; 145 // First, check the decl itself. 146 if (!canRequestForDecl(D)) 147 return false; 148 // Completion also returns comments for properties, corresponding to ObjC 149 // methods. 150 const ObjCMethodDecl *M = dyn_cast<ObjCMethodDecl>(&D); 151 const ObjCPropertyDecl *PDecl = M ? M->findPropertyDecl() : nullptr; 152 return !PDecl || canRequestForDecl(*PDecl); 153 } 154 155 bool LooksLikeDocComment(llvm::StringRef CommentText) { 156 // We don't report comments that only contain "special" chars. 157 // This avoids reporting various delimiters, like: 158 // ================= 159 // ----------------- 160 // ***************** 161 return CommentText.find_first_not_of("/*-= \t\r\n") != llvm::StringRef::npos; 162 } 163 164 } // namespace 165 166 std::string getDocComment(const ASTContext &Ctx, 167 const CodeCompletionResult &Result, 168 bool CommentsFromHeaders) { 169 // FIXME: clang's completion also returns documentation for RK_Pattern if they 170 // contain a pattern for ObjC properties. Unfortunately, there is no API to 171 // get this declaration, so we don't show documentation in that case. 172 if (Result.Kind != CodeCompletionResult::RK_Declaration) 173 return ""; 174 auto *Decl = Result.getDeclaration(); 175 if (!Decl || !canRequestComment(Ctx, *Decl, CommentsFromHeaders)) 176 return ""; 177 const RawComment *RC = getCompletionComment(Ctx, Decl); 178 if (!RC) 179 return ""; 180 std::string Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); 181 if (!LooksLikeDocComment(Doc)) 182 return ""; 183 return Doc; 184 } 185 186 std::string 187 getParameterDocComment(const ASTContext &Ctx, 188 const CodeCompleteConsumer::OverloadCandidate &Result, 189 unsigned ArgIndex, bool CommentsFromHeaders) { 190 auto *Func = Result.getFunction(); 191 if (!Func || !canRequestComment(Ctx, *Func, CommentsFromHeaders)) 192 return ""; 193 const RawComment *RC = getParameterComment(Ctx, Result, ArgIndex); 194 if (!RC) 195 return ""; 196 std::string Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); 197 if (!LooksLikeDocComment(Doc)) 198 return ""; 199 return Doc; 200 } 201 202 void getLabelAndInsertText(const CodeCompletionString &CCS, std::string *Label, 203 std::string *InsertText, bool EnableSnippets) { 204 return EnableSnippets ? processSnippetChunks(CCS, Label, InsertText) 205 : processPlainTextChunks(CCS, Label, InsertText); 206 } 207 208 std::string formatDocumentation(const CodeCompletionString &CCS, 209 llvm::StringRef DocComment) { 210 // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this 211 // information in the documentation field. 212 std::string Result; 213 const unsigned AnnotationCount = CCS.getAnnotationCount(); 214 if (AnnotationCount > 0) { 215 Result += "Annotation"; 216 if (AnnotationCount == 1) { 217 Result += ": "; 218 } else /* AnnotationCount > 1 */ { 219 Result += "s: "; 220 } 221 for (unsigned I = 0; I < AnnotationCount; ++I) { 222 Result += CCS.getAnnotation(I); 223 Result.push_back(I == AnnotationCount - 1 ? '\n' : ' '); 224 } 225 } 226 // Add brief documentation (if there is any). 227 if (!DocComment.empty()) { 228 if (!Result.empty()) { 229 // This means we previously added annotations. Add an extra newline 230 // character to make the annotations stand out. 231 Result.push_back('\n'); 232 } 233 Result += DocComment; 234 } 235 return Result; 236 } 237 238 std::string getDetail(const CodeCompletionString &CCS) { 239 for (const auto &Chunk : CCS) { 240 // Informative qualifier chunks only clutter completion results, skip 241 // them. 242 switch (Chunk.Kind) { 243 case CodeCompletionString::CK_ResultType: 244 return Chunk.Text; 245 default: 246 break; 247 } 248 } 249 return ""; 250 } 251 252 std::string getFilterText(const CodeCompletionString &CCS) { 253 for (const auto &Chunk : CCS) { 254 switch (Chunk.Kind) { 255 case CodeCompletionString::CK_TypedText: 256 // There's always exactly one CK_TypedText chunk. 257 return Chunk.Text; 258 default: 259 break; 260 } 261 } 262 return ""; 263 } 264 265 } // namespace clangd 266 } // namespace clang 267