Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -288,45 +288,189 @@ void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, CodeCompletionResult *Results, unsigned NumResults) override { - for (unsigned I = 0; I != NumResults; ++I) { - CodeCompletionResult &Result = Results[I]; - CodeCompletionString *CCS = Result.CreateCodeCompletionString( + assert(Items && "We need a non-null items container here"); + Items->reserve(NumResults); + for (unsigned I = 0; I < NumResults; ++I) { + auto &Result = Results[I]; + const auto *CCS = Result.CreateCodeCompletionString( S, Context, *Allocator, CCTUInfo, CodeCompleteOpts.IncludeBriefComments); - if (CCS) { - CompletionItem Item; - for (CodeCompletionString::Chunk C : *CCS) { - switch (C.Kind) { - case CodeCompletionString::CK_ResultType: - Item.detail = C.Text; - break; - case CodeCompletionString::CK_Optional: - break; - default: - Item.label += C.Text; - break; - } - } - assert(CCS->getTypedText()); - Item.kind = getKind(Result.CursorKind); - // Priority is a 16-bit integer, hence at most 5 digits. - assert(CCS->getPriority() < 99999 && "Expecting code completion result " - "priority to have at most " - "5-digits"); - llvm::raw_string_ostream(Item.sortText) - << llvm::format("%05d%s", CCS->getPriority(), CCS->getTypedText()); - Item.insertText = Item.filterText = CCS->getTypedText(); - if (CCS->getBriefComment()) - Item.documentation = CCS->getBriefComment(); - Items->push_back(std::move(Item)); - } + assert(CCS && "Expected the CodeCompletionString to be non-null"); + Items->push_back(ProcessCodeCompleteResult(Result, *CCS)); } } GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; } CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } -}; + +private: + CompletionItem + ProcessCodeCompleteResult(const CodeCompletionResult &Result, + const CodeCompletionString &CCS) const { + // The object that we'll return. + CompletionItem Item; + + // By default InsertTextFormat::PlainText. When we encounter a + // CK_Placeholder or CK_CurrentParameter, this will be modified to + // InsertTextFormat::PlainText. + Item.insertTextFormat = InsertTextFormat::PlainText; + + // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this + // information in the documentation field. + if (CCS.getAnnotationCount() > 0) { + Item.documentation += "Annotation"; + if (CCS.getAnnotationCount() == 1) { + Item.documentation += ": "; + } else /* CCS.getAnnotationCount() > 1 */ { + Item.documentation += "s: "; + } + for (unsigned j = 0; j < CCS.getAnnotationCount(); ++j) { + Item.documentation += CCS.getAnnotation(j); + Item.documentation += j == CCS.getAnnotationCount() - 1 ? "\n\n" : " "; + } + } + + // Fill in the label, detail, documentation and insertText fields of the + // CompletionItem. The informative chunks are put into the documentation + // field. + ProcessChunks(CCS, Item); + + // Add brief documentation (if there is any). + if (CCS.getBriefComment() != nullptr) { + Item.documentation += CCS.getBriefComment(); + } + + // Fill in the kind field of the CompletionItem. + Item.kind = getKind(Result.CursorKind); + + // Fill in the sortText of the CompletionItem. + assert(CCS.getPriority() < 99999 && "Expecting code completion result " + "priority to have at most 5-digits"); + llvm::raw_string_ostream(Item.sortText) + << llvm::format("%05d%s", CCS.getPriority(), CCS.getTypedText()); + + return Item; + } + + void ProcessChunks(const CodeCompletionString &CCS, + CompletionItem &Item) const { + unsigned ArgCount = 0; + for (const auto &Chunk : CCS) { + switch (Chunk.Kind) { + case CodeCompletionString::CK_TypedText: + // The piece of text that the user is expected to type to match + // the code-completion string, typically a keyword or the name of + // a declarator or macro. + Item.label += Chunk.Text; + Item.insertText += Chunk.Text; + break; + case CodeCompletionString::CK_Text: + // A piece of text that should be placed in the buffer, + // e.g., parentheses or a comma in a function call. + Item.label += Chunk.Text; + Item.insertText += Chunk.Text; + break; + case CodeCompletionString::CK_Optional: + // A code completion string that is entirely optional. + // For example, an optional code completion string that + // describes the default arguments in a function call. + + // TODO: Maybe add an option to allow presenting the optional chunks? + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + AddSnippetParameter(Chunk.Text, ArgCount, Item); + break; + case CodeCompletionString::CK_Informative: + // A piece of text that describes something about the result + // but should not be inserted into the buffer. + // 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. + + // Terrible hack. + // TODO: See SemaCodeComplete.cpp:2598 + if (Chunk.Text[0] == ' ') + Item.documentation += "[" + std::string(Chunk.Text + 1) + "] "; + else + Item.documentation += "[" + std::string(Chunk.Text) + "] "; + break; + case CodeCompletionString::CK_ResultType: + // A piece of text that describes the type of an entity or, + // for functions and methods, the return type. + Item.detail += Chunk.Text; + break; + case CodeCompletionString::CK_CurrentParameter: + // TODO(rwols): I'm still not sure what the difference is between + // CK_CurrentParameter and CK_Placeholder. From skimming over the code + // in SemaCodeComplete.cpp they seem to be used interchangeably. + // + // A piece of text that describes the parameter that corresponds to the + // code-completion location within a function call, message send, macro + // invocation, etc. + AddSnippetParameter(Chunk.Text, ArgCount, Item); + break; + case CodeCompletionString::CK_LeftParen: + // A left parenthesis ('('). + case CodeCompletionString::CK_RightParen: + // A right parenthesis (')'). + case CodeCompletionString::CK_LeftBracket: + // A left bracket ('['). + case CodeCompletionString::CK_RightBracket: + // A right bracket (']'). + case CodeCompletionString::CK_LeftBrace: + // A left brace ('{'). + case CodeCompletionString::CK_RightBrace: + // A right brace ('}'). + case CodeCompletionString::CK_LeftAngle: + // A left angle bracket ('<'). + case CodeCompletionString::CK_RightAngle: + // A right angle bracket ('>'). + case CodeCompletionString::CK_Comma: + // A comma separator (','). + case CodeCompletionString::CK_Colon: + // A colon (':'). + case CodeCompletionString::CK_SemiColon: + // A semicolon (';'). + case CodeCompletionString::CK_Equal: + // An '=' sign. + case CodeCompletionString::CK_HorizontalSpace: + // Horizontal whitespace (' '). + case CodeCompletionString::CK_VerticalSpace: + // Vertical whitespace ('\n' or '\r\n', depending on the + // platform). + Item.insertText += Chunk.Text; + Item.label += Chunk.Text; + break; + default: + llvm_unreachable("Unexpected CodeCompletionString chunk type!"); + } + } + } + + void AddSnippetParameter(const char *Text, unsigned &ArgCount, + CompletionItem &Item) const { + ++ArgCount; + Item.insertText += + "${" + std::to_string(ArgCount) + ":" + SnippetEscape(Text) + "}"; + Item.label += Text; + Item.insertTextFormat = InsertTextFormat::Snippet; + } + + std::string SnippetEscape(const char *Text) const { + std::string Result; + const auto Length = std::strlen(Text); + Result.reserve(Length); // Assume a '$', '}' and '\\' are rare. + for (const auto *S = Text; S != Text + Length; ++S) { + if (*S == '$' || *S == '}' || *S == '\\') + Result += '\\'; + Result += *S; + } + return Result; + } + +}; // CompletionItemsCollector } // namespace std::vector Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -724,8 +724,8 @@ if (!CI.insertText.empty()) Os << R"("insertText":")" << llvm::yaml::escape(CI.insertText) << R"(",)"; if (CI.insertTextFormat != InsertTextFormat::Missing) { - Os << R"("insertTextFormat":")" << static_cast(CI.insertTextFormat) - << R"(",)"; + Os << R"("insertTextFormat":)" << static_cast(CI.insertTextFormat) + << R"(,)"; } if (CI.textEdit) Os << R"("textEdit":)" << TextEdit::unparse(*CI.textEdit) << ',';