//===--- CodeCompletionStrings.cpp -------------------------------*- C++-*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===---------------------------------------------------------------------===// #include "CodeCompletionStrings.h" #include "clang/AST/ASTContext.h" #include "clang/AST/RawCommentList.h" #include "clang/Basic/SourceManager.h" #include namespace clang { namespace clangd { namespace { bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) { return Chunk.Kind == CodeCompletionString::CK_Informative && StringRef(Chunk.Text).endswith("::"); } void processPlainTextChunks(const CodeCompletionString &CCS, std::string *LabelOut, std::string *InsertTextOut) { std::string &Label = *LabelOut; std::string &InsertText = *InsertTextOut; for (const auto &Chunk : CCS) { // Informative qualifier chunks only clutter completion results, skip // them. if (isInformativeQualifierChunk(Chunk)) continue; switch (Chunk.Kind) { case CodeCompletionString::CK_ResultType: case CodeCompletionString::CK_Optional: break; case CodeCompletionString::CK_TypedText: InsertText += Chunk.Text; Label += Chunk.Text; break; default: Label += Chunk.Text; break; } } } 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 processSnippetChunks(const CodeCompletionString &CCS, std::string *LabelOut, std::string *InsertTextOut) { std::string &Label = *LabelOut; std::string &InsertText = *InsertTextOut; 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: case CodeCompletionString::CK_Text: Label += Chunk.Text; InsertText += Chunk.Text; break; case CodeCompletionString::CK_Optional: // FIXME: Maybe add an option to allow presenting the optional chunks? break; case CodeCompletionString::CK_Placeholder: ++ArgCount; InsertText += "${" + std::to_string(ArgCount) + ':'; appendEscapeSnippet(Chunk.Text, &InsertText); InsertText += '}'; Label += Chunk.Text; break; case CodeCompletionString::CK_Informative: // 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. Label += Chunk.Text; // Don't put the informative chunks in the insertText. break; case CodeCompletionString::CK_ResultType: // This is retrieved as detail. 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: InsertText += Chunk.Text; Label += Chunk.Text; break; case CodeCompletionString::CK_VerticalSpace: InsertText += Chunk.Text; // Don't even add a space to the label. break; } } } std::string getFormattedComment(const ASTContext &Ctx, const RawComment &RC, bool CommentsFromHeaders) { auto &SourceMgr = Ctx.getSourceManager(); // Parsing comments from invalid preamble can lead to crashes. So we only // return comments from the main file when doing code completion. For // indexing, we still read all the comments. // FIXME: find a better fix, e.g. store file contents in the preamble or get // doc comments from the index. if (!CommentsFromHeaders && !SourceMgr.isWrittenInMainFile(RC.getLocStart())) return ""; return RC.getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); } } // 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 ""; auto Decl = Result.getDeclaration(); if (!Decl) return ""; const RawComment *RC = getCompletionComment(Ctx, Decl); if (!RC) return ""; return getFormattedComment(Ctx, *RC, CommentsFromHeaders); } std::string getParameterDocComment(const ASTContext &Ctx, const CodeCompleteConsumer::OverloadCandidate &Result, unsigned ArgIndex, bool CommentsFromHeaders) { auto Func = Result.getFunction(); if (!Func) return ""; const RawComment *RC = getParameterComment(Ctx, Result, ArgIndex); if (!RC) return ""; return getFormattedComment(Ctx, *RC, CommentsFromHeaders); } void getLabelAndInsertText(const CodeCompletionString &CCS, std::string *Label, std::string *InsertText, bool EnableSnippets) { return EnableSnippets ? processSnippetChunks(CCS, Label, InsertText) : processPlainTextChunks(CCS, Label, InsertText); } 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 getDetail(const CodeCompletionString &CCS) { for (const auto &Chunk : CCS) { // Informative qualifier chunks only clutter completion results, skip // them. switch (Chunk.Kind) { case CodeCompletionString::CK_ResultType: return Chunk.Text; default: break; } } return ""; } std::string getFilterText(const CodeCompletionString &CCS) { for (const auto &Chunk : CCS) { switch (Chunk.Kind) { case CodeCompletionString::CK_TypedText: // There's always exactly one CK_TypedText chunk. return Chunk.Text; default: break; } } return ""; } } // namespace clangd } // namespace clang