mirror of
				https://github.com/MaskRay/ccls.git
				synced 2025-11-03 22:04:24 +00:00 
			
		
		
		
	First iteration of code completion off the main thread. Still one race condition but hopefully it shouldn't happen too often.
This commit is contained in:
		
							parent
							
								
									c6dead848e
								
							
						
					
					
						commit
						5e8e13380d
					
				
							
								
								
									
										32
									
								
								src/atomic_object.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/atomic_object.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <condition_variable>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// A object which can be stored and taken from atomically.
 | 
			
		||||
template <class T>
 | 
			
		||||
struct AtomicObject {
 | 
			
		||||
  void Set(std::unique_ptr<T> t) {
 | 
			
		||||
    std::lock_guard<std::mutex> lock(mutex_);
 | 
			
		||||
    value_ = std::move(t);
 | 
			
		||||
    cv_.notify_one();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::unique_ptr<T> Take() {
 | 
			
		||||
    std::unique_lock<std::mutex> 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<T> value_;
 | 
			
		||||
  mutable std::mutex mutex_;
 | 
			
		||||
  std::condition_variable cv_;
 | 
			
		||||
};
 | 
			
		||||
@ -4,6 +4,7 @@
 | 
			
		||||
#include "timer.h"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <thread>
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
unsigned Flags() {
 | 
			
		||||
@ -191,64 +192,28 @@ std::string BuildDetailString(CXCompletionString completion_string) {
 | 
			
		||||
 | 
			
		||||
  return detail;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CompletionSession::CompletionSession(const CompilationEntry& file, WorkingFiles* working_files) : file(file) {
 | 
			
		||||
  std::vector<CXUnsavedFile> unsaved = working_files->AsUnsavedFiles();
 | 
			
		||||
void CompletionMain(CompletionManager* completion_manager) {
 | 
			
		||||
  while (true) {
 | 
			
		||||
    std::unique_ptr<CompletionManager::CompletionRequest> request = completion_manager->completion_request.Take();
 | 
			
		||||
 | 
			
		||||
  std::vector<std::string> args = file.args;
 | 
			
		||||
  args.push_back("-x");
 | 
			
		||||
  args.push_back("c++");
 | 
			
		||||
  args.push_back("-fparse-all-comments");
 | 
			
		||||
 | 
			
		||||
  std::string sent_args = "";
 | 
			
		||||
  for (auto& arg : args)
 | 
			
		||||
    sent_args += arg + ", ";
 | 
			
		||||
  std::cerr << "Creating completion session with arguments " << sent_args << std::endl;
 | 
			
		||||
 | 
			
		||||
  // TODO: I think we crash when there are syntax errors.
 | 
			
		||||
  active_index = MakeUnique<clang::Index>(0 /*excludeDeclarationsFromPCH*/, 0 /*displayDiagnostics*/);
 | 
			
		||||
  active = MakeUnique<clang::TranslationUnit>(*active_index, file.filename, args, unsaved, Flags());
 | 
			
		||||
  std::cerr << "Done creating active; did_fail=" << active->did_fail << std::endl;
 | 
			
		||||
  //if (active->did_fail) {
 | 
			
		||||
  //  std::cerr << "Failed to create translation unit; trying again..." << std::endl;
 | 
			
		||||
  //  active = MakeUnique<clang::TranslationUnit>(*active_index, file.filename, args, unsaved, Flags());
 | 
			
		||||
  //}
 | 
			
		||||
 | 
			
		||||
  // Despite requesting clang create a precompiled header on the first parse in
 | 
			
		||||
  // Flags() via CXTranslationUnit_CreatePreambleOnFirstParse, it doesn't seem
 | 
			
		||||
  // to do so. Immediately reparsing will create one which reduces
 | 
			
		||||
  // clang_codeCompleteAt timing from 200ms to 20ms on simple files.
 | 
			
		||||
  // TODO: figure out why this is crashing so much
 | 
			
		||||
  if (!active->did_fail) {
 | 
			
		||||
    std::cerr << "Start reparse" << std::endl;
 | 
			
		||||
    active->ReparseTranslationUnit(unsaved);
 | 
			
		||||
 | 
			
		||||
    std::cerr << "Done reparse" << std::endl;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CompletionSession::~CompletionSession() {}
 | 
			
		||||
 | 
			
		||||
void CompletionSession::Refresh(std::vector<CXUnsavedFile>& unsaved) {
 | 
			
		||||
  // TODO: Do this off the code completion thread so we don't block completions.
 | 
			
		||||
  active->ReparseTranslationUnit(unsaved);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CompletionManager::CompletionManager(Project* project, WorkingFiles* working_files) : project(project), working_files(working_files) {}
 | 
			
		||||
 | 
			
		||||
NonElidedVector<lsCompletionItem> CompletionManager::CodeComplete(const lsTextDocumentPositionParams& completion_location) {
 | 
			
		||||
    NonElidedVector<lsCompletionItem> ls_result;
 | 
			
		||||
 | 
			
		||||
  CompletionSession* session = GetOrOpenSession(completion_location.textDocument.uri.GetPath());
 | 
			
		||||
    CompletionSession* session = completion_manager->GetOrOpenSession(request->location.textDocument.uri.GetPath());
 | 
			
		||||
 | 
			
		||||
  unsigned line = completion_location.position.line + 1;
 | 
			
		||||
  unsigned column = completion_location.position.character + 1;
 | 
			
		||||
    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;
 | 
			
		||||
    std::cerr << "Completing at " << line << ":" << column << std::endl;
 | 
			
		||||
 | 
			
		||||
  std::vector<CXUnsavedFile> unsaved = working_files->AsUnsavedFiles();
 | 
			
		||||
    // TODO/FIXME
 | 
			
		||||
    // TODO/FIXME
 | 
			
		||||
    // TODO/FIXME NOT THREAD SAFE
 | 
			
		||||
    // TODO/FIXME
 | 
			
		||||
    // TODO/FIXME
 | 
			
		||||
    std::vector<CXUnsavedFile> unsaved = completion_manager->working_files->AsUnsavedFiles();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    Timer timer;
 | 
			
		||||
@ -262,7 +227,8 @@ NonElidedVector<lsCompletionItem> CompletionManager::CodeComplete(const lsTextDo
 | 
			
		||||
      CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeBriefComments);
 | 
			
		||||
    if (!cx_results) {
 | 
			
		||||
      std::cerr << "Code completion failed" << std::endl;
 | 
			
		||||
    return ls_result;
 | 
			
		||||
      request->on_complete(ls_result);
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    timer.ResetAndPrint("clangCodeCompleteAt");
 | 
			
		||||
    std::cerr << "Got " << cx_results->NumResults << " results" << std::endl;
 | 
			
		||||
@ -323,12 +289,13 @@ NonElidedVector<lsCompletionItem> CompletionManager::CodeComplete(const lsTextDo
 | 
			
		||||
 | 
			
		||||
      ls_result.push_back(ls_completion_item);
 | 
			
		||||
    }
 | 
			
		||||
  timer.ResetAndPrint("Building completion results");
 | 
			
		||||
    timer.ResetAndPrint("Building " + std::to_string(ls_result.size()) + " completion results");
 | 
			
		||||
 | 
			
		||||
    clang_disposeCodeCompleteResults(cx_results);
 | 
			
		||||
    timer.ResetAndPrint("clang_disposeCodeCompleteResults ");
 | 
			
		||||
 | 
			
		||||
  return ls_result;
 | 
			
		||||
    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)
 | 
			
		||||
@ -352,6 +319,64 @@ NonElidedVector<lsCompletionItem> CompletionManager::CodeComplete(const lsTextDo
 | 
			
		||||
 | 
			
		||||
    // probably don't need
 | 
			
		||||
    //  - textDocument/willSave
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace
 | 
			
		||||
 | 
			
		||||
CompletionSession::CompletionSession(const CompilationEntry& file, WorkingFiles* working_files) : file(file) {
 | 
			
		||||
  std::vector<CXUnsavedFile> unsaved = working_files->AsUnsavedFiles();
 | 
			
		||||
 | 
			
		||||
  std::vector<std::string> args = file.args;
 | 
			
		||||
  args.push_back("-x");
 | 
			
		||||
  args.push_back("c++");
 | 
			
		||||
  args.push_back("-fparse-all-comments");
 | 
			
		||||
 | 
			
		||||
  std::string sent_args = "";
 | 
			
		||||
  for (auto& arg : args)
 | 
			
		||||
    sent_args += arg + ", ";
 | 
			
		||||
  std::cerr << "Creating completion session with arguments " << sent_args << std::endl;
 | 
			
		||||
 | 
			
		||||
  // TODO: I think we crash when there are syntax errors.
 | 
			
		||||
  active_index = MakeUnique<clang::Index>(0 /*excludeDeclarationsFromPCH*/, 0 /*displayDiagnostics*/);
 | 
			
		||||
  active = MakeUnique<clang::TranslationUnit>(*active_index, file.filename, args, unsaved, Flags());
 | 
			
		||||
  std::cerr << "Done creating active; did_fail=" << active->did_fail << std::endl;
 | 
			
		||||
  //if (active->did_fail) {
 | 
			
		||||
  //  std::cerr << "Failed to create translation unit; trying again..." << std::endl;
 | 
			
		||||
  //  active = MakeUnique<clang::TranslationUnit>(*active_index, file.filename, args, unsaved, Flags());
 | 
			
		||||
  //}
 | 
			
		||||
 | 
			
		||||
  // Despite requesting clang create a precompiled header on the first parse in
 | 
			
		||||
  // Flags() via CXTranslationUnit_CreatePreambleOnFirstParse, it doesn't seem
 | 
			
		||||
  // to do so. Immediately reparsing will create one which reduces
 | 
			
		||||
  // clang_codeCompleteAt timing from 200ms to 20ms on simple files.
 | 
			
		||||
  // TODO: figure out why this is crashing so much
 | 
			
		||||
  if (!active->did_fail) {
 | 
			
		||||
    std::cerr << "Start reparse" << std::endl;
 | 
			
		||||
    active->ReparseTranslationUnit(unsaved);
 | 
			
		||||
 | 
			
		||||
    std::cerr << "Done reparse" << std::endl;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CompletionSession::~CompletionSession() {}
 | 
			
		||||
 | 
			
		||||
void CompletionSession::Refresh(std::vector<CXUnsavedFile>& unsaved) {
 | 
			
		||||
  // TODO: Do this off the code completion thread so we don't block completions.
 | 
			
		||||
  active->ReparseTranslationUnit(unsaved);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CompletionManager::CompletionManager(Project* project, WorkingFiles* working_files) : project(project), working_files(working_files) {
 | 
			
		||||
  new std::thread([&]() {
 | 
			
		||||
    CompletionMain(this);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CompletionManager::CodeComplete(const lsTextDocumentPositionParams& completion_location, const OnComplete& on_complete) {
 | 
			
		||||
  auto request = MakeUnique<CompletionRequest>();
 | 
			
		||||
  request->location = completion_location;
 | 
			
		||||
  request->on_complete = on_complete;
 | 
			
		||||
  completion_request.Set(std::move(request));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CompletionSession* CompletionManager::GetOrOpenSession(const std::string& filename) {
 | 
			
		||||
 | 
			
		||||
@ -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 <clang-c/Index.h>
 | 
			
		||||
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
struct CompletionSession {
 | 
			
		||||
  CompilationEntry file;
 | 
			
		||||
 | 
			
		||||
@ -33,12 +36,18 @@ struct CompletionManager {
 | 
			
		||||
  Project* project;
 | 
			
		||||
  WorkingFiles* working_files;
 | 
			
		||||
 | 
			
		||||
  using OnComplete = std::function<void(NonElidedVector<lsCompletionItem> results)>;
 | 
			
		||||
  struct CompletionRequest {
 | 
			
		||||
    lsTextDocumentPositionParams location;
 | 
			
		||||
    OnComplete on_complete;
 | 
			
		||||
  };
 | 
			
		||||
  AtomicObject<CompletionRequest> 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<lsCompletionItem> 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);
 | 
			
		||||
};
 | 
			
		||||
@ -20,6 +20,7 @@
 | 
			
		||||
#include <rapidjson/ostreamwrapper.h>
 | 
			
		||||
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
@ -999,13 +1000,6 @@ void IndexMain(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1170,16 +1164,27 @@ void QueryDbMainLoop(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case IpcId::TextDocumentCompletion: {
 | 
			
		||||
      // TODO: better performance
 | 
			
		||||
      auto msg = static_cast<Ipc_TextDocumentComplete*>(message.get());
 | 
			
		||||
      lsTextDocumentPositionParams params = msg->params;
 | 
			
		||||
 | 
			
		||||
      CompletionManager::OnComplete callback = std::bind([](BaseIpcMessage* message, const NonElidedVector<lsCompletionItem>& results) {
 | 
			
		||||
        auto msg = static_cast<Ipc_TextDocumentComplete*>(message);
 | 
			
		||||
        auto ipc = IpcManager::instance();
 | 
			
		||||
 | 
			
		||||
        Out_TextDocumentComplete response;
 | 
			
		||||
        response.id = msg->id;
 | 
			
		||||
        response.result.isIncomplete = false;
 | 
			
		||||
      response.result.items = completion_manager->CodeComplete(msg->params);
 | 
			
		||||
        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));
 | 
			
		||||
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user