ccls/src/code_completion.cc

406 lines
14 KiB
C++
Raw Normal View History

#include "code_completion.h"
#include "libclangmm/Utility.h"
2017-05-10 04:00:05 +00:00
#include "platform.h"
#include "timer.h"
#include <algorithm>
#include <thread>
namespace {
unsigned Flags() {
2017-03-27 04:04:48 +00:00
/*
return
2017-03-27 04:04:48 +00:00
//CXTranslationUnit_Incomplete |
CXTranslationUnit_PrecompiledPreamble |
CXTranslationUnit_CacheCompletionResults |
//CXTranslationUnit_ForSerialization |
2017-03-27 04:04:48 +00:00
CXTranslationUnit_IncludeBriefCommentsInCodeCompletion |
//CXTranslationUnit_CreatePreambleOnFirstParse |
CXTranslationUnit_KeepGoing;
2017-03-27 04:04:48 +00:00
*/
return
CXTranslationUnit_CacheCompletionResults |
CXTranslationUnit_PrecompiledPreamble |
CXTranslationUnit_IncludeBriefCommentsInCodeCompletion |
#if !defined(_WIN32)
// For whatever reason, CreatePreambleOnFirstParse causes clang to become
// very crashy on windows.
// TODO: do more investigation, submit fixes to clang.
CXTranslationUnit_CreatePreambleOnFirstParse |
#endif
2017-03-27 04:04:48 +00:00
CXTranslationUnit_DetailedPreprocessingRecord;
}
lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) {
switch (cursor_kind) {
case CXCursor_ObjCInstanceMethodDecl:
case CXCursor_CXXMethod:
return lsCompletionItemKind::Method;
case CXCursor_FunctionTemplate:
case CXCursor_FunctionDecl:
return lsCompletionItemKind::Function;
case CXCursor_Constructor:
case CXCursor_Destructor:
case CXCursor_ConversionFunction:
return lsCompletionItemKind::Constructor;
case CXCursor_FieldDecl:
return lsCompletionItemKind::Field;
case CXCursor_VarDecl:
case CXCursor_ParmDecl:
return lsCompletionItemKind::Variable;
2017-03-27 04:04:48 +00:00
case CXCursor_UnionDecl:
case CXCursor_ClassTemplate:
case CXCursor_ClassTemplatePartialSpecialization:
case CXCursor_ClassDecl:
case CXCursor_StructDecl:
case CXCursor_UsingDeclaration:
case CXCursor_TypedefDecl:
case CXCursor_TypeAliasDecl:
case CXCursor_TypeAliasTemplateDecl:
return lsCompletionItemKind::Class;
case CXCursor_EnumConstantDecl:
case CXCursor_EnumDecl:
return lsCompletionItemKind::Enum;
2017-04-16 08:37:27 +00:00
case CXCursor_MacroInstantiation:
case CXCursor_MacroDefinition:
return lsCompletionItemKind::Interface;
case CXCursor_Namespace:
2017-04-16 08:37:27 +00:00
case CXCursor_NamespaceAlias:
case CXCursor_NamespaceRef:
return lsCompletionItemKind::Module;
2017-04-16 08:37:27 +00:00
case CXCursor_MemberRef:
case CXCursor_TypeRef:
return lsCompletionItemKind::Reference;
//return lsCompletionItemKind::Property;
//return lsCompletionItemKind::Unit;
//return lsCompletionItemKind::Value;
//return lsCompletionItemKind::Keyword;
//return lsCompletionItemKind::Snippet;
//return lsCompletionItemKind::Color;
//return lsCompletionItemKind::File;
case CXCursor_NotImplemented:
2017-03-27 04:04:48 +00:00
return lsCompletionItemKind::Text;
default:
std::cerr << "Unhandled completion kind " << cursor_kind << std::endl;
return lsCompletionItemKind::Text;
}
}
2017-03-31 04:13:58 +00:00
std::string BuildLabelString(CXCompletionString completion_string) {
std::string label;
int num_chunks = clang_getNumCompletionChunks(completion_string);
2017-04-16 08:55:14 +00:00
for (int i = 0; i < num_chunks; ++i) {
2017-03-31 04:13:58 +00:00
CXCompletionChunkKind kind = clang_getCompletionChunkKind(completion_string, i);
if (kind == CXCompletionChunk_TypedText) {
label += clang::ToString(clang_getCompletionChunkText(completion_string, i));
break;
}
}
return label;
}
std::string BuildDetailString(CXCompletionString completion_string) {
std::string detail;
int num_chunks = clang_getNumCompletionChunks(completion_string);
2017-04-16 08:55:14 +00:00
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);
detail += BuildDetailString(nested);
break;
}
case CXCompletionChunk_Placeholder: {
// TODO: send this info to vscode.
CXString text = clang_getCompletionChunkText(completion_string, i);
detail += clang::ToString(text);
break;
}
case CXCompletionChunk_TypedText:
case CXCompletionChunk_Text:
case CXCompletionChunk_Informative:
case CXCompletionChunk_CurrentParameter: {
CXString text = clang_getCompletionChunkText(completion_string, i);
detail += clang::ToString(text);
break;
}
case CXCompletionChunk_ResultType: {
CXString text = clang_getCompletionChunkText(completion_string, i);
std::string new_detail = clang::ToString(text) + detail + " ";
detail = new_detail;
break;
}
case CXCompletionChunk_LeftParen:
detail += "(";
break;
case CXCompletionChunk_RightParen:
detail += ")";
break;
case CXCompletionChunk_LeftBracket:
detail += "]";
break;
case CXCompletionChunk_RightBracket:
detail += "[";
break;
case CXCompletionChunk_LeftBrace:
detail += "{";
break;
case CXCompletionChunk_RightBrace:
detail += "}";
break;
case CXCompletionChunk_LeftAngle:
detail += "<";
break;
case CXCompletionChunk_RightAngle:
detail += ">";
break;
case CXCompletionChunk_Comma:
detail += ", ";
break;
case CXCompletionChunk_Colon:
detail += ":";
break;
case CXCompletionChunk_SemiColon:
detail += ";";
break;
case CXCompletionChunk_Equal:
detail += "=";
break;
case CXCompletionChunk_HorizontalSpace:
case CXCompletionChunk_VerticalSpace:
detail += " ";
break;
}
}
return detail;
}
void CompletionMain(CompletionManager* completion_manager) {
while (true) {
std::unique_ptr<CompletionManager::CompletionRequest> request = completion_manager->completion_request.Take();
NonElidedVector<lsCompletionItem> 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<CXUnsavedFile> 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 Project::Entry& file, WorkingFiles* working_files) : file(file) {
std::vector<CXUnsavedFile> unsaved = working_files->AsUnsavedFiles();
std::vector<std::string> args = file.args;
args.push_back("-fparse-all-comments");
std::cerr << "Creating completion session with arguments " << StringJoin(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());
2017-03-27 04:04:48 +00:00
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.
2017-03-27 04:04:48 +00:00
// 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() {}
2017-03-27 04:04:48 +00:00
void CompletionSession::Refresh(std::vector<CXUnsavedFile>& unsaved) {
// TODO: Do this off the code completion thread so we don't block completions.
2017-03-27 04:04:48 +00:00
active->ReparseTranslationUnit(unsaved);
}
CompletionManager::CompletionManager(IndexerConfig* config, Project* project, WorkingFiles* working_files)
: config(config), project(project), working_files(working_files) {
new std::thread([&]() {
2017-05-10 04:00:05 +00:00
SetCurrentThreadName("completion");
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) {
// Try to find existing session.
for (auto& session : sessions) {
if (session->file.filename == filename)
return session.get();
}
// Create new session. Note that this will block.
std::cerr << "Creating new code completion session for " << filename << std::endl;
2017-04-20 05:01:36 +00:00
optional<Project::Entry> entry = project->FindCompilationEntryForFile(filename);
if (!entry) {
std::cerr << "Unable to find compilation entry" << std::endl;
2017-04-20 05:01:36 +00:00
entry = Project::Entry();
entry->filename = filename;
}
else {
std::cerr << "Found compilation entry" << std::endl;
}
sessions.push_back(MakeUnique<CompletionSession>(*entry, working_files));
return sessions[sessions.size() - 1].get();
2017-03-28 01:47:12 +00:00
}