From 4c499c93e2d48cd94bcde34be0da6f668aa60ec2 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Mon, 10 Sep 2018 22:37:01 -0700 Subject: [PATCH] Revamp completion and signatureHelp --- src/clang_complete.cc | 345 +---------------- src/clang_complete.hh | 14 +- src/message_handler.h | 3 +- src/messages/textDocument_completion.cc | 407 +++++++++++++++++---- src/messages/textDocument_signatureHelp.cc | 258 +++++++------ src/pipeline.cc | 10 +- 6 files changed, 503 insertions(+), 534 deletions(-) diff --git a/src/clang_complete.cc b/src/clang_complete.cc index f6a84820..67986f24 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -23,6 +23,7 @@ limitations under the License. #include #include #include +#include #include #include #include @@ -42,268 +43,6 @@ std::string StripFileType(const std::string &path) { return Ret.str(); } -unsigned GetCompletionPriority(const CodeCompletionString &CCS, - CXCursorKind result_kind, - const std::optional &typedText) { - unsigned priority = CCS.getPriority(); - if (CCS.getAvailability() != CXAvailability_Available || - result_kind == CXCursor_Destructor || - result_kind == CXCursor_ConversionFunction || - (result_kind == CXCursor_CXXMethod && typedText && - StartsWith(*typedText, "operator"))) - priority *= 100; - return priority; -} - -lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) { - switch (cursor_kind) { - case CXCursor_UnexposedDecl: - return lsCompletionItemKind::Text; - - case CXCursor_StructDecl: - case CXCursor_UnionDecl: - return lsCompletionItemKind::Struct; - case CXCursor_ClassDecl: - return lsCompletionItemKind::Class; - case CXCursor_EnumDecl: - return lsCompletionItemKind::Enum; - case CXCursor_FieldDecl: - return lsCompletionItemKind::Field; - case CXCursor_EnumConstantDecl: - return lsCompletionItemKind::EnumMember; - case CXCursor_FunctionDecl: - return lsCompletionItemKind::Function; - case CXCursor_VarDecl: - case CXCursor_ParmDecl: - return lsCompletionItemKind::Variable; - case CXCursor_ObjCInterfaceDecl: - return lsCompletionItemKind::Interface; - - case CXCursor_ObjCInstanceMethodDecl: - case CXCursor_CXXMethod: - case CXCursor_ObjCClassMethodDecl: - return lsCompletionItemKind::Method; - - case CXCursor_FunctionTemplate: - return lsCompletionItemKind::Function; - - case CXCursor_Constructor: - case CXCursor_Destructor: - case CXCursor_ConversionFunction: - return lsCompletionItemKind::Constructor; - - case CXCursor_ObjCIvarDecl: - return lsCompletionItemKind::Variable; - - case CXCursor_ClassTemplate: - case CXCursor_ClassTemplatePartialSpecialization: - case CXCursor_UsingDeclaration: - case CXCursor_TypedefDecl: - case CXCursor_TypeAliasDecl: - case CXCursor_TypeAliasTemplateDecl: - case CXCursor_ObjCCategoryDecl: - case CXCursor_ObjCProtocolDecl: - case CXCursor_ObjCImplementationDecl: - case CXCursor_ObjCCategoryImplDecl: - return lsCompletionItemKind::Class; - - case CXCursor_ObjCPropertyDecl: - return lsCompletionItemKind::Property; - - case CXCursor_MacroInstantiation: - case CXCursor_MacroDefinition: - return lsCompletionItemKind::Interface; - - case CXCursor_Namespace: - case CXCursor_NamespaceAlias: - case CXCursor_NamespaceRef: - return lsCompletionItemKind::Module; - - case CXCursor_MemberRef: - case CXCursor_TypeRef: - case CXCursor_ObjCSuperClassRef: - case CXCursor_ObjCProtocolRef: - case CXCursor_ObjCClassRef: - return lsCompletionItemKind::Reference; - - // return lsCompletionItemKind::Unit; - // return lsCompletionItemKind::Value; - // return lsCompletionItemKind::Keyword; - // return lsCompletionItemKind::Snippet; - // return lsCompletionItemKind::Color; - // return lsCompletionItemKind::File; - - case CXCursor_NotImplemented: - case CXCursor_OverloadCandidate: - return lsCompletionItemKind::Text; - - case CXCursor_TemplateTypeParameter: - case CXCursor_TemplateTemplateParameter: - return lsCompletionItemKind::TypeParameter; - - default: - LOG_S(WARNING) << "Unhandled completion kind " << cursor_kind; - return lsCompletionItemKind::Text; - } -} - -void BuildCompletionItemTexts(std::vector &out, - CodeCompletionString &CCS, - bool include_snippets) { - assert(!out.empty()); - auto out_first = out.size() - 1; - - std::string result_type; - - for (unsigned i = 0, num_chunks = CCS.size(); i < num_chunks; ++i) { - const CodeCompletionString::Chunk &Chunk = CCS[i]; - CodeCompletionString::ChunkKind Kind = Chunk.Kind; - std::string text; - switch (Kind) { - case CodeCompletionString::CK_TypedText: - case CodeCompletionString::CK_Text: - case CodeCompletionString::CK_Placeholder: - case CodeCompletionString::CK_Informative: - if (Chunk.Text) - text = Chunk.Text; - for (auto i = out_first; i < out.size(); i++) { - // first TypedText is used for filtering - if (Kind == CodeCompletionString::CK_TypedText && !out[i].filterText) - out[i].filterText = text; - if (Kind == CodeCompletionString::CK_Placeholder) - out[i].parameters_.push_back(text); - } - break; - case CodeCompletionString::CK_ResultType: - if (Chunk.Text) - result_type = Chunk.Text; - continue; - case CodeCompletionString::CK_CurrentParameter: - // We have our own parsing logic for active parameter. This doesn't seem - // to be very reliable. - continue; - case CodeCompletionString::CK_Optional: { - // duplicate last element, the recursive call will complete it - out.push_back(out.back()); - BuildCompletionItemTexts(out, *Chunk.Optional, include_snippets); - continue; - } - // clang-format off - case CodeCompletionString::CK_LeftParen: text = '('; break; - case CodeCompletionString::CK_RightParen: text = ')'; break; - case CodeCompletionString::CK_LeftBracket: text = '['; break; - case CodeCompletionString::CK_RightBracket: text = ']'; break; - case CodeCompletionString::CK_LeftBrace: text = '{'; break; - case CodeCompletionString::CK_RightBrace: text = '}'; break; - case CodeCompletionString::CK_LeftAngle: text = '<'; break; - case CodeCompletionString::CK_RightAngle: text = '>'; break; - case CodeCompletionString::CK_Comma: text = ", "; break; - case CodeCompletionString::CK_Colon: text = ':'; break; - case CodeCompletionString::CK_SemiColon: text = ';'; break; - case CodeCompletionString::CK_Equal: text = '='; break; - case CodeCompletionString::CK_HorizontalSpace: text = ' '; break; - case CodeCompletionString::CK_VerticalSpace: text = ' '; break; - // clang-format on - } - - if (Kind != CodeCompletionString::CK_Informative) - for (auto i = out_first; i < out.size(); ++i) { - out[i].label += text; - if (!include_snippets && !out[i].parameters_.empty()) - continue; - - if (Kind == CodeCompletionString::CK_Placeholder) { - out[i].insertText += "${" + - std::to_string(out[i].parameters_.size()) + ":" + - text + "}"; - out[i].insertTextFormat = lsInsertTextFormat::Snippet; - } else { - out[i].insertText += text; - } - } - } - - if (result_type.size()) - for (auto i = out_first; i < out.size(); ++i) { - // ' : ' for variables, - // ' -> ' (trailing return type-like) for functions - out[i].label += (out[i].label == out[i].filterText ? " : " : " -> "); - out[i].label += result_type; - } -} - -// |do_insert|: if |!do_insert|, do not append strings to |insert| after -// a placeholder. -void BuildDetailString(const CodeCompletionString &CCS, lsCompletionItem &item, - bool &do_insert, std::vector *parameters, - bool include_snippets, int &angle_stack) { - for (unsigned i = 0, num_chunks = CCS.size(); i < num_chunks; ++i) { - const CodeCompletionString::Chunk &Chunk = CCS[i]; - CodeCompletionString::ChunkKind Kind = Chunk.Kind; - const char *text = nullptr; - switch (Kind) { - case CodeCompletionString::CK_TypedText: - item.label = Chunk.Text; - [[fallthrough]]; - case CodeCompletionString::CK_Text: - item.detail += Chunk.Text; - if (do_insert) - item.insertText += Chunk.Text; - break; - case CodeCompletionString::CK_Placeholder: { - parameters->push_back(Chunk.Text); - item.detail += Chunk.Text; - // Add parameter declarations as snippets if enabled - if (include_snippets) { - item.insertText += - "${" + std::to_string(parameters->size()) + ":" + Chunk.Text + "}"; - item.insertTextFormat = lsInsertTextFormat::Snippet; - } else - do_insert = false; - break; - } - case CodeCompletionString::CK_Informative: - item.detail += Chunk.Text; - break; - case CodeCompletionString::CK_Optional: { - // Do not add text to insert string if we're in angle brackets. - bool should_insert = do_insert && angle_stack == 0; - BuildDetailString(*Chunk.Optional, item, should_insert, parameters, - include_snippets, angle_stack); - break; - } - case CodeCompletionString::CK_ResultType: - item.detail = Chunk.Text + item.detail + " "; - break; - case CodeCompletionString::CK_CurrentParameter: - // We have our own parsing logic for active parameter. This doesn't seem - // to be very reliable. - break; - // clang-format off - case CodeCompletionString::CK_LeftParen: text = "("; break; - case CodeCompletionString::CK_RightParen: text = ")"; break; - case CodeCompletionString::CK_LeftBracket: text = "["; break; - case CodeCompletionString::CK_RightBracket: text = "]"; break; - case CodeCompletionString::CK_LeftBrace: text = "{"; break; - case CodeCompletionString::CK_RightBrace: text = "}"; break; - case CodeCompletionString::CK_LeftAngle: text = "<"; angle_stack++; break; - case CodeCompletionString::CK_RightAngle: text = ">"; angle_stack--; break; - case CodeCompletionString::CK_Comma: text = ", "; break; - case CodeCompletionString::CK_Colon: text = ":"; break; - case CodeCompletionString::CK_SemiColon: text = ";"; break; - case CodeCompletionString::CK_Equal: text = "="; break; - case CodeCompletionString::CK_HorizontalSpace: - case CodeCompletionString::CK_VerticalSpace: text = " "; break; - // clang-format on - } - if (text) { - item.detail += text; - if (do_insert && include_snippets) - item.insertText += text; - } - } -} - bool LocationInRange(SourceLocation L, CharSourceRange R, const SourceManager &M) { assert(R.isCharRange()); @@ -336,70 +75,6 @@ CharSourceRange DiagnosticRange(const clang::Diagnostic &D, const LangOptions &L } -class CaptureCompletionResults : public CodeCompleteConsumer { - std::shared_ptr Alloc; - CodeCompletionTUInfo CCTUInfo; - -public: - std::vector ls_items; - - CaptureCompletionResults(const CodeCompleteOptions &Opts) - : CodeCompleteConsumer(Opts, false), - Alloc(std::make_shared()), - CCTUInfo(Alloc) {} - - void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, - CodeCompletionResult *Results, - unsigned NumResults) override { - ls_items.reserve(NumResults); - for (unsigned i = 0; i != NumResults; i++) { - auto &R = Results[i]; - if (R.Availability == CXAvailability_NotAccessible || - R.Availability == CXAvailability_NotAvailable) - continue; - CodeCompletionString *CCS = R.CreateCodeCompletionString( - S, Context, getAllocator(), getCodeCompletionTUInfo(), - includeBriefComments()); - lsCompletionItem ls_item; - ls_item.kind = GetCompletionKind(R.CursorKind); - if (const char *brief = CCS->getBriefComment()) - ls_item.documentation = brief; - - // label/detail/filterText/insertText/priority - if (g_config->completion.detailedLabel) { - ls_item.detail = CCS->getParentContextName().str(); - - size_t first_idx = ls_items.size(); - ls_items.push_back(ls_item); - BuildCompletionItemTexts(ls_items, *CCS, - g_config->client.snippetSupport); - - for (size_t j = first_idx; j < ls_items.size(); j++) { - if (g_config->client.snippetSupport && - ls_items[j].insertTextFormat == lsInsertTextFormat::Snippet) - ls_items[j].insertText += "$0"; - ls_items[j].priority_ = GetCompletionPriority( - *CCS, Results[i].CursorKind, ls_items[j].filterText); - } - } else { - bool do_insert = true; - int angle_stack = 0; - BuildDetailString(*CCS, ls_item, do_insert, &ls_item.parameters_, - g_config->client.snippetSupport, angle_stack); - if (g_config->client.snippetSupport && - ls_item.insertTextFormat == lsInsertTextFormat::Snippet) - ls_item.insertText += "$0"; - ls_item.priority_ = - GetCompletionPriority(*CCS, Results[i].CursorKind, ls_item.label); - ls_items.push_back(ls_item); - } - } - } - - CodeCompletionAllocator &getAllocator() override { return *Alloc; } - - CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } -}; class StoreDiags : public DiagnosticConsumer { const LangOptions *LangOpts; @@ -560,6 +235,8 @@ void CompletionMain(CompletionManager *completion_manager) { while (g_config->completion.dropOldRequests && !completion_manager->completion_request_.IsEmpty()) { completion_manager->on_dropped_(request->id); + request->Consumer.reset(); + request->on_complete(nullptr); request = completion_manager->completion_request_.Dequeue(); } @@ -573,14 +250,8 @@ void CompletionMain(CompletionManager *completion_manager) { BuildCompilerInvocation(session->file.args, session->FS); if (!CI) continue; - clang::CodeCompleteOptions CCOpts; - CCOpts.IncludeBriefComments = true; -#if LLVM_VERSION_MAJOR >= 7 - CCOpts.IncludeFixIts = true; -#endif - CCOpts.IncludeCodePatterns = true; auto &FOpts = CI->getFrontendOpts(); - FOpts.CodeCompleteOpts = CCOpts; + FOpts.CodeCompleteOpts = request->CCOpts; FOpts.CodeCompletionAt.FileName = session->file.filename; FOpts.CodeCompletionAt.Line = request->position.line + 1; FOpts.CodeCompletionAt.Column = request->position.character + 1; @@ -595,14 +266,13 @@ void CompletionMain(CompletionManager *completion_manager) { if (!Clang) continue; - auto Consumer = new CaptureCompletionResults(CCOpts); - Clang->setCodeCompletionConsumer(Consumer); + Clang->setCodeCompletionConsumer(request->Consumer.release()); if (!Parse(*Clang)) continue; for (auto &Buf : Bufs) Buf.release(); - request->on_complete(Consumer->ls_items, false /*is_cached_result*/); + request->on_complete(&Clang->getCodeCompletionConsumer()); } } @@ -729,9 +399,6 @@ void CompletionManager::CodeComplete( const lsRequestId &id, const lsTextDocumentPositionParams &completion_location, const OnComplete &on_complete) { - completion_request_.PushBack(std::make_unique( - id, completion_location.textDocument, completion_location.position, - on_complete)); } void CompletionManager::DiagnosticsUpdate( diff --git a/src/clang_complete.hh b/src/clang_complete.hh index 94e9fc36..6299487f 100644 --- a/src/clang_complete.hh +++ b/src/clang_complete.hh @@ -25,6 +25,7 @@ limitations under the License. #include #include +#include #include #include @@ -79,8 +80,9 @@ struct CompletionSession struct CompletionManager { using OnDiagnostic = std::function diagnostics)>; - using OnComplete = std::function &results, bool is_cached_result)>; + // If OptConsumer is nullptr, the request has been cancelled. + using OnComplete = + std::function; using OnDropped = std::function; struct PreloadRequest { @@ -93,13 +95,19 @@ struct CompletionManager { struct CompletionRequest { CompletionRequest(const lsRequestId &id, const lsTextDocumentIdentifier &document, - const lsPosition &position, const OnComplete &on_complete) + const lsPosition &position, + std::unique_ptr Consumer, + clang::CodeCompleteOptions CCOpts, + const OnComplete &on_complete) : id(id), document(document), position(position), + Consumer(std::move(Consumer)), CCOpts(CCOpts), on_complete(on_complete) {} lsRequestId id; lsTextDocumentIdentifier document; lsPosition position; + std::unique_ptr Consumer; + clang::CodeCompleteOptions CCOpts; OnComplete on_complete; }; struct DiagnosticRequest { diff --git a/src/message_handler.h b/src/message_handler.h index 3d314222..a19ddf84 100644 --- a/src/message_handler.h +++ b/src/message_handler.h @@ -121,8 +121,7 @@ struct MessageHandler { WorkingFiles *working_files = nullptr; CompletionManager *clang_complete = nullptr; IncludeComplete *include_complete = nullptr; - CodeCompleteCache *global_code_complete_cache = nullptr; - CodeCompleteCache *non_global_code_complete_cache = nullptr; + CodeCompleteCache *completion_cache = nullptr; CodeCompleteCache *signature_cache = nullptr; virtual MethodType GetMethodType() const = 0; diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc index 78383969..075a100d 100644 --- a/src/messages/textDocument_completion.cc +++ b/src/messages/textDocument_completion.cc @@ -16,12 +16,16 @@ limitations under the License. #include "clang_complete.hh" #include "fuzzy_match.h" #include "include_complete.h" +#include "log.hh" #include "message_handler.h" #include "pipeline.hh" #include "working_files.h" using namespace ccls; +#include +#include #include +using namespace clang; using namespace llvm; #include @@ -261,24 +265,320 @@ bool IsOpenParenOrAngle(const std::vector &lines, return false; } -struct Handler_TextDocumentCompletion : MessageHandler { +unsigned GetCompletionPriority(const CodeCompletionString &CCS, + CXCursorKind result_kind, + const std::optional &typedText) { + unsigned priority = CCS.getPriority(); + if (CCS.getAvailability() != CXAvailability_Available || + result_kind == CXCursor_Destructor || + result_kind == CXCursor_ConversionFunction || + (result_kind == CXCursor_CXXMethod && typedText && + StartsWith(*typedText, "operator"))) + priority *= 100; + return priority; +} + +lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) { + switch (cursor_kind) { + case CXCursor_UnexposedDecl: + return lsCompletionItemKind::Text; + + case CXCursor_StructDecl: + case CXCursor_UnionDecl: + return lsCompletionItemKind::Struct; + case CXCursor_ClassDecl: + return lsCompletionItemKind::Class; + case CXCursor_EnumDecl: + return lsCompletionItemKind::Enum; + case CXCursor_FieldDecl: + return lsCompletionItemKind::Field; + case CXCursor_EnumConstantDecl: + return lsCompletionItemKind::EnumMember; + case CXCursor_FunctionDecl: + return lsCompletionItemKind::Function; + case CXCursor_VarDecl: + case CXCursor_ParmDecl: + return lsCompletionItemKind::Variable; + case CXCursor_ObjCInterfaceDecl: + return lsCompletionItemKind::Interface; + + case CXCursor_ObjCInstanceMethodDecl: + case CXCursor_CXXMethod: + case CXCursor_ObjCClassMethodDecl: + return lsCompletionItemKind::Method; + + case CXCursor_FunctionTemplate: + return lsCompletionItemKind::Function; + + case CXCursor_Constructor: + case CXCursor_Destructor: + case CXCursor_ConversionFunction: + return lsCompletionItemKind::Constructor; + + case CXCursor_ObjCIvarDecl: + return lsCompletionItemKind::Variable; + + case CXCursor_ClassTemplate: + case CXCursor_ClassTemplatePartialSpecialization: + case CXCursor_UsingDeclaration: + case CXCursor_TypedefDecl: + case CXCursor_TypeAliasDecl: + case CXCursor_TypeAliasTemplateDecl: + case CXCursor_ObjCCategoryDecl: + case CXCursor_ObjCProtocolDecl: + case CXCursor_ObjCImplementationDecl: + case CXCursor_ObjCCategoryImplDecl: + return lsCompletionItemKind::Class; + + case CXCursor_ObjCPropertyDecl: + return lsCompletionItemKind::Property; + + case CXCursor_MacroInstantiation: + case CXCursor_MacroDefinition: + return lsCompletionItemKind::Interface; + + case CXCursor_Namespace: + case CXCursor_NamespaceAlias: + case CXCursor_NamespaceRef: + return lsCompletionItemKind::Module; + + case CXCursor_MemberRef: + case CXCursor_TypeRef: + case CXCursor_ObjCSuperClassRef: + case CXCursor_ObjCProtocolRef: + case CXCursor_ObjCClassRef: + return lsCompletionItemKind::Reference; + + // return lsCompletionItemKind::Unit; + // return lsCompletionItemKind::Value; + // return lsCompletionItemKind::Keyword; + // return lsCompletionItemKind::Snippet; + // return lsCompletionItemKind::Color; + // return lsCompletionItemKind::File; + + case CXCursor_NotImplemented: + case CXCursor_OverloadCandidate: + return lsCompletionItemKind::Text; + + case CXCursor_TemplateTypeParameter: + case CXCursor_TemplateTemplateParameter: + return lsCompletionItemKind::TypeParameter; + + default: + LOG_S(WARNING) << "Unhandled completion kind " << cursor_kind; + return lsCompletionItemKind::Text; + } +} + +void BuildCompletionItemTexts(std::vector &out, + CodeCompletionString &CCS, + bool include_snippets) { + assert(!out.empty()); + auto out_first = out.size() - 1; + + std::string result_type; + + for (unsigned i = 0, num_chunks = CCS.size(); i < num_chunks; ++i) { + const CodeCompletionString::Chunk &Chunk = CCS[i]; + CodeCompletionString::ChunkKind Kind = Chunk.Kind; + std::string text; + switch (Kind) { + case CodeCompletionString::CK_TypedText: + case CodeCompletionString::CK_Text: + case CodeCompletionString::CK_Placeholder: + case CodeCompletionString::CK_Informative: + if (Chunk.Text) + text = Chunk.Text; + for (auto i = out_first; i < out.size(); i++) { + // first TypedText is used for filtering + if (Kind == CodeCompletionString::CK_TypedText && !out[i].filterText) + out[i].filterText = text; + if (Kind == CodeCompletionString::CK_Placeholder) + out[i].parameters_.push_back(text); + } + break; + case CodeCompletionString::CK_ResultType: + if (Chunk.Text) + result_type = Chunk.Text; + continue; + case CodeCompletionString::CK_CurrentParameter: + // We have our own parsing logic for active parameter. This doesn't seem + // to be very reliable. + continue; + case CodeCompletionString::CK_Optional: { + // duplicate last element, the recursive call will complete it + out.push_back(out.back()); + BuildCompletionItemTexts(out, *Chunk.Optional, include_snippets); + continue; + } + default: + text = Chunk.Text; + break; + } + + if (Kind != CodeCompletionString::CK_Informative) + for (auto i = out_first; i < out.size(); ++i) { + out[i].label += text; + if (!include_snippets && !out[i].parameters_.empty()) + continue; + + if (Kind == CodeCompletionString::CK_Placeholder) { + out[i].insertText += "${" + + std::to_string(out[i].parameters_.size()) + ":" + + text + "}"; + out[i].insertTextFormat = lsInsertTextFormat::Snippet; + } else { + out[i].insertText += text; + } + } + } + + if (result_type.size()) + for (auto i = out_first; i < out.size(); ++i) { + // ' : ' for variables, + // ' -> ' (trailing return type-like) for functions + out[i].label += (out[i].label == out[i].filterText ? " : " : " -> "); + out[i].label += result_type; + } +} + +// |do_insert|: if |!do_insert|, do not append strings to |insert| after +// a placeholder. +void BuildDetailString(const CodeCompletionString &CCS, lsCompletionItem &item, + bool &do_insert, std::vector *parameters, + bool include_snippets, int &angle_stack) { + for (unsigned i = 0, num_chunks = CCS.size(); i < num_chunks; ++i) { + const CodeCompletionString::Chunk &Chunk = CCS[i]; + CodeCompletionString::ChunkKind Kind = Chunk.Kind; + const char *text = nullptr; + switch (Kind) { + case CodeCompletionString::CK_TypedText: + item.label = Chunk.Text; + [[fallthrough]]; + case CodeCompletionString::CK_Text: + item.detail += Chunk.Text; + if (do_insert) + item.insertText += Chunk.Text; + break; + case CodeCompletionString::CK_Placeholder: { + parameters->push_back(Chunk.Text); + item.detail += Chunk.Text; + // Add parameter declarations as snippets if enabled + if (include_snippets) { + item.insertText += + "${" + std::to_string(parameters->size()) + ":" + Chunk.Text + "}"; + item.insertTextFormat = lsInsertTextFormat::Snippet; + } else + do_insert = false; + break; + } + case CodeCompletionString::CK_Informative: + item.detail += Chunk.Text; + break; + case CodeCompletionString::CK_Optional: { + // Do not add text to insert string if we're in angle brackets. + bool should_insert = do_insert && angle_stack == 0; + BuildDetailString(*Chunk.Optional, item, should_insert, parameters, + include_snippets, angle_stack); + break; + } + case CodeCompletionString::CK_ResultType: + item.detail = Chunk.Text + item.detail + " "; + break; + case CodeCompletionString::CK_CurrentParameter: + // We have our own parsing logic for active parameter. This doesn't seem + // to be very reliable. + break; + default: + text = Chunk.Text; + break; + } + if (text) { + item.detail += text; + if (do_insert && include_snippets) + item.insertText += text; + } + } +} + +class CompletionConsumer : public CodeCompleteConsumer { + std::shared_ptr Alloc; + CodeCompletionTUInfo CCTUInfo; + +public: + bool from_cache; + std::vector ls_items; + + CompletionConsumer(const CodeCompleteOptions &Opts, bool from_cache) + : CodeCompleteConsumer(Opts, false), + Alloc(std::make_shared()), + CCTUInfo(Alloc), from_cache(from_cache) {} + + void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, + CodeCompletionResult *Results, + unsigned NumResults) override { + ls_items.reserve(NumResults); + for (unsigned i = 0; i != NumResults; i++) { + auto &R = Results[i]; + if (R.Availability == CXAvailability_NotAccessible || + R.Availability == CXAvailability_NotAvailable) + continue; + CodeCompletionString *CCS = R.CreateCodeCompletionString( + S, Context, getAllocator(), getCodeCompletionTUInfo(), + includeBriefComments()); + lsCompletionItem ls_item; + ls_item.kind = GetCompletionKind(R.CursorKind); + if (const char *brief = CCS->getBriefComment()) + ls_item.documentation = brief; + + // label/detail/filterText/insertText/priority + if (g_config->completion.detailedLabel) { + ls_item.detail = CCS->getParentContextName().str(); + + size_t first_idx = ls_items.size(); + ls_items.push_back(ls_item); + BuildCompletionItemTexts(ls_items, *CCS, + g_config->client.snippetSupport); + + for (size_t j = first_idx; j < ls_items.size(); j++) { + if (g_config->client.snippetSupport && + ls_items[j].insertTextFormat == lsInsertTextFormat::Snippet) + ls_items[j].insertText += "$0"; + ls_items[j].priority_ = GetCompletionPriority( + *CCS, Results[i].CursorKind, ls_items[j].filterText); + } + } else { + bool do_insert = true; + int angle_stack = 0; + BuildDetailString(*CCS, ls_item, do_insert, &ls_item.parameters_, + g_config->client.snippetSupport, angle_stack); + if (g_config->client.snippetSupport && + ls_item.insertTextFormat == lsInsertTextFormat::Snippet) + ls_item.insertText += "$0"; + ls_item.priority_ = + GetCompletionPriority(*CCS, Results[i].CursorKind, ls_item.label); + ls_items.push_back(ls_item); + } + } + } + + CodeCompletionAllocator &getAllocator() override { return *Alloc; } + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } +}; + +struct Handler_TextDocumentCompletion + : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } - void Run(std::unique_ptr message) override { - auto request = std::shared_ptr( - static_cast(message.release())); + void Run(In_TextDocumentComplete *request) override { auto ¶ms = request->params; - - auto write_empty_result = [request]() { - Out_TextDocumentComplete out; - out.id = request->id; - pipeline::WriteStdout(kMethodType, out); - }; + Out_TextDocumentComplete out; + out.id = request->id; std::string path = params.textDocument.uri.GetPath(); WorkingFile *file = working_files->GetFileByFilename(path); if (!file) { - write_empty_result(); + pipeline::WriteStdout(kMethodType, out); return; } @@ -323,7 +623,7 @@ struct Handler_TextDocumentCompletion : MessageHandler { } if (did_fail_check) { - write_empty_result(); + pipeline::WriteStdout(kMethodType, out); return; } } @@ -380,69 +680,46 @@ struct Handler_TextDocumentCompletion : MessageHandler { pipeline::WriteStdout(kMethodType, out); } else { - CompletionManager::OnComplete callback = std::bind( - [this, request, params, is_global_completion, existing_completion, - has_open_paren](const std::vector &results, - bool is_cached_result) { + CompletionManager::OnComplete callback = + [this, existing_completion, has_open_paren, id = request->id, + params = request->params](CodeCompleteConsumer *OptConsumer) { + if (!OptConsumer) + return; + auto *Consumer = static_cast(OptConsumer); Out_TextDocumentComplete out; - out.id = request->id; - out.result.items = results; + out.id = id; + out.result.items = Consumer->ls_items; - // Emit completion results. FilterAndSortCompletionResponse(&out, existing_completion, has_open_paren); pipeline::WriteStdout(kMethodType, out); - - // Cache completion results. - if (!is_cached_result) { + if (!Consumer->from_cache) { std::string path = params.textDocument.uri.GetPath(); - if (is_global_completion) { - global_code_complete_cache->WithLock([&]() { - global_code_complete_cache->cached_path_ = path; - global_code_complete_cache->cached_results_ = results; - }); - } else { - non_global_code_complete_cache->WithLock([&]() { - non_global_code_complete_cache->cached_path_ = path; - non_global_code_complete_cache->cached_completion_position_ = - params.position; - non_global_code_complete_cache->cached_results_ = results; - }); - } - } - }, - std::placeholders::_1, std::placeholders::_2); - - bool is_cache_match = false; - global_code_complete_cache->WithLock([&]() { - is_cache_match = is_global_completion && - global_code_complete_cache->cached_path_ == path && - !global_code_complete_cache->cached_results_.empty(); - }); - if (is_cache_match) { - CompletionManager::OnComplete freshen_global = - [this](std::vector results, - bool is_cached_result) { - assert(!is_cached_result); - - // note: path is updated in the normal completion handler. - global_code_complete_cache->WithLock([&]() { - global_code_complete_cache->cached_results_ = results; + completion_cache->WithLock([&]() { + completion_cache->cached_path_ = path; + completion_cache->cached_completion_position_ = params.position; + completion_cache->cached_results_ = Consumer->ls_items; }); - }; + } + }; - global_code_complete_cache->WithLock([&]() { - callback(global_code_complete_cache->cached_results_, - true /*is_cached_result*/); - }); - clang_complete->CodeComplete(request->id, params, freshen_global); - } else if (non_global_code_complete_cache->IsCacheValid(params)) { - non_global_code_complete_cache->WithLock([&]() { - callback(non_global_code_complete_cache->cached_results_, - true /*is_cached_result*/); + clang::CodeCompleteOptions CCOpts; + CCOpts.IncludeBriefComments = true; +#if LLVM_VERSION_MAJOR >= 7 + CCOpts.IncludeFixIts = true; +#endif + if (completion_cache->IsCacheValid(params)) { + CompletionConsumer Consumer(CCOpts, true); + completion_cache->WithLock([&]() { + Consumer.ls_items = completion_cache->cached_results_; }); + callback(&Consumer); } else { - clang_complete->CodeComplete(request->id, params, callback); + clang_complete->completion_request_.PushBack( + std::make_unique( + request->id, params.textDocument, params.position, + std::make_unique(CCOpts, false), CCOpts, + callback)); } } } diff --git a/src/messages/textDocument_signatureHelp.cc b/src/messages/textDocument_signatureHelp.cc index 1a763e21..ab08b52c 100644 --- a/src/messages/textDocument_signatureHelp.cc +++ b/src/messages/textDocument_signatureHelp.cc @@ -18,44 +18,29 @@ limitations under the License. #include "pipeline.hh" using namespace ccls; +#include +using namespace clang; + #include namespace { MethodType kMethodType = "textDocument/signatureHelp"; -struct In_TextDocumentSignatureHelp : public RequestInMessage { - MethodType GetMethodType() const override { return kMethodType; } - lsTextDocumentPositionParams params; -}; -MAKE_REFLECT_STRUCT(In_TextDocumentSignatureHelp, id, params); -REGISTER_IN_MESSAGE(In_TextDocumentSignatureHelp); - // Represents a parameter of a callable-signature. A parameter can // have a label and a doc-comment. struct lsParameterInformation { - // The label of this parameter. Will be shown in - // the UI. std::string label; - - // The human-readable doc-comment of this parameter. Will be shown - // in the UI but can be omitted. - std::optional documentation; + // Not available in clang + // std::optional documentation; }; -MAKE_REFLECT_STRUCT(lsParameterInformation, label, documentation); +MAKE_REFLECT_STRUCT(lsParameterInformation, label); // Represents the signature of something callable. A signature // can have a label, like a function-name, a doc-comment, and // a set of parameters. struct lsSignatureInformation { - // The label of this signature. Will be shown in - // the UI. std::string label; - - // The human-readable doc-comment of this signature. Will be shown - // in the UI but can be omitted. std::optional documentation; - - // The parameters of this signature. std::vector parameters; }; MAKE_REFLECT_STRUCT(lsSignatureInformation, label, documentation, parameters); @@ -64,30 +49,21 @@ MAKE_REFLECT_STRUCT(lsSignatureInformation, label, documentation, parameters); // callable. There can be multiple signature but only one // active and only one active parameter. struct lsSignatureHelp { - // One or more signatures. std::vector signatures; - - // The active signature. If omitted or the value lies outside the - // range of `signatures` the value defaults to zero or is ignored if - // `signatures.length === 0`. Whenever possible implementors should - // make an active decision about the active signature and shouldn't - // rely on a default value. - // In future version of the protocol this property might become - // mandantory to better express this. - std::optional activeSignature; - - // The active parameter of the active signature. If omitted or the value - // lies outside the range of `signatures[activeSignature].parameters` - // defaults to 0 if the active signature has parameters. If - // the active signature has no parameters it is ignored. - // In future version of the protocol this property might become - // mandantory to better express the active parameter if the - // active signature does have any. - std::optional activeParameter; + int activeSignature = 0; + int activeParameter; }; MAKE_REFLECT_STRUCT(lsSignatureHelp, signatures, activeSignature, activeParameter); + +struct In_TextDocumentSignatureHelp : public RequestInMessage { + MethodType GetMethodType() const override { return kMethodType; } + lsTextDocumentPositionParams params; +}; +MAKE_REFLECT_STRUCT(In_TextDocumentSignatureHelp, id, params); +REGISTER_IN_MESSAGE(In_TextDocumentSignatureHelp); + struct Out_TextDocumentSignatureHelp : public lsOutMessage { lsRequestId id; @@ -95,89 +71,135 @@ struct Out_TextDocumentSignatureHelp }; MAKE_REFLECT_STRUCT(Out_TextDocumentSignatureHelp, jsonrpc, id, result); -struct Handler_TextDocumentSignatureHelp : MessageHandler { +std::string BuildOptional(const CodeCompletionString &CCS, + std::vector &ls_params) { + std::string ret; + for (const auto &Chunk : CCS) { + switch (Chunk.Kind) { + case CodeCompletionString::CK_Optional: + ret += BuildOptional(*Chunk.Optional, ls_params); + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + // Intentional fallthrough here. + 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. + ret += Chunk.Text; + ls_params.push_back(lsParameterInformation{Chunk.Text}); + break; + } + case CodeCompletionString::CK_VerticalSpace: + break; + default: + ret += Chunk.Text; + break; + } + } + return ret; +} + +class SignatureHelpConsumer : public CodeCompleteConsumer { + std::shared_ptr Alloc; + CodeCompletionTUInfo CCTUInfo; +public: + lsSignatureHelp ls_sighelp; + SignatureHelpConsumer(const clang::CodeCompleteOptions &CCOpts) + : CodeCompleteConsumer(CCOpts, false), + Alloc(std::make_shared()), + CCTUInfo(Alloc) {} + void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates +#if LLVM_VERSION_MAJOR >= 7 + , + SourceLocation OpenParLoc +#endif + ) override { + ls_sighelp.activeParameter = (int)CurrentArg; + for (unsigned i = 0; i < NumCandidates; i++) { + OverloadCandidate Cand = Candidates[i]; + // We want to avoid showing instantiated signatures, because they may be + // long in some cases (e.g. when 'T' is substituted with 'std::string', we + // would get 'std::basic_string'). + if (auto *Func = Cand.getFunction()) + if (auto *Pattern = Func->getTemplateInstantiationPattern()) + Cand = OverloadCandidate(Pattern); + + const auto *CCS = + Cand.CreateSignatureString(CurrentArg, S, *Alloc, CCTUInfo, true); + + const char *ret_type = nullptr; + lsSignatureInformation &ls_sig = ls_sighelp.signatures.emplace_back(); +#if LLVM_VERSION_MAJOR >= 7 + const RawComment *RC = getCompletionComment(S.getASTContext(), Cand.getFunction()); + ls_sig.documentation = RC ? RC->getBriefText(S.getASTContext()) : ""; +#endif + for (const auto &Chunk : *CCS) + switch (Chunk.Kind) { + case CodeCompletionString::CK_ResultType: + ret_type = Chunk.Text; + break; + case CodeCompletionString::CK_Placeholder: + case CodeCompletionString::CK_CurrentParameter: { + ls_sig.label += Chunk.Text; + ls_sig.parameters.push_back(lsParameterInformation{Chunk.Text}); + break; + } + case CodeCompletionString::CK_Optional: + ls_sig.label += BuildOptional(*Chunk.Optional, ls_sig.parameters); + break; + case CodeCompletionString::CK_VerticalSpace: + break; + default: + ls_sig.label += Chunk.Text; + break; + } + if (ret_type) { + ls_sig.label += " -> "; + ls_sig.label += ret_type; + } + } + std::sort( + ls_sighelp.signatures.begin(), ls_sighelp.signatures.end(), + [](const lsSignatureInformation &l, const lsSignatureInformation &r) { + if (l.parameters.size() != r.parameters.size()) + return l.parameters.size() < r.parameters.size(); + return l.label.size() < r.label.size(); + }); + } + + CodeCompletionAllocator &getAllocator() override { return *Alloc; } + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } +}; + +struct Handler_TextDocumentSignatureHelp + : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } - void Run(std::unique_ptr message) override { - auto request = static_cast(message.get()); - lsTextDocumentPositionParams ¶ms = request->params; - WorkingFile *file = - working_files->GetFileByFilename(params.textDocument.uri.GetPath()); - std::string search; - int active_param = 0; - if (file) { - lsPosition completion_position; - search = file->FindClosestCallNameInBuffer(params.position, &active_param, - &completion_position); - params.position = completion_position; - } - if (search.empty()) - return; - - CompletionManager::OnComplete callback = std::bind( - [this](InMessage *message, std::string search, int active_param, - const std::vector &results, - bool is_cached_result) { - auto msg = static_cast(message); - + void Run(In_TextDocumentSignatureHelp *request) override { + const auto ¶ms = request->params; + CompletionManager::OnComplete callback = + [id = request->id](CodeCompleteConsumer *OptConsumer) { + if (!OptConsumer) + return; + auto *Consumer = static_cast(OptConsumer); Out_TextDocumentSignatureHelp out; - out.id = msg->id; - - for (auto &result : results) { - if (result.label != search) - continue; - - lsSignatureInformation signature; - signature.label = result.detail; - for (auto ¶meter : result.parameters_) { - lsParameterInformation ls_param; - ls_param.label = parameter; - signature.parameters.push_back(ls_param); - } - out.result.signatures.push_back(signature); - } - - // Prefer the signature with least parameter count but still larger - // than active_param. - out.result.activeSignature = 0; - if (out.result.signatures.size()) { - size_t num_parameters = SIZE_MAX; - for (size_t i = 0; i < out.result.signatures.size(); ++i) { - size_t t = out.result.signatures[i].parameters.size(); - if (active_param < t && t < num_parameters) { - out.result.activeSignature = int(i); - num_parameters = t; - } - } - } - - // Set signature to what we parsed from the working file. - out.result.activeParameter = active_param; - + out.id = id; + out.result = Consumer->ls_sighelp; pipeline::WriteStdout(kMethodType, out); + }; - if (!is_cached_result) { - signature_cache->WithLock([&]() { - signature_cache->cached_path_ = - msg->params.textDocument.uri.GetPath(); - signature_cache->cached_completion_position_ = - msg->params.position; - signature_cache->cached_results_ = results; - }); - } - - delete message; - }, - message.release(), search, active_param, std::placeholders::_1, - std::placeholders::_2); - - if (signature_cache->IsCacheValid(params)) { - signature_cache->WithLock([&]() { - callback(signature_cache->cached_results_, true /*is_cached_result*/); - }); - } else { - clang_complete->CodeComplete(request->id, params, std::move(callback)); - } + CodeCompleteOptions CCOpts; + CCOpts.IncludeGlobals = false; + CCOpts.IncludeMacros = false; + CCOpts.IncludeBriefComments = false; + clang_complete->completion_request_.PushBack( + std::make_unique( + request->id, params.textDocument, params.position, + std::make_unique(CCOpts), CCOpts, callback)); } }; REGISTER_MESSAGE_HANDLER(Handler_TextDocumentSignatureHelp); diff --git a/src/pipeline.cc b/src/pipeline.cc index 8fea4f96..46e867ee 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -486,9 +486,7 @@ void MainLoop() { }); IncludeComplete include_complete(&project); - auto global_code_complete_cache = std::make_unique(); - auto non_global_code_complete_cache = std::make_unique(); - auto signature_cache = std::make_unique(); + CodeCompleteCache completion_cache, signature_cache; DB db; // Setup shared references. @@ -502,10 +500,8 @@ void MainLoop() { handler->working_files = &working_files; handler->clang_complete = &clang_complete; handler->include_complete = &include_complete; - handler->global_code_complete_cache = global_code_complete_cache.get(); - handler->non_global_code_complete_cache = - non_global_code_complete_cache.get(); - handler->signature_cache = signature_cache.get(); + handler->completion_cache = &completion_cache; + handler->signature_cache = &signature_cache; } while (true) {