diff --git a/src/clang_complete.cc b/src/clang_complete.cc index f2408ec0..2d0fb251 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -5,8 +5,10 @@ #include "log.hh" #include "platform.h" +#include #include #include +using namespace clang; using namespace llvm; #include @@ -30,23 +32,16 @@ std::string StripFileType(const std::string& path) { return Ret.str(); } -unsigned GetCompletionPriority(const CXCompletionString& str, +unsigned GetCompletionPriority(const CodeCompletionString& CCS, CXCursorKind result_kind, const std::optional& typedText) { - unsigned priority = clang_getCompletionPriority(str); - - // XXX: What happens if priority overflows? - if (result_kind == CXCursor_Destructor) { - priority *= 100; - } - if (result_kind == CXCursor_ConversionFunction || + 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"))) { + StartsWith(*typedText, "operator"))) priority *= 100; - } - if (clang_getCompletionAvailability(str) != CXAvailability_Available) { - priority *= 100; - } return priority; } @@ -142,85 +137,76 @@ lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) { } } -void BuildCompletionItemTexts(std::vector& out, - CXCompletionString completion_string, +void BuildCompletionItemTexts(std::vector &out, + CodeCompletionString &CCS, bool include_snippets) { assert(!out.empty()); auto out_first = out.size() - 1; std::string result_type; - int num_chunks = clang_getNumCompletionChunks(completion_string); - for (int i = 0; i < num_chunks; ++i) { - CXCompletionChunkKind kind = - clang_getCompletionChunkKind(completion_string, i); - + 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) { - // clang-format off - case CXCompletionChunk_LeftParen: text = '('; break; - case CXCompletionChunk_RightParen: text = ')'; break; - case CXCompletionChunk_LeftBracket: text = '['; break; - case CXCompletionChunk_RightBracket: text = ']'; break; - case CXCompletionChunk_LeftBrace: text = '{'; break; - case CXCompletionChunk_RightBrace: text = '}'; break; - case CXCompletionChunk_LeftAngle: text = '<'; break; - case CXCompletionChunk_RightAngle: text = '>'; break; - case CXCompletionChunk_Comma: text = ", "; break; - case CXCompletionChunk_Colon: text = ':'; break; - case CXCompletionChunk_SemiColon: text = ';'; break; - case CXCompletionChunk_Equal: text = '='; break; - case CXCompletionChunk_HorizontalSpace: text = ' '; break; - case CXCompletionChunk_VerticalSpace: text = ' '; break; - // clang-format on - - case CXCompletionChunk_ResultType: - result_type = - ToString(clang_getCompletionChunkText(completion_string, i)); - continue; - - case CXCompletionChunk_TypedText: - case CXCompletionChunk_Placeholder: - case CXCompletionChunk_Text: - case CXCompletionChunk_Informative: - text = ToString(clang_getCompletionChunkText(completion_string, i)); - - for (auto i = out_first; i < out.size(); ++i) { - // first typed text is used for filtering - if (kind == CXCompletionChunk_TypedText && !out[i].filterText) + 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 == CXCompletionChunk_Placeholder) + if (Kind == CodeCompletionString::CK_Placeholder) out[i].parameters_.push_back(text); } break; - - case CXCompletionChunk_CurrentParameter: + 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 CXCompletionChunk_Optional: { - CXCompletionString nested = - clang_getCompletionChunkCompletionString(completion_string, i); + case CodeCompletionString::CK_Optional: { // duplicate last element, the recursive call will complete it out.push_back(out.back()); - BuildCompletionItemTexts(out, nested, include_snippets); + 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 } for (auto i = out_first; i < out.size(); ++i) out[i].label += text; - if (kind == CXCompletionChunk_Informative) + if (Kind == CodeCompletionString::CK_Informative) continue; for (auto i = out_first; i < out.size(); ++i) { if (!include_snippets && !out[i].parameters_.empty()) continue; - if (kind == CXCompletionChunk_Placeholder) { + if (Kind == CodeCompletionString::CK_Placeholder) { out[i].insertText += "${" + std::to_string(out[i].parameters_.size()) + ":" + text + "}"; out[i].insertTextFormat = lsInsertTextFormat::Snippet; @@ -243,106 +229,71 @@ void BuildCompletionItemTexts(std::vector& out, // |do_insert|: if |!do_insert|, do not append strings to |insert| after // a placeholder. -void BuildDetailString(CXCompletionString completion_string, - std::string& label, - std::string& detail, - std::string& insert, - bool& do_insert, - lsInsertTextFormat& format, - std::vector* parameters, - bool include_snippets, - int& angle_stack) { - int num_chunks = clang_getNumCompletionChunks(completion_string); - auto append = [&](const char* text) { - detail += text; - if (do_insert && include_snippets) - insert += text; - }; - for (int i = 0; i < num_chunks; ++i) { - CXCompletionChunkKind kind = - clang_getCompletionChunkKind(completion_string, i); - - switch (kind) { - case CXCompletionChunk_Optional: { - CXCompletionString nested = - clang_getCompletionChunkCompletionString(completion_string, i); - // Do not add text to insert string if we're in angle brackets. - bool should_insert = do_insert && angle_stack == 0; - BuildDetailString(nested, label, detail, insert, - should_insert, format, parameters, - include_snippets, angle_stack); - break; - } - - case CXCompletionChunk_Placeholder: { - std::string text = - ToString(clang_getCompletionChunkText(completion_string, i)); - parameters->push_back(text); - detail += text; - // Add parameter declarations as snippets if enabled - if (include_snippets) { - insert += - "${" + std::to_string(parameters->size()) + ":" + text + "}"; - format = lsInsertTextFormat::Snippet; - } else - do_insert = false; - break; - } - - case CXCompletionChunk_CurrentParameter: - // We have our own parsing logic for active parameter. This doesn't seem - // to be very reliable. - break; - - case CXCompletionChunk_TypedText: { - std::string text = - ToString(clang_getCompletionChunkText(completion_string, i)); - label = text; - detail += text; - if (do_insert) - insert += text; - break; - } - - case CXCompletionChunk_Text: { - std::string text = - ToString(clang_getCompletionChunkText(completion_string, i)); - detail += text; - if (do_insert) - insert += text; - break; - } - - case CXCompletionChunk_Informative: { - detail += ToString(clang_getCompletionChunkText(completion_string, i)); - break; - } - - case CXCompletionChunk_ResultType: { - CXString text = clang_getCompletionChunkText(completion_string, i); - std::string new_detail = ToString(text) + detail + " "; - detail = new_detail; - break; - } - - // clang-format off - case CXCompletionChunk_LeftParen: append("("); break; - case CXCompletionChunk_RightParen: append(")"); break; - case CXCompletionChunk_LeftBracket: append("["); break; - case CXCompletionChunk_RightBracket: append("]"); break; - case CXCompletionChunk_LeftBrace: append("{"); break; - case CXCompletionChunk_RightBrace: append("}"); break; - case CXCompletionChunk_LeftAngle: append("<"); angle_stack++; break; - case CXCompletionChunk_RightAngle: append(">"); angle_stack--; break; - case CXCompletionChunk_Comma: append(", "); break; - case CXCompletionChunk_Colon: append(":"); break; - case CXCompletionChunk_SemiColon: append(";"); break; - case CXCompletionChunk_Equal: append("="); break; - // clang-format on - case CXCompletionChunk_HorizontalSpace: - case CXCompletionChunk_VerticalSpace: - append(" "); - break; +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; } } } @@ -483,35 +434,24 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) { for (unsigned i = 0; i < cx_results->NumResults; ++i) { CXCompletionResult& result = cx_results->Results[i]; - - // TODO: Try to figure out how we can hide base method calls without - // also hiding method implementation assistance, ie, - // - // void Foo::* { - // } - // - - if (clang_getCompletionAvailability(result.CompletionString) == - CXAvailability_NotAvailable) + auto CCS = (CodeCompletionString *)result.CompletionString; + if (CCS->getAvailability() == CXAvailability_NotAvailable) continue; - // TODO: fill in more data - lsCompletionItem ls_completion_item; - - ls_completion_item.kind = GetCompletionKind(result.CursorKind); - ls_completion_item.documentation = - ToString(clang_getCompletionBriefComment(result.CompletionString)); + lsCompletionItem ls_item; + ls_item.kind = GetCompletionKind(result.CursorKind); + if (const char* brief = CCS->getBriefComment()) + ls_item.documentation = brief; // label/detail/filterText/insertText/priority if (g_config->completion.detailedLabel) { - ls_completion_item.detail = ToString( - clang_getCompletionParent(result.CompletionString, nullptr)); + ls_item.detail = CCS->getParentContextName().str(); auto first_idx = ls_result.size(); - ls_result.push_back(ls_completion_item); + ls_result.push_back(ls_item); // label/filterText/insertText - BuildCompletionItemTexts(ls_result, result.CompletionString, + BuildCompletionItemTexts(ls_result, *CCS, g_config->client.snippetSupport); for (auto i = first_idx; i < ls_result.size(); ++i) { @@ -520,28 +460,21 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) { ls_result[i].insertText += "$0"; } - ls_result[i].priority_ = - GetCompletionPriority(result.CompletionString, result.CursorKind, - ls_result[i].filterText); + ls_result[i].priority_ = GetCompletionPriority( + *CCS, result.CursorKind, ls_result[i].filterText); } } else { bool do_insert = true; int angle_stack = 0; - BuildDetailString(result.CompletionString, ls_completion_item.label, - ls_completion_item.detail, - ls_completion_item.insertText, do_insert, - ls_completion_item.insertTextFormat, - &ls_completion_item.parameters_, + BuildDetailString(*CCS, ls_item, do_insert, + &ls_item.parameters_, g_config->client.snippetSupport, angle_stack); if (g_config->client.snippetSupport && - ls_completion_item.insertTextFormat == - lsInsertTextFormat::Snippet) { - ls_completion_item.insertText += "$0"; - } - ls_completion_item.priority_ = - GetCompletionPriority(result.CompletionString, result.CursorKind, - ls_completion_item.label); - ls_result.push_back(ls_completion_item); + ls_item.insertTextFormat == lsInsertTextFormat::Snippet) + ls_item.insertText += "$0"; + ls_item.priority_ = + GetCompletionPriority(*CCS, result.CursorKind, ls_item.label); + ls_result.push_back(ls_item); } } diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc index 1479f803..20093539 100644 --- a/src/messages/textDocument_completion.cc +++ b/src/messages/textDocument_completion.cc @@ -19,10 +19,11 @@ enum class lsCompletionTriggerKind { // Completion was triggered by typing an identifier (24x7 code // complete), manual invocation (e.g Ctrl+Space) or via API. Invoked = 1, - // Completion was triggered by a trigger character specified by // the `triggerCharacters` properties of the `CompletionRegistrationOptions`. - TriggerCharacter = 2 + TriggerCharacter = 2, + // Completion was re-triggered as the current completion list is incomplete. + TriggerForIncompleteCompletions = 3, }; MAKE_REFLECT_TYPE_PROXY(lsCompletionTriggerKind); @@ -30,7 +31,7 @@ MAKE_REFLECT_TYPE_PROXY(lsCompletionTriggerKind); // request is triggered. struct lsCompletionContext { // How the completion was triggered. - lsCompletionTriggerKind triggerKind; + lsCompletionTriggerKind triggerKind = lsCompletionTriggerKind::Invoked; // The trigger character (a single character) that has trigger code complete. // Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` @@ -42,7 +43,7 @@ struct lsCompletionParams : lsTextDocumentPositionParams { // The completion context. This is only available it the client specifies to // send this using // `ClientCapabilities.textDocument.completion.contextSupport === true` - std::optional context; + lsCompletionContext context; }; MAKE_REFLECT_STRUCT(lsCompletionParams, textDocument, position, context); @@ -254,6 +255,7 @@ struct Handler_TextDocumentCompletion : MessageHandler { void Run(std::unique_ptr message) override { auto request = std::shared_ptr( static_cast(message.release())); + auto& params = request->params; auto write_empty_result = [request]() { Out_TextDocumentComplete out; @@ -261,7 +263,7 @@ struct Handler_TextDocumentCompletion : MessageHandler { pipeline::WriteStdout(kMethodType, out); }; - std::string path = request->params.textDocument.uri.GetPath(); + std::string path = params.textDocument.uri.GetPath(); WorkingFile* file = working_files->GetFileByFilename(path); if (!file) { write_empty_result(); @@ -271,21 +273,19 @@ struct Handler_TextDocumentCompletion : MessageHandler { // It shouldn't be possible, but sometimes vscode will send queries out // of order, ie, we get completion request before buffer content update. std::string buffer_line; - if (request->params.position.line >= 0 && - request->params.position.line < file->buffer_lines.size()) { - buffer_line = file->buffer_lines[request->params.position.line]; - } + if (params.position.line >= 0 && + params.position.line < file->buffer_lines.size()) + buffer_line = file->buffer_lines[params.position.line]; // Check for - and : before completing -> or ::, since vscode does not // support multi-character trigger characters. - if (request->params.context && - request->params.context->triggerKind == + if (params.context.triggerKind == lsCompletionTriggerKind::TriggerCharacter && - request->params.context->triggerCharacter) { + params.context.triggerCharacter) { bool did_fail_check = false; - std::string character = *request->params.context->triggerCharacter; - int preceding_index = request->params.position.character - 2; + std::string character = *params.context.triggerCharacter; + int preceding_index = params.position.character - 2; // If the character is '"', '<' or '/', make sure that the line starts // with '#'. @@ -318,11 +318,11 @@ struct Handler_TextDocumentCompletion : MessageHandler { bool is_global_completion = false; std::string existing_completion; - lsPosition end_pos = request->params.position; + lsPosition end_pos = params.position; if (file) { - request->params.position = file->FindStableCompletionSource( - request->params.position, &is_global_completion, - &existing_completion, &end_pos); + params.position = file->FindStableCompletionSource( + request->params.position, &is_global_completion, &existing_completion, + &end_pos); } ParseIncludeLineResult result = ParseIncludeLine(buffer_line); @@ -359,16 +359,16 @@ struct Handler_TextDocumentCompletion : MessageHandler { } for (lsCompletionItem& item : out.result.items) { - item.textEdit->range.start.line = request->params.position.line; + item.textEdit->range.start.line = params.position.line; item.textEdit->range.start.character = 0; - item.textEdit->range.end.line = request->params.position.line; + item.textEdit->range.end.line = params.position.line; item.textEdit->range.end.character = (int)buffer_line.size(); } pipeline::WriteStdout(kMethodType, out); } else { ClangCompleteManager::OnComplete callback = std::bind( - [this, request, is_global_completion, existing_completion, + [this, request, params, is_global_completion, existing_completion, has_open_paren](const std::vector& results, bool is_cached_result) { Out_TextDocumentComplete out; @@ -381,7 +381,7 @@ struct Handler_TextDocumentCompletion : MessageHandler { // Cache completion results. if (!is_cached_result) { - std::string path = request->params.textDocument.uri.GetPath(); + std::string path = params.textDocument.uri.GetPath(); if (is_global_completion) { global_code_complete_cache->WithLock([&]() { global_code_complete_cache->cached_path_ = path; @@ -391,7 +391,7 @@ struct Handler_TextDocumentCompletion : MessageHandler { non_global_code_complete_cache->WithLock([&]() { non_global_code_complete_cache->cached_path_ = path; non_global_code_complete_cache->cached_completion_position_ = - request->params.position; + params.position; non_global_code_complete_cache->cached_results_ = results; }); } @@ -421,16 +421,14 @@ struct Handler_TextDocumentCompletion : MessageHandler { callback(global_code_complete_cache->cached_results_, true /*is_cached_result*/); }); - clang_complete->CodeComplete(request->id, request->params, - freshen_global); - } else if (non_global_code_complete_cache->IsCacheValid( - request->params)) { + 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*/); }); } else { - clang_complete->CodeComplete(request->id, request->params, callback); + clang_complete->CodeComplete(request->id, params, callback); } } }