Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -272,6 +272,17 @@ } } +std::string SnippetEscape(const llvm::StringRef Text) { + std::string Result; + Result.reserve(Text.size()); // Assume '$', '}' and '\\' are rare. + for (const auto Character : Text) { + if (Character == '$' || Character == '}' || Character == '\\') + Result.push_back('\\'); + Result.push_back(Character); + } + return Result; +} + class CompletionItemsCollector : public CodeCompleteConsumer { std::vector *Items; std::shared_ptr Allocator; @@ -288,45 +299,161 @@ 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; + + // Always a snippet. This is because CK_Informative chunks should not be + // inserted into the text buffer, but they are part of the label. For + // example, "foo::bar() const" is the label, but "bar()" is the insertText. + Item.insertTextFormat = InsertTextFormat::Snippet; + + // 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" : " "; + } + } + + // Add brief documentation (if there is any). + if (CCS.getBriefComment() != nullptr) { + Item.documentation += CCS.getBriefComment(); + } + + // Fill in the label, detail and insertText fields of the CompletionItem. + ProcessChunks(CCS, Item); + + // 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. + 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. + + // FIXME: 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. + ++ArgCount; + Item.insertText += "${" + std::to_string(ArgCount) + ':' + + SnippetEscape(Chunk.Text) + '}'; + Item.label += Chunk.Text; + 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. + Item.label += Chunk.Text; + // Don't put the informative chunks in the insertText. + break; + case CodeCompletionString::CK_ResultType: + // A piece of text that describes the type of an entity or, + // for functions and methods, the return type. + assert(item.detail.empty() && "Unexpected extraneous CK_ResultType"); + Item.detail = Chunk.Text; + break; + case CodeCompletionString::CK_CurrentParameter: + // A piece of text that describes the parameter that corresponds to the + // code-completion location within a function call, message send, macro + // invocation, etc. + // + // This should never be present while collecting completion items, only + // while collecting overload candidates. + llvm_unreachable( + "Unexpected CK_CurrentParameter while collecting CompletionItems"); + 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 (' '). + Item.insertText += Chunk.Text; + Item.label += Chunk.Text; + break; + case CodeCompletionString::CK_VerticalSpace: + // Vertical whitespace ('\n' or '\r\n', depending on the + // platform). + Item.insertText += Chunk.Text; + Item.label.push_back(' '); // Just a space for the label. + } + } + } + +}; // 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) << ',';