diff --git a/src/clang_complete.cc b/src/clang_complete.cc index 76259761..0dbf9979 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -30,268 +31,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()); @@ -324,70 +63,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; @@ -548,6 +223,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(); } @@ -561,15 +238,9 @@ 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.CodeCompletionAt.FileName = session->file.filename; + FOpts.CodeCompleteOpts = request->CCOpts; + FOpts.CodeCompletionAt.FileName = path; FOpts.CodeCompletionAt.Line = request->position.line + 1; FOpts.CodeCompletionAt.Column = request->position.character + 1; FOpts.SkipFunctionBodies = true; @@ -583,14 +254,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()); } } @@ -718,9 +388,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( @@ -833,14 +500,3 @@ void CompletionManager::FlushAllSessions() { preloaded_sessions_.Clear(); completion_sessions_.Clear(); } - -void CodeCompleteCache::WithLock(std::function action) { - std::lock_guard lock(mutex_); - action(); -} - -bool CodeCompleteCache::IsCacheValid(lsTextDocumentPositionParams position) { - std::lock_guard lock(mutex_); - return cached_path_ == position.textDocument.uri.GetPath() && - cached_completion_position_ == position.position; -} diff --git a/src/clang_complete.hh b/src/clang_complete.hh index 2d170cd0..5c2b2df2 100644 --- a/src/clang_complete.hh +++ b/src/clang_complete.hh @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -67,8 +68,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 { @@ -81,13 +83,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 { @@ -164,14 +172,22 @@ struct CompletionManager { // Cached completion information, so we can give fast completion results when // the user erases a character. vscode will resend the completion request if // that happens. -struct CodeCompleteCache { +template +struct CompleteConsumerCache { // NOTE: Make sure to access these variables under |WithLock|. - std::optional cached_path_; - std::optional cached_completion_position_; - std::vector cached_results_; + std::optional path; + std::optional position; + T result; - std::mutex mutex_; + std::mutex mutex; - void WithLock(std::function action); - bool IsCacheValid(lsTextDocumentPositionParams position); + void WithLock(std::function action) { + std::lock_guard lock(mutex); + action(); + } + bool IsCacheValid(const lsTextDocumentPositionParams ¶ms) { + std::lock_guard lock(mutex); + return path == params.textDocument.uri.GetPath() && + position == params.position; + } }; diff --git a/src/config.h b/src/config.h index 0f675813..1b1fa8a3 100644 --- a/src/config.h +++ b/src/config.h @@ -80,16 +80,7 @@ struct Config { // When this option is enabled, the completion item label is very detailed, // it shows the full signature of the candidate. // The detail just contains the completion item parent context. - // Also, in this mode, functions with default arguments, - // generates one more item per default argument - // so that the right function call can be selected. - // That is, you get something like: - // "int foo()" "Foo" - // "void bar()" "Foo" - // "void bar(int i = 0)" "Foo" - // Be wary, this is quickly quite verbose, - // items can end up truncated by the UIs. - bool detailedLabel = false; + bool detailedLabel = true; // On large projects, completion can take a long time. By default if ccls // receives multiple completion requests while completion is still running @@ -97,6 +88,15 @@ struct Config { // completion requests will be serviced. bool dropOldRequests = true; + // Functions with default arguments, generate one more item per default + // argument. That is, you get something like: + // "int foo()" "Foo" + // "void bar()" "Foo" + // "void bar(int i = 0)" "Foo" + // Be wary, this is quickly quite verbose, + // items can end up truncated by the UIs. + bool duplicateOptional = true; + // If true, filter and sort completion response. ccls filters and sorts // completions to try to be nicer to clients that can't handle big numbers // of completion candidates. This behaviour can be disabled by specifying @@ -226,10 +226,10 @@ struct Config { MAKE_REFLECT_STRUCT(Config::Clang, excludeArgs, extraArgs, resourceDir); MAKE_REFLECT_STRUCT(Config::ClientCapability, snippetSupport); MAKE_REFLECT_STRUCT(Config::CodeLens, localVariables); -MAKE_REFLECT_STRUCT(Config::Completion, caseSensitivity, dropOldRequests, - detailedLabel, filterAndSort, includeBlacklist, - includeMaxPathSize, includeSuffixWhitelist, - includeWhitelist); +MAKE_REFLECT_STRUCT(Config::Completion, caseSensitivity, detailedLabel, + dropOldRequests, duplicateOptional, filterAndSort, + includeBlacklist, includeMaxPathSize, + includeSuffixWhitelist, includeWhitelist); MAKE_REFLECT_STRUCT(Config::Diagnostics, blacklist, frequencyMs, onChange, onOpen, onSave, spellChecking, whitelist) MAKE_REFLECT_STRUCT(Config::Highlight, lsRanges, blacklist, whitelist) diff --git a/src/message_handler.h b/src/message_handler.h index b0a2cd23..b015635f 100644 --- a/src/message_handler.h +++ b/src/message_handler.h @@ -15,11 +15,9 @@ #include struct CompletionManager; -struct CodeCompleteCache; struct Config; class DiagnosticsPublisher; struct VFS; -struct ImportManager; struct IncludeComplete; struct MultiQueueWaiter; struct Project; @@ -104,14 +102,10 @@ struct MessageHandler { Project *project = nullptr; DiagnosticsPublisher *diag_pub = nullptr; VFS *vfs = nullptr; - ImportManager *import_manager = nullptr; SemanticHighlightSymbolCache *semantic_cache = nullptr; 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 *signature_cache = nullptr; virtual MethodType GetMethodType() const = 0; virtual void Run(std::unique_ptr message) = 0; diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc index 7b7792a2..4e9d1a3c 100644 --- a/src/messages/textDocument_completion.cc +++ b/src/messages/textDocument_completion.cc @@ -4,12 +4,16 @@ #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 @@ -249,24 +253,245 @@ 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 BuildItem(std::vector &out, + const CodeCompletionString &CCS) { + assert(!out.empty()); + auto first = out.size() - 1; + + std::string result_type; + + for (const auto &Chunk : CCS) { + CodeCompletionString::ChunkKind Kind = Chunk.Kind; + std::string text; + switch (Kind) { + case CodeCompletionString::CK_TypedText: + text = Chunk.Text; + for (auto i = first; i < out.size(); i++) + if (Kind == CodeCompletionString::CK_TypedText && !out[i].filterText) + out[i].filterText = text; + break; + case CodeCompletionString::CK_Placeholder: + text = Chunk.Text; + for (auto i = first; i < out.size(); i++) + out[i].parameters_.push_back(text); + break; + case CodeCompletionString::CK_ResultType: + result_type = Chunk.Text; + continue; + case CodeCompletionString::CK_CurrentParameter: + // This should never be present while collecting completion items. + llvm_unreachable("unexpected CK_CurrentParameter"); + continue; + case CodeCompletionString::CK_Optional: { + // Duplicate last element, the recursive call will complete it. + if (g_config->completion.duplicateOptional) { + out.push_back(out.back()); + BuildItem(out, *Chunk.Optional); + } + continue; + } + default: + text = Chunk.Text; + break; + } + + for (auto i = first; i < out.size(); ++i) { + out[i].label += text; + if (!g_config->client.snippetSupport && !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 = 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; + } +} + +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; + ls_item.detail = CCS->getParentContextName().str(); + + size_t first_idx = ls_items.size(); + ls_items.push_back(ls_item); + BuildItem(ls_items, *CCS); + + 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); + if (!g_config->completion.detailedLabel) { + ls_items[j].detail = ls_items[j].label; + ls_items[j].label = ls_items[j].filterText.value_or(""); + } + } + } + } + + 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())); - auto ¶ms = request->params; + void Run(In_TextDocumentComplete *request) override { + static CompleteConsumerCache> cache; - auto write_empty_result = [request]() { - Out_TextDocumentComplete out; - out.id = request->id; - pipeline::WriteStdout(kMethodType, out); - }; + auto ¶ms = request->params; + 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; } @@ -311,19 +536,15 @@ struct Handler_TextDocumentCompletion : MessageHandler { } if (did_fail_check) { - write_empty_result(); + pipeline::WriteStdout(kMethodType, out); return; } } - bool is_global_completion = false; - std::string existing_completion; + std::string completion_text; lsPosition end_pos = params.position; - if (file) { - params.position = file->FindStableCompletionSource( - request->params.position, &is_global_completion, &existing_completion, - &end_pos); - } + params.position = file->FindStableCompletionSource( + request->params.position, &completion_text, &end_pos); ParseIncludeLineResult result = ParseIncludeLine(buffer_line); bool has_open_paren = IsOpenParenOrAngle(file->buffer_lines, end_pos); @@ -368,69 +589,44 @@ 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 = + [completion_text, 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, + FilterAndSortCompletionResponse(&out, completion_text, 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; + cache.WithLock([&]() { + cache.path = path; + cache.position = params.position; + cache.result = 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 (cache.IsCacheValid(params)) { + CompletionConsumer Consumer(CCOpts, true); + cache.WithLock([&]() { Consumer.ls_items = cache.result; }); + 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 866b3a16..8c8265d0 100644 --- a/src/messages/textDocument_signatureHelp.cc +++ b/src/messages/textDocument_signatureHelp.cc @@ -6,44 +6,29 @@ #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); @@ -52,30 +37,20 @@ 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; @@ -83,88 +58,164 @@ 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: + bool from_cache; + lsSignatureHelp ls_sighelp; + SignatureHelpConsumer(const clang::CodeCompleteOptions &CCOpts, + bool from_cache) + : CodeCompleteConsumer(CCOpts, false), + Alloc(std::make_shared()), + CCTUInfo(Alloc), from_cache(from_cache) {} + void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates +#if LLVM_VERSION_MAJOR >= 8 + , + 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 >= 8 + 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(); + if (l.label.size() != r.label.size()) + return l.label.size() < r.label.size(); + return l.label < r.label; + }); + } + + 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; + void Run(In_TextDocumentSignatureHelp *request) override { + static CompleteConsumerCache cache; + + auto ¶ms = request->params; + std::string path = params.textDocument.uri.GetPath(); + if (WorkingFile *file = working_files->GetFileByFilename(path)) { + std::string completion_text; + lsPosition end_pos = params.position; + params.position = file->FindStableCompletionSource( + request->params.position, &completion_text, &end_pos); } - 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); + CompletionManager::OnComplete callback = + [id = request->id, + params = request->params](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; + if (!Consumer->from_cache) { + std::string path = params.textDocument.uri.GetPath(); + cache.WithLock([&]() { + cache.path = path; + cache.position = params.position; + cache.result = Consumer->ls_sighelp; }); } + }; - 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*/); - }); + CodeCompleteOptions CCOpts; + CCOpts.IncludeGlobals = false; + CCOpts.IncludeMacros = false; + CCOpts.IncludeBriefComments = false; + if (cache.IsCacheValid(params)) { + SignatureHelpConsumer Consumer(CCOpts, true); + cache.WithLock([&]() { Consumer.ls_sighelp = cache.result; }); + callback(&Consumer); } else { - clang_complete->CodeComplete(request->id, params, std::move(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/pipeline.cc b/src/pipeline.cc index 00b24a14..181f552a 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -474,9 +474,6 @@ 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(); DB db; // Setup shared references. @@ -490,10 +487,6 @@ 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(); } while (true) { diff --git a/src/working_files.cc b/src/working_files.cc index 730bf42e..e3cb245e 100644 --- a/src/working_files.cc +++ b/src/working_files.cc @@ -375,31 +375,17 @@ std::string WorkingFile::FindClosestCallNameInBuffer( return buffer_content.substr(offset, start_offset - offset + 1); } -lsPosition WorkingFile::FindStableCompletionSource( - lsPosition position, bool *is_global_completion, - std::string *existing_completion, lsPosition *replace_end_pos) const { - *is_global_completion = true; - +lsPosition +WorkingFile::FindStableCompletionSource(lsPosition position, + std::string *existing_completion, + lsPosition *replace_end_pos) const { int start_offset = GetOffsetForPosition(position, buffer_content); int offset = start_offset; while (offset > 0) { char c = buffer_content[offset - 1]; - if (!isalnum(c) && c != '_') { - // Global completion is everything except for dot (.), arrow (->), and - // double colon (::) - if (c == '.') - *is_global_completion = false; - if (offset > 2) { - char pc = buffer_content[offset - 2]; - if (pc == ':' && c == ':') - *is_global_completion = false; - else if (pc == '-' && c == '>') - *is_global_completion = false; - } - + if (!isalnum(c) && c != '_') break; - } --offset; } diff --git a/src/working_files.h b/src/working_files.h index 29857ceb..0750de6b 100644 --- a/src/working_files.h +++ b/src/working_files.h @@ -70,7 +70,6 @@ struct WorkingFile { // The out param |existing_completion| is set to any existing completion // content the user has entered. lsPosition FindStableCompletionSource(lsPosition position, - bool *is_global_completion, std::string *existing_completion, lsPosition *replace_end_pos) const;