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