From 5e8e13380d57384b7c22c1584275afd7c1cd8620 Mon Sep 17 00:00:00 2001 From: Jacob Dufault Date: Sun, 16 Apr 2017 18:22:59 -0700 Subject: [PATCH] First iteration of code completion off the main thread. Still one race condition but hopefully it shouldn't happen too often. --- src/atomic_object.h | 32 +++++ src/code_completion.cc | 257 ++++++++++++++++++++++------------------- src/code_completion.h | 17 ++- src/command_line.cc | 35 +++--- 4 files changed, 206 insertions(+), 135 deletions(-) create mode 100644 src/atomic_object.h diff --git a/src/atomic_object.h b/src/atomic_object.h new file mode 100644 index 00000000..52e8b812 --- /dev/null +++ b/src/atomic_object.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + + +// A object which can be stored and taken from atomically. +template +struct AtomicObject { + void Set(std::unique_ptr t) { + std::lock_guard lock(mutex_); + value_ = std::move(t); + cv_.notify_one(); + } + + std::unique_ptr Take() { + std::unique_lock lock(mutex_); + while (!value_) { + // release lock as long as the wait and reaquire it afterwards. + cv_.wait(lock); + } + + return std::move(value_); + } + + private: + std::unique_ptr value_; + mutable std::mutex mutex_; + std::condition_variable cv_; +}; diff --git a/src/code_completion.cc b/src/code_completion.cc index 59aa6cdd..6c4a88f6 100644 --- a/src/code_completion.cc +++ b/src/code_completion.cc @@ -4,6 +4,7 @@ #include "timer.h" #include +#include namespace { unsigned Flags() { @@ -191,8 +192,138 @@ std::string BuildDetailString(CXCompletionString completion_string) { return detail; } + +void CompletionMain(CompletionManager* completion_manager) { + while (true) { + std::unique_ptr request = completion_manager->completion_request.Take(); + + + NonElidedVector ls_result; + + CompletionSession* session = completion_manager->GetOrOpenSession(request->location.textDocument.uri.GetPath()); + + unsigned line = request->location.position.line + 1; + unsigned column = request->location.position.character + 1; + + std::cerr << std::endl; + std::cerr << "Completing at " << line << ":" << column << std::endl; + + // TODO/FIXME + // TODO/FIXME + // TODO/FIXME NOT THREAD SAFE + // TODO/FIXME + // TODO/FIXME + std::vector unsaved = completion_manager->working_files->AsUnsavedFiles(); + + + Timer timer; + + + timer.Reset(); + CXCodeCompleteResults* cx_results = clang_codeCompleteAt( + session->active->cx_tu, + session->file.filename.c_str(), line, column, + unsaved.data(), unsaved.size(), + CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeBriefComments); + if (!cx_results) { + std::cerr << "Code completion failed" << std::endl; + request->on_complete(ls_result); + continue; + } + timer.ResetAndPrint("clangCodeCompleteAt"); + std::cerr << "Got " << cx_results->NumResults << " results" << std::endl; + + // TODO: for comments we could hack the unsaved buffer and transform // into /// + + ls_result.reserve(cx_results->NumResults); + + timer.Reset(); + for (unsigned i = 0; i < cx_results->NumResults; ++i) { + CXCompletionResult& result = cx_results->Results[i]; + + //unsigned int is_incomplete; + //CXCursorKind kind = clang_codeCompleteGetContainerKind(cx_results, &is_incomplete); + //std::cerr << "clang_codeCompleteGetContainerKind kind=" << kind << " is_incomplete=" << is_incomplete << std::endl; + + // CXCursor_InvalidCode fo + + //clang_codeCompleteGetContexts + //CXCompletionContext + // clang_codeCompleteGetContexts + // + //CXCursorKind kind; + //CXString str = clang_getCompletionParent(result.CompletionString, &kind); + //std::cerr << "clang_getCompletionParent kind=" << kind << ", str=" << clang::ToString(str) << std::endl; + // if global don't append now, append to end later + + // also clang_codeCompleteGetContainerKind + + // TODO: fill in more data + lsCompletionItem ls_completion_item; + + // kind/label/detail/docs + ls_completion_item.kind = GetCompletionKind(result.CursorKind); + ls_completion_item.label = BuildLabelString(result.CompletionString); + ls_completion_item.detail = BuildDetailString(result.CompletionString); + ls_completion_item.documentation = clang::ToString(clang_getCompletionBriefComment(result.CompletionString)); + + // Priority + int priority = clang_getCompletionPriority(result.CompletionString); + if (result.CursorKind == CXCursor_Destructor) { + priority *= 100; + //std::cerr << "Bumping[destructor] " << ls_completion_item.label << std::endl; + } + if (result.CursorKind == CXCursor_ConversionFunction || + (result.CursorKind == CXCursor_CXXMethod && StartsWith(ls_completion_item.label, "operator"))) { + //std::cerr << "Bumping[conversion] " << ls_completion_item.label << std::endl; + priority *= 100; + } + if (clang_getCompletionAvailability(result.CompletionString) != CXAvailability_Available) { + //std::cerr << "Bumping[notavailable] " << ls_completion_item.label << std::endl; + priority *= 100; + } + //std::cerr << "Adding kind=" << result.CursorKind << ", priority=" << ls_completion_item.priority_ << ", label=" << ls_completion_item.label << std::endl; + + // TODO: we can probably remove priority_ and our sort. + ls_completion_item.sortText = uint64_t(priority);// std::to_string(ls_completion_item.priority_); + + ls_result.push_back(ls_completion_item); + } + timer.ResetAndPrint("Building " + std::to_string(ls_result.size()) + " completion results"); + + clang_disposeCodeCompleteResults(cx_results); + timer.ResetAndPrint("clang_disposeCodeCompleteResults "); + + request->on_complete(ls_result); + continue; + + // we should probably main two translation units, one for + // serving current requests, and one that is reparsing (follow qtcreator) + + // todo: we need to run code completion on a separate thread from querydb + // so thread layout looks like: + // - stdin # Reads data from stdin + // - stdout # Pushes data to stdout + // - querydb # Resolves index database queries. + // - complete_responder # Handles low-latency code complete requests. + // - complete_parser # Parses most recent document for future code complete requests. + // - indexer (many) # Runs index jobs (for querydb updates) + + // use clang_codeCompleteAt + //CXUnsavedFile + // we need to setup CXUnsavedFile + // The key to doing that is via + // - textDocument/didOpen + // - textDocument/didChange + // - textDocument/didClose + + // probably don't need + // - textDocument/willSave + } } +} // namespace + CompletionSession::CompletionSession(const CompilationEntry& file, WorkingFiles* working_files) : file(file) { std::vector unsaved = working_files->AsUnsavedFiles(); @@ -235,123 +366,17 @@ void CompletionSession::Refresh(std::vector& unsaved) { active->ReparseTranslationUnit(unsaved); } -CompletionManager::CompletionManager(Project* project, WorkingFiles* working_files) : project(project), working_files(working_files) {} +CompletionManager::CompletionManager(Project* project, WorkingFiles* working_files) : project(project), working_files(working_files) { + new std::thread([&]() { + CompletionMain(this); + }); +} -NonElidedVector CompletionManager::CodeComplete(const lsTextDocumentPositionParams& completion_location) { - NonElidedVector ls_result; - - CompletionSession* session = GetOrOpenSession(completion_location.textDocument.uri.GetPath()); - - unsigned line = completion_location.position.line + 1; - unsigned column = completion_location.position.character + 1; - - std::cerr << std::endl; - //std::cerr << "Completing at " << line << ":" << column << std::endl; - - std::vector unsaved = working_files->AsUnsavedFiles(); - - - Timer timer; - - - timer.Reset(); - CXCodeCompleteResults* cx_results = clang_codeCompleteAt( - session->active->cx_tu, - session->file.filename.c_str(), line, column, - unsaved.data(), unsaved.size(), - CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeBriefComments); - if (!cx_results) { - std::cerr << "Code completion failed" << std::endl; - return ls_result; - } - timer.ResetAndPrint("clangCodeCompleteAt"); - std::cerr << "Got " << cx_results->NumResults << " results" << std::endl; - - // TODO: for comments we could hack the unsaved buffer and transform // into /// - - ls_result.reserve(cx_results->NumResults); - - timer.Reset(); - for (unsigned i = 0; i < cx_results->NumResults; ++i) { - CXCompletionResult& result = cx_results->Results[i]; - - //unsigned int is_incomplete; - //CXCursorKind kind = clang_codeCompleteGetContainerKind(cx_results, &is_incomplete); - //std::cerr << "clang_codeCompleteGetContainerKind kind=" << kind << " is_incomplete=" << is_incomplete << std::endl; - - // CXCursor_InvalidCode fo - - //clang_codeCompleteGetContexts - //CXCompletionContext - // clang_codeCompleteGetContexts - // - //CXCursorKind kind; - //CXString str = clang_getCompletionParent(result.CompletionString, &kind); - //std::cerr << "clang_getCompletionParent kind=" << kind << ", str=" << clang::ToString(str) << std::endl; - // if global don't append now, append to end later - - // also clang_codeCompleteGetContainerKind - - // TODO: fill in more data - lsCompletionItem ls_completion_item; - - // kind/label/detail/docs - ls_completion_item.kind = GetCompletionKind(result.CursorKind); - ls_completion_item.label = BuildLabelString(result.CompletionString); - ls_completion_item.detail = BuildDetailString(result.CompletionString); - ls_completion_item.documentation = clang::ToString(clang_getCompletionBriefComment(result.CompletionString)); - - // Priority - int priority = clang_getCompletionPriority(result.CompletionString); - if (result.CursorKind == CXCursor_Destructor) { - priority *= 100; - //std::cerr << "Bumping[destructor] " << ls_completion_item.label << std::endl; - } - if (result.CursorKind == CXCursor_ConversionFunction || - (result.CursorKind == CXCursor_CXXMethod && StartsWith(ls_completion_item.label, "operator"))) { - //std::cerr << "Bumping[conversion] " << ls_completion_item.label << std::endl; - priority *= 100; - } - if (clang_getCompletionAvailability(result.CompletionString) != CXAvailability_Available) { - //std::cerr << "Bumping[notavailable] " << ls_completion_item.label << std::endl; - priority *= 100; - } - //std::cerr << "Adding kind=" << result.CursorKind << ", priority=" << ls_completion_item.priority_ << ", label=" << ls_completion_item.label << std::endl; - - // TODO: we can probably remove priority_ and our sort. - ls_completion_item.sortText = uint64_t(priority);// std::to_string(ls_completion_item.priority_); - - ls_result.push_back(ls_completion_item); - } - timer.ResetAndPrint("Building completion results"); - - clang_disposeCodeCompleteResults(cx_results); - timer.ResetAndPrint("clang_disposeCodeCompleteResults "); - - return ls_result; - - // we should probably main two translation units, one for - // serving current requests, and one that is reparsing (follow qtcreator) - - // todo: we need to run code completion on a separate thread from querydb - // so thread layout looks like: - // - stdin # Reads data from stdin - // - stdout # Pushes data to stdout - // - querydb # Resolves index database queries. - // - complete_responder # Handles low-latency code complete requests. - // - complete_parser # Parses most recent document for future code complete requests. - // - indexer (many) # Runs index jobs (for querydb updates) - - // use clang_codeCompleteAt - //CXUnsavedFile - // we need to setup CXUnsavedFile - // The key to doing that is via - // - textDocument/didOpen - // - textDocument/didChange - // - textDocument/didClose - - // probably don't need - // - textDocument/willSave +void CompletionManager::CodeComplete(const lsTextDocumentPositionParams& completion_location, const OnComplete& on_complete) { + auto request = MakeUnique(); + request->location = completion_location; + request->on_complete = on_complete; + completion_request.Set(std::move(request)); } CompletionSession* CompletionManager::GetOrOpenSession(const std::string& filename) { diff --git a/src/code_completion.h b/src/code_completion.h index 1e4b6bfc..5ef9d61e 100644 --- a/src/code_completion.h +++ b/src/code_completion.h @@ -1,3 +1,4 @@ +#include "atomic_object.h" #include "language_server_api.h" #include "libclangmm/CompletionString.h" #include "libclangmm/Index.h" @@ -7,6 +8,8 @@ #include +#include + struct CompletionSession { CompilationEntry file; @@ -33,12 +36,18 @@ struct CompletionManager { Project* project; WorkingFiles* working_files; + using OnComplete = std::function results)>; + struct CompletionRequest { + lsTextDocumentPositionParams location; + OnComplete on_complete; + }; + AtomicObject completion_request; + CompletionManager(Project* project, WorkingFiles* working_files); - // This all should run on complete_responder thread. This will internally run a child thread to - // reparse. - NonElidedVector CodeComplete(const lsTextDocumentPositionParams& completion_location); + // Start a code completion at the given location. |on_complete| will run when + // completion results are available. |on_complete| may run on any thread. + void CodeComplete(const lsTextDocumentPositionParams& completion_location, const OnComplete& on_complete); - private: CompletionSession* GetOrOpenSession(const std::string& filename); }; \ No newline at end of file diff --git a/src/command_line.cc b/src/command_line.cc index 6bfbd79e..aab55427 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -999,13 +1000,6 @@ void IndexMain( - - - - - - - @@ -1170,16 +1164,27 @@ void QueryDbMainLoop( } case IpcId::TextDocumentCompletion: { - // TODO: better performance auto msg = static_cast(message.get()); - Out_TextDocumentComplete response; - response.id = msg->id; - response.result.isIncomplete = false; - response.result.items = completion_manager->CodeComplete(msg->params); + lsTextDocumentPositionParams params = msg->params; + + CompletionManager::OnComplete callback = std::bind([](BaseIpcMessage* message, const NonElidedVector& results) { + auto msg = static_cast(message); + auto ipc = IpcManager::instance(); + + Out_TextDocumentComplete response; + response.id = msg->id; + response.result.isIncomplete = false; + response.result.items = results; + + Timer timer; + ipc->SendOutMessageToClient(response); + timer.ResetAndPrint("Writing completion results"); + + delete message; + }, message.release(), std::placeholders::_1); + + completion_manager->CodeComplete(params, std::move(callback)); - Timer timer; - ipc->SendOutMessageToClient(response); - timer.ResetAndPrint("Writing completion results"); break; }