diff --git a/src/clang_complete.cc b/src/clang_complete.cc index 05a89ed8..b3c271d6 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -270,7 +270,8 @@ void BuildDetailString(CXCompletionString completion_string, bool& do_insert, lsInsertTextFormat& format, std::vector* parameters, - bool include_snippets) { + bool include_snippets, + int& angle_stack) { int num_chunks = clang_getNumCompletionChunks(completion_string); auto append = [&](const char* text) { detail += text; @@ -285,8 +286,11 @@ void BuildDetailString(CXCompletionString completion_string, case CXCompletionChunk_Optional: { CXCompletionString nested = clang_getCompletionChunkCompletionString(completion_string, i); - BuildDetailString(nested, label, detail, insert, do_insert, format, - parameters, include_snippets); + // 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; } @@ -348,8 +352,8 @@ void BuildDetailString(CXCompletionString completion_string, case CXCompletionChunk_RightBracket: append("]"); break; case CXCompletionChunk_LeftBrace: append("{"); break; case CXCompletionChunk_RightBrace: append("}"); break; - case CXCompletionChunk_LeftAngle: append("<"); break; - case CXCompletionChunk_RightAngle: 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; @@ -366,7 +370,8 @@ void BuildDetailString(CXCompletionString completion_string, void TryEnsureDocumentParsed(ClangCompleteManager* manager, std::shared_ptr session, std::unique_ptr* tu, - ClangIndex* index) { + ClangIndex* index, + bool emit_diag) { // Nothing to do. We already have a translation unit. if (*tu) return; @@ -414,11 +419,10 @@ void TryEnsureDocumentParsed(ClangCompleteManager* manager, } } -void CompletionParseMain(ClangCompleteManager* completion_manager) { +void CompletionPreloadMain(ClangCompleteManager* completion_manager) { while (true) { // Fetching the completion request blocks until we have a request. - ClangCompleteManager::ParseRequest request = - completion_manager->parse_requests_.Dequeue(); + auto request = completion_manager->preload_requests_.Dequeue(); // If we don't get a session then that means we don't care about the file // anymore - abandon the request. @@ -429,23 +433,23 @@ void CompletionParseMain(ClangCompleteManager* completion_manager) { if (!session) continue; + // Note: we only preload completion. We emit diagnostics for the + // completion preload though. + CompletionSession::Tu* tu = &session->completion; + // If we've parsed it more recently than the request time, don't bother // reparsing. - if (session->tu_last_parsed_at && - *session->tu_last_parsed_at > request.request_time) { + if (tu->last_parsed_at && *tu->last_parsed_at > request.request_time) continue; - } std::unique_ptr parsing; - TryEnsureDocumentParsed(completion_manager, session, &parsing, - &session->index); + TryEnsureDocumentParsed(completion_manager, session, &parsing, &tu->index, + true); // Activate new translation unit. - // tu_last_parsed_at is only read by this thread, so it doesn't need to be - // under the mutex. - session->tu_last_parsed_at = std::chrono::high_resolution_clock::now(); - std::lock_guard lock(session->tu_lock); - session->tu = std::move(parsing); + std::lock_guard lock(tu->lock); + tu->last_parsed_at = std::chrono::high_resolution_clock::now(); + tu->tu = std::move(parsing); } } @@ -468,15 +472,15 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) { completion_manager->TryGetSession(path, true /*mark_as_completion*/, true /*create_if_needed*/); - std::lock_guard lock(session->tu_lock); + std::lock_guard lock(session->completion.lock); Timer timer; - TryEnsureDocumentParsed(completion_manager, session, &session->tu, - &session->index); + TryEnsureDocumentParsed(completion_manager, session, &session->completion.tu, + &session->completion.index, false); timer.ResetAndPrint("[complete] TryEnsureDocumentParsed"); // It is possible we failed to create the document despite // |TryEnsureDocumentParsed|. - if (!session->tu) + if (!session->completion.tu) continue; timer.Reset(); @@ -485,185 +489,161 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) { std::vector unsaved = snapshot.AsUnsavedFiles(); timer.ResetAndPrint("[complete] Creating WorkingFile snapshot"); - // Emit code completion data. - if (request->position) { - // Language server is 0-based, clang is 1-based. - unsigned line = request->position->line + 1; - unsigned column = request->position->character + 1; + unsigned const kCompleteOptions = + CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeBriefComments; + CXCodeCompleteResults* cx_results = clang_codeCompleteAt( + session->completion.tu->cx_tu, session->file.filename.c_str(), + request->position.line + 1, request->position.character + 1, + unsaved.data(), (unsigned)unsaved.size(), kCompleteOptions); + timer.ResetAndPrint("[complete] clangCodeCompleteAt"); + if (!cx_results) { + request->on_complete({}, false /*is_cached_result*/); + continue; + } - timer.Reset(); - unsigned const kCompleteOptions = - CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeBriefComments; - CXCodeCompleteResults* cx_results = clang_codeCompleteAt( - session->tu->cx_tu, session->file.filename.c_str(), line, column, - unsaved.data(), (unsigned)unsaved.size(), kCompleteOptions); - timer.ResetAndPrint("[complete] clangCodeCompleteAt"); - if (!cx_results) { - if (request->on_complete) - request->on_complete({}, false /*is_cached_result*/); + std::vector ls_result; + // this is a guess but can be larger in case of std::optional + // parameters, as they may be expanded into multiple items + ls_result.reserve(cx_results->NumResults); + + timer.Reset(); + 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) continue; - } - { - if (request->on_complete) { - std::vector ls_result; - // this is a guess but can be larger in case of std::optional parameters, - // as they may be expanded into multiple items - ls_result.reserve(cx_results->NumResults); + // TODO: fill in more data + lsCompletionItem ls_completion_item; - timer.Reset(); - for (unsigned i = 0; i < cx_results->NumResults; ++i) { - CXCompletionResult& result = cx_results->Results[i]; + ls_completion_item.kind = GetCompletionKind(result.CursorKind); + ls_completion_item.documentation = + ToString(clang_getCompletionBriefComment(result.CompletionString)); - // TODO: Try to figure out how we can hide base method calls without - // also hiding method implementation assistance, ie, - // - // void Foo::* { - // } - // + // label/detail/filterText/insertText/priority + if (g_config->completion.detailedLabel) { + ls_completion_item.detail = ToString( + clang_getCompletionParent(result.CompletionString, nullptr)); - if (clang_getCompletionAvailability(result.CompletionString) == - CXAvailability_NotAvailable) - continue; + auto first_idx = ls_result.size(); + ls_result.push_back(ls_completion_item); - // TODO: fill in more data - lsCompletionItem ls_completion_item; + // label/filterText/insertText + BuildCompletionItemTexts(ls_result, result.CompletionString, + g_config->client.snippetSupport); - ls_completion_item.kind = GetCompletionKind(result.CursorKind); - ls_completion_item.documentation = ToString( - clang_getCompletionBriefComment(result.CompletionString)); - - // label/detail/filterText/insertText/priority - if (g_config->completion.detailedLabel) { - ls_completion_item.detail = ToString( - clang_getCompletionParent(result.CompletionString, nullptr)); - - auto first_idx = ls_result.size(); - ls_result.push_back(ls_completion_item); - - // label/filterText/insertText - BuildCompletionItemTexts( - ls_result, result.CompletionString, - g_config->client.snippetSupport); - - for (auto i = first_idx; i < ls_result.size(); ++i) { - if (g_config->client.snippetSupport && - ls_result[i].insertTextFormat == - lsInsertTextFormat::Snippet) { - ls_result[i].insertText += "$0"; - } - - ls_result[i].priority_ = GetCompletionPriority( - result.CompletionString, result.CursorKind, - ls_result[i].filterText); - } - } else { - bool do_insert = true; - 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_, - g_config->client.snippetSupport); - 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); - } + for (auto i = first_idx; i < ls_result.size(); ++i) { + if (g_config->client.snippetSupport && + ls_result[i].insertTextFormat == lsInsertTextFormat::Snippet) { + ls_result[i].insertText += "$0"; } - timer.ResetAndPrint("[complete] Building " + - std::to_string(ls_result.size()) + - " completion results"); - - request->on_complete(ls_result, false /*is_cached_result*/); + ls_result[i].priority_ = + GetCompletionPriority(result.CompletionString, 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_, + 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); } - - // Make sure |ls_results| is destroyed before clearing |cx_results|. - clang_disposeCodeCompleteResults(cx_results); } + timer.ResetAndPrint("[complete] Building " + + std::to_string(ls_result.size()) + + " completion results"); + + request->on_complete(ls_result, false /*is_cached_result*/); + + // Make sure |ls_results| is destroyed before clearing |cx_results|. + clang_disposeCodeCompleteResults(cx_results); + } +} + +void DiagnosticQueryMain(ClangCompleteManager* completion_manager) { + while (true) { + // Fetching the completion request blocks until we have a request. + ClangCompleteManager::DiagnosticRequest request = + completion_manager->diagnostic_request_.Dequeue(); + std::string path = request.document.uri.GetPath(); + + std::shared_ptr session = + completion_manager->TryGetSession(path, true /*mark_as_completion*/, + true /*create_if_needed*/); + + // At this point, we must have a translation unit. Block until we have one. + std::lock_guard lock(session->diagnostics.lock); + Timer timer; + TryEnsureDocumentParsed( + completion_manager, session, &session->diagnostics.tu, + &session->diagnostics.index, false /*emit_diagnostics*/); + timer.ResetAndPrint("[diagnostics] TryEnsureDocumentParsed"); + + // It is possible we failed to create the document despite + // |TryEnsureDocumentParsed|. + if (!session->diagnostics.tu) + continue; + + timer.Reset(); + WorkingFiles::Snapshot snapshot = + completion_manager->working_files_->AsSnapshot({StripFileType(path)}); + std::vector unsaved = snapshot.AsUnsavedFiles(); + timer.ResetAndPrint("[diagnostics] Creating WorkingFile snapshot"); + // Emit diagnostics. - if (request->emit_diagnostics) { - // TODO: before emitting diagnostics check if we have another completion - // request and think about servicing that first, because it may be much - // faster than reparsing the document. - // TODO: have a separate thread for diagnostics? - - timer.Reset(); - session->tu = - ClangTranslationUnit::Reparse(std::move(session->tu), unsaved); - timer.ResetAndPrint("[complete] clang_reparseTranslationUnit"); - if (!session->tu) { - LOG_S(ERROR) << "Reparsing translation unit for diagnostics failed for " - << path; - continue; - } - - size_t num_diagnostics = clang_getNumDiagnostics(session->tu->cx_tu); - std::vector ls_diagnostics; - ls_diagnostics.reserve(num_diagnostics); - for (unsigned i = 0; i < num_diagnostics; ++i) { - CXDiagnostic cx_diag = clang_getDiagnostic(session->tu->cx_tu, i); - std::optional diagnostic = - BuildAndDisposeDiagnostic(cx_diag, path); - // Filter messages like "too many errors emitted, stopping now - // [-ferror-limit=]" which has line = 0 and got subtracted by 1 after - // conversion to lsDiagnostic - if (diagnostic && diagnostic->range.start.line >= 0) - ls_diagnostics.push_back(*diagnostic); - } - completion_manager->on_diagnostic_(session->file.filename, - ls_diagnostics); - - /* - timer.Reset(); - completion_manager->on_index_(session->tu.get(), unsaved, - session->file.filename, session->file.args); - timer.ResetAndPrint("[complete] Reindex file"); - */ + timer.Reset(); + session->diagnostics.tu = ClangTranslationUnit::Reparse( + std::move(session->diagnostics.tu), unsaved); + timer.ResetAndPrint("[diagnostics] clang_reparseTranslationUnit"); + if (!session->diagnostics.tu) { + LOG_S(ERROR) << "Reparsing translation unit for diagnostics failed for " + << path; + continue; } - continue; + size_t num_diagnostics = + clang_getNumDiagnostics(session->diagnostics.tu->cx_tu); + std::vector ls_diagnostics; + ls_diagnostics.reserve(num_diagnostics); + for (unsigned i = 0; i < num_diagnostics; ++i) { + CXDiagnostic cx_diag = + clang_getDiagnostic(session->diagnostics.tu->cx_tu, i); + std::optional diagnostic = + BuildAndDisposeDiagnostic(cx_diag, path); + // Filter messages like "too many errors emitted, stopping now + // [-ferror-limit=]" which has line = 0 and got subtracted by 1 after + // conversion to lsDiagnostic + if (diagnostic && diagnostic->range.start.line >= 0) + ls_diagnostics.push_back(*diagnostic); + } + completion_manager->on_diagnostic_(session->file.filename, ls_diagnostics); } } } // namespace -CompletionSession::CompletionSession(const Project::Entry& file, - WorkingFiles* working_files) - : file(file), - working_files(working_files), - index(0 /*excludeDeclarationsFromPCH*/, 0 /*displayDiagnostics*/) {} - -CompletionSession::~CompletionSession() {} - -ClangCompleteManager::ParseRequest::ParseRequest(const std::string& path) - : request_time(std::chrono::high_resolution_clock::now()), path(path) {} - -ClangCompleteManager::CompletionRequest::CompletionRequest( - const lsRequestId& id, - const lsTextDocumentIdentifier& document, - bool emit_diagnostics) - : id(id), document(document), emit_diagnostics(emit_diagnostics) {} -ClangCompleteManager::CompletionRequest::CompletionRequest( - const lsRequestId& id, - const lsTextDocumentIdentifier& document, - const lsPosition& position, - const OnComplete& on_complete, - bool emit_diagnostics) - : id(id), - document(document), - position(position), - on_complete(on_complete), - emit_diagnostics(emit_diagnostics) {} - ClangCompleteManager::ClangCompleteManager(Project* project, WorkingFiles* working_files, OnDiagnostic on_diagnostic, @@ -677,13 +657,16 @@ ClangCompleteManager::ClangCompleteManager(Project* project, preloaded_sessions_(kMaxPreloadedSessions), completion_sessions_(kMaxCompletionSessions) { new std::thread([&]() { - SetThreadName("completequery"); + SetThreadName("comp-query"); CompletionQueryMain(this); }); - new std::thread([&]() { - SetThreadName("completeparse"); - CompletionParseMain(this); + SetThreadName("comp-preload"); + CompletionPreloadMain(this); + }); + new std::thread([&]() { + SetThreadName("diag-query"); + DiagnosticQueryMain(this); }); } @@ -693,14 +676,20 @@ void ClangCompleteManager::CodeComplete( const OnComplete& on_complete) { completion_request_.PushBack(std::make_unique( id, completion_location.textDocument, completion_location.position, - on_complete, false)); + on_complete)); } void ClangCompleteManager::DiagnosticsUpdate( const lsRequestId& id, const lsTextDocumentIdentifier& document) { - completion_request_.PushBack( - std::make_unique(id, document, true)); + bool has = false; + diagnostic_request_.Iterate([&](const DiagnosticRequest& request) { + if (request.document.uri == document.uri) + has = true; + }); + if (!has) + diagnostic_request_.PushBack(DiagnosticRequest{document}, + true /*priority*/); } void ClangCompleteManager::NotifyView(const std::string& filename) { @@ -712,7 +701,7 @@ void ClangCompleteManager::NotifyView(const std::string& filename) { // Only reparse the file if we create a new CompletionSession. if (EnsureCompletionOrCreatePreloadSession(filename)) - parse_requests_.PushBack(ParseRequest(filename), true); + preload_requests_.PushBack(PreloadRequest(filename), true); } void ClangCompleteManager::NotifyEdit(const std::string& filename) { @@ -731,7 +720,7 @@ void ClangCompleteManager::NotifySave(const std::string& filename) { // EnsureCompletionOrCreatePreloadSession(filename); - parse_requests_.PushBack(ParseRequest(filename), true); + preload_requests_.PushBack(PreloadRequest(filename), true); } void ClangCompleteManager::NotifyClose(const std::string& filename) { diff --git a/src/clang_complete.h b/src/clang_complete.h index 015609a8..904bc704 100644 --- a/src/clang_complete.h +++ b/src/clang_complete.h @@ -18,22 +18,26 @@ struct CompletionSession : public std::enable_shared_from_this { + // Translation unit for clang. + struct Tu { + ClangIndex index{0, 0}; + + // When |tu| was last parsed. + std::optional> + last_parsed_at; + // Acquired when |tu| is being used. + std::mutex lock; + std::unique_ptr tu; + }; + Project::Entry file; WorkingFiles* working_files; - ClangIndex index; - // When |tu| was last parsed. - std::optional> - tu_last_parsed_at; + Tu completion; + Tu diagnostics; - // Acquired when |tu| is being used. - std::mutex tu_lock; - - // The active translation unit. - std::unique_ptr tu; - - CompletionSession(const Project::Entry& file, WorkingFiles* working_files); - ~CompletionSession(); + CompletionSession(const Project::Entry& file, WorkingFiles* wfiles) + : file(file), working_files(wfiles) {} }; struct ClangCompleteManager { @@ -49,27 +53,30 @@ struct ClangCompleteManager { bool is_cached_result)>; using OnDropped = std::function; - struct ParseRequest { - ParseRequest(const std::string& path); + struct PreloadRequest { + PreloadRequest(const std::string& path) + : request_time(std::chrono::high_resolution_clock::now()), path(path) {} std::chrono::time_point request_time; std::string path; }; struct CompletionRequest { - CompletionRequest(const lsRequestId& id, - const lsTextDocumentIdentifier& document, - bool emit_diagnostics); CompletionRequest(const lsRequestId& id, const lsTextDocumentIdentifier& document, const lsPosition& position, - const OnComplete& on_complete, - bool emit_diagnostics); + const OnComplete& on_complete) + : id(id), + document(document), + position(position), + on_complete(on_complete) {} lsRequestId id; lsTextDocumentIdentifier document; - std::optional position; - OnComplete on_complete; // May be null/empty. - bool emit_diagnostics = false; + lsPosition position; + OnComplete on_complete; + }; + struct DiagnosticRequest { + lsTextDocumentIdentifier document; }; ClangCompleteManager(Project* project, @@ -138,9 +145,10 @@ struct ClangCompleteManager { // Request a code completion at the given location. ThreadedQueue> completion_request_; + ThreadedQueue diagnostic_request_; // Parse requests. The path may already be parsed, in which case it should be // reparsed. - ThreadedQueue parse_requests_; + ThreadedQueue preload_requests_; }; // Cached completion information, so we can give fast completion results when diff --git a/src/messages/text_document_completion.cc b/src/messages/text_document_completion.cc index b0f62960..3f70405b 100644 --- a/src/messages/text_document_completion.cc +++ b/src/messages/text_document_completion.cc @@ -123,7 +123,7 @@ static const std::vector preprocessorKeywords = { "define", "undef", "include", "if", "ifdef", "ifndef", "else", "elif", "endif", "line", "error", "pragma"}; -std::vector preprocessorKeywordCompletionItems( +std::vector PreprocessorKeywordCompletionItems( const std::smatch& match) { std::vector items; for (auto& keyword : preprocessorKeywords) { @@ -160,7 +160,8 @@ char* tofixedbase64(T input, char* out) { // when given 1000+ completion items. void FilterAndSortCompletionResponse( Out_TextDocumentComplete* complete_response, - const std::string& complete_text) { + const std::string& complete_text, + bool has_open_paren) { if (!g_config->completion.filterAndSort) return; @@ -189,6 +190,10 @@ void FilterAndSortCompletionResponse( complete_response->result.isIncomplete = true; } + if (has_open_paren) + for (auto& item: items) + item.insertText = item.label; + // Set sortText. Note that this happens after resizing - we could do it // before, but then we should also sort by priority. char buf[16]; @@ -236,6 +241,26 @@ void FilterAndSortCompletionResponse( finalize(); } +// Returns true if position is an points to a '(' character in |lines|. Skips +// whitespace. +bool IsOpenParenOrAngle(const std::vector& lines, + const lsPosition& position) { + auto [c, l] = position; + while (l < lines.size()) { + const auto& line = lines[l]; + if (c >= line.size()) + return false; + if (line[c] == '(' || line[c] == '<') + return true; + if (!isspace(line[c])) break; + if (++c >= line.size()) { + c = 0; + l++; + } + } + return false; +} + struct Handler_TextDocumentCompletion : MessageHandler { MethodType GetMethodType() const override { return kMethodType; } @@ -306,13 +331,15 @@ struct Handler_TextDocumentCompletion : MessageHandler { bool is_global_completion = false; std::string existing_completion; + lsPosition end_pos = request->params.position; if (file) { request->params.position = file->FindStableCompletionSource( request->params.position, &is_global_completion, - &existing_completion); + &existing_completion, &end_pos); } ParseIncludeLineResult result = ParseIncludeLine(buffer_line); + bool has_open_paren = IsOpenParenOrAngle(file->buffer_lines, end_pos); if (result.ok) { Out_TextDocumentComplete out; @@ -325,8 +352,8 @@ struct Handler_TextDocumentCompletion : MessageHandler { [&result](std::string_view k) { return k == result.keyword; })) { - out.result.items = preprocessorKeywordCompletionItems(result.match); - FilterAndSortCompletionResponse(&out, result.keyword); + out.result.items = PreprocessorKeywordCompletionItems(result.match); + FilterAndSortCompletionResponse(&out, result.keyword, has_open_paren); } } else if (result.keyword.compare("include") == 0) { { @@ -340,7 +367,7 @@ struct Handler_TextDocumentCompletion : MessageHandler { if (quote.empty() || quote == (item.use_angle_brackets_ ? "<" : "\"")) out.result.items.push_back(item); } - FilterAndSortCompletionResponse(&out, result.pattern); + FilterAndSortCompletionResponse(&out, result.pattern, has_open_paren); DecorateIncludePaths(result.match, &out.result.items); } @@ -354,15 +381,15 @@ struct Handler_TextDocumentCompletion : MessageHandler { QueueManager::WriteStdout(kMethodType, out); } else { ClangCompleteManager::OnComplete callback = std::bind( - [this, is_global_completion, existing_completion, request]( - const std::vector& results, - bool is_cached_result) { + [this, request, is_global_completion, existing_completion, + has_open_paren](const std::vector& results, + bool is_cached_result) { Out_TextDocumentComplete out; out.id = request->id; out.result.items = results; // Emit completion results. - FilterAndSortCompletionResponse(&out, existing_completion); + FilterAndSortCompletionResponse(&out, existing_completion, has_open_paren); QueueManager::WriteStdout(kMethodType, out); // Cache completion results. diff --git a/src/threaded_queue.h b/src/threaded_queue.h index f72950e0..c14fed3b 100644 --- a/src/threaded_queue.h +++ b/src/threaded_queue.h @@ -205,6 +205,15 @@ struct ThreadedQueue : public BaseThreadQueue { std::optional TryPopFrontHigh() { return TryPopFrontHelper(2); } + template + void Iterate(Fn fn) { + std::lock_guard lock(mutex_); + for (auto& entry : priority_) + fn(entry); + for (auto& entry : queue_) + fn(entry); + } + mutable std::mutex mutex_; private: diff --git a/src/working_files.cc b/src/working_files.cc index 1bd00aa1..e759b5d5 100644 --- a/src/working_files.cc +++ b/src/working_files.cc @@ -415,7 +415,8 @@ std::string WorkingFile::FindClosestCallNameInBuffer( lsPosition WorkingFile::FindStableCompletionSource( lsPosition position, bool* is_global_completion, - std::string* existing_completion) const { + std::string* existing_completion, + lsPosition* replace_end_pos) const { *is_global_completion = true; int start_offset = GetOffsetForPosition(position, buffer_content); @@ -441,6 +442,14 @@ lsPosition WorkingFile::FindStableCompletionSource( --offset; } + *replace_end_pos = position; + for (int i = start_offset; i < buffer_content.size(); i++) { + char c = buffer_content[i]; + if (!isalnum(c) && c != '_') break; + // We know that replace_end_pos and position are on the same line. + replace_end_pos->character++; + } + *existing_completion = buffer_content.substr(offset, start_offset - offset); return GetPositionForOffset(buffer_content, offset); } diff --git a/src/working_files.h b/src/working_files.h index c48c57d8..46e71b99 100644 --- a/src/working_files.h +++ b/src/working_files.h @@ -69,7 +69,8 @@ struct WorkingFile { // content the user has entered. lsPosition FindStableCompletionSource(lsPosition position, bool* is_global_completion, - std::string* existing_completion) const; + std::string* existing_completion, + lsPosition* replace_end_pos) const; private: // Compute index_to_buffer and buffer_to_index.