2017-03-26 21:40:34 +00:00
|
|
|
#include "code_completion.h"
|
|
|
|
|
|
|
|
#include "libclangmm/Utility.h"
|
2017-05-10 04:00:05 +00:00
|
|
|
#include "platform.h"
|
2017-03-26 21:40:34 +00:00
|
|
|
#include "timer.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
2017-04-17 01:22:59 +00:00
|
|
|
#include <thread>
|
2017-03-26 21:40:34 +00:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
unsigned Flags() {
|
2017-03-27 04:04:48 +00:00
|
|
|
return
|
2017-05-10 04:52:15 +00:00
|
|
|
CXTranslationUnit_Incomplete |
|
|
|
|
CXTranslationUnit_KeepGoing |
|
2017-03-27 04:04:48 +00:00
|
|
|
CXTranslationUnit_CacheCompletionResults |
|
|
|
|
CXTranslationUnit_PrecompiledPreamble |
|
2017-05-10 04:52:15 +00:00
|
|
|
CXTranslationUnit_IncludeBriefCommentsInCodeCompletion
|
2017-05-09 05:15:35 +00:00
|
|
|
#if !defined(_WIN32)
|
|
|
|
// For whatever reason, CreatePreambleOnFirstParse causes clang to become
|
|
|
|
// very crashy on windows.
|
|
|
|
// TODO: do more investigation, submit fixes to clang.
|
2017-05-10 04:52:15 +00:00
|
|
|
| CXTranslationUnit_CreatePreambleOnFirstParse
|
2017-05-09 05:15:35 +00:00
|
|
|
#endif
|
2017-05-10 04:52:15 +00:00
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetCompletionPriority(const CXCompletionString& str, CXCursorKind result_kind, const std::string& label) {
|
|
|
|
int priority = clang_getCompletionPriority(str);
|
|
|
|
if (result_kind == CXCursor_Destructor) {
|
|
|
|
priority *= 100;
|
|
|
|
//std::cerr << "Bumping[destructor] " << ls_completion_item.label << std::endl;
|
|
|
|
}
|
|
|
|
if (result_kind == CXCursor_ConversionFunction ||
|
|
|
|
(result_kind == CXCursor_CXXMethod && StartsWith(label, "operator"))) {
|
|
|
|
//std::cerr << "Bumping[conversion] " << ls_completion_item.label << std::endl;
|
|
|
|
priority *= 100;
|
|
|
|
}
|
|
|
|
if (clang_getCompletionAvailability(str) != CXAvailability_Available) {
|
|
|
|
//std::cerr << "Bumping[notavailable] " << ls_completion_item.label << std::endl;
|
|
|
|
priority *= 100;
|
|
|
|
}
|
|
|
|
return priority;
|
2017-03-26 21:40:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
2017-03-26 21:40:34 +00:00
|
|
|
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:
|
2017-03-26 21:40:34 +00:00
|
|
|
case CXCursor_MacroDefinition:
|
|
|
|
return lsCompletionItemKind::Interface;
|
|
|
|
|
|
|
|
case CXCursor_Namespace:
|
2017-04-16 08:37:27 +00:00
|
|
|
case CXCursor_NamespaceAlias:
|
|
|
|
case CXCursor_NamespaceRef:
|
2017-03-26 21:40:34 +00:00
|
|
|
return lsCompletionItemKind::Module;
|
|
|
|
|
2017-04-16 08:37:27 +00:00
|
|
|
case CXCursor_MemberRef:
|
|
|
|
case CXCursor_TypeRef:
|
|
|
|
return lsCompletionItemKind::Reference;
|
|
|
|
|
2017-03-26 21:40:34 +00:00
|
|
|
//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;
|
2017-03-26 21:40:34 +00:00
|
|
|
|
|
|
|
default:
|
2017-05-10 04:52:15 +00:00
|
|
|
std::cerr << "[complete] Unhandled completion kind " << cursor_kind << std::endl;
|
2017-03-26 21:40:34 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-03-26 21:40:34 +00:00
|
|
|
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) {
|
2017-03-26 21:40:34 +00:00
|
|
|
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;
|
|
|
|
}
|
2017-04-17 01:22:59 +00:00
|
|
|
|
2017-05-11 06:25:41 +00:00
|
|
|
void EnsureDocumentParsed(CompletionSession* session,
|
|
|
|
std::unique_ptr<clang::TranslationUnit>* tu,
|
|
|
|
std::unique_ptr<clang::Index>* index) {
|
|
|
|
// Nothing to do. We already have a translation unit and an index.
|
|
|
|
if (*tu && *index)
|
|
|
|
return;
|
|
|
|
|
|
|
|
std::vector<std::string> args = session->file.args;
|
|
|
|
args.push_back("-fparse-all-comments");
|
|
|
|
|
|
|
|
std::vector<CXUnsavedFile> unsaved = session->working_files->AsUnsavedFiles();
|
|
|
|
|
|
|
|
std::cerr << "[complete] Creating completion session with arguments " << StringJoin(args) << std::endl;
|
|
|
|
*index = MakeUnique<clang::Index>(0 /*excludeDeclarationsFromPCH*/, 0 /*displayDiagnostics*/);
|
|
|
|
*tu = MakeUnique<clang::TranslationUnit>(*index->get(), session->file.filename, args, unsaved, Flags());
|
|
|
|
std::cerr << "[complete] Done creating active; did_fail=" << (*tu)->did_fail << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CompletionParseMain(CompletionManager* completion_manager) {
|
|
|
|
while (true) {
|
|
|
|
// Fetching the completion request blocks until we have a request.
|
|
|
|
std::unique_ptr<std::string> path = completion_manager->reparse_request.Take();
|
|
|
|
|
|
|
|
CompletionSession* session = completion_manager->GetOrOpenSession(*path);
|
|
|
|
std::unique_ptr<clang::TranslationUnit> parsing;
|
|
|
|
std::unique_ptr<clang::Index> parsing_index;
|
|
|
|
|
|
|
|
EnsureDocumentParsed(session, &parsing, &parsing_index);
|
|
|
|
|
|
|
|
// Swap out active.
|
|
|
|
std::lock_guard<std::mutex> lock(session->usage_lock);
|
|
|
|
session->active = std::move(parsing);
|
|
|
|
session->active_index = std::move(parsing_index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CompletionQueryMain(CompletionManager* completion_manager) {
|
2017-04-17 01:22:59 +00:00
|
|
|
while (true) {
|
2017-05-10 04:52:15 +00:00
|
|
|
// Fetching the completion request blocks until we have a request.
|
2017-04-17 01:22:59 +00:00
|
|
|
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());
|
2017-05-10 04:52:15 +00:00
|
|
|
std::lock_guard<std::mutex> lock(session->usage_lock);
|
|
|
|
|
2017-05-11 06:25:41 +00:00
|
|
|
EnsureDocumentParsed(session, &session->active, &session->active_index);
|
2017-04-17 01:22:59 +00:00
|
|
|
|
|
|
|
unsigned line = request->location.position.line + 1;
|
|
|
|
unsigned column = request->location.position.character + 1;
|
|
|
|
|
|
|
|
std::cerr << std::endl;
|
2017-05-10 04:52:15 +00:00
|
|
|
std::cerr << "[complete] Completing at " << line << ":" << column << std::endl;
|
|
|
|
|
|
|
|
Timer timer;
|
2017-04-17 01:22:59 +00:00
|
|
|
|
|
|
|
std::vector<CXUnsavedFile> unsaved = completion_manager->working_files->AsUnsavedFiles();
|
2017-05-10 04:52:15 +00:00
|
|
|
timer.ResetAndPrint("[complete] Fetching unsaved files");
|
2017-04-17 01:22:59 +00:00
|
|
|
|
|
|
|
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) {
|
2017-05-10 04:52:15 +00:00
|
|
|
std::cerr << "[complete] Code completion failed" << std::endl;
|
2017-04-17 01:22:59 +00:00
|
|
|
request->on_complete(ls_result);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-05-10 04:52:15 +00:00
|
|
|
timer.ResetAndPrint("[complete] clangCodeCompleteAt");
|
|
|
|
std::cerr << "[complete] Got " << cx_results->NumResults << " results" << std::endl;
|
2017-04-17 01:22:59 +00:00
|
|
|
|
|
|
|
ls_result.reserve(cx_results->NumResults);
|
|
|
|
|
|
|
|
timer.Reset();
|
|
|
|
for (unsigned i = 0; i < cx_results->NumResults; ++i) {
|
|
|
|
CXCompletionResult& result = cx_results->Results[i];
|
|
|
|
|
|
|
|
// TODO: fill in more data
|
|
|
|
lsCompletionItem ls_completion_item;
|
|
|
|
|
2017-05-10 04:52:15 +00:00
|
|
|
// kind/label/detail/docs/sortText
|
2017-04-17 01:22:59 +00:00
|
|
|
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));
|
2017-05-10 04:52:15 +00:00
|
|
|
ls_completion_item.sortText = uint64_t(GetCompletionPriority(result.CompletionString, result.CursorKind, ls_completion_item.label));
|
2017-04-17 01:22:59 +00:00
|
|
|
|
|
|
|
ls_result.push_back(ls_completion_item);
|
|
|
|
}
|
2017-05-10 04:52:15 +00:00
|
|
|
timer.ResetAndPrint("[complete] Building " + std::to_string(ls_result.size()) + " completion results");
|
2017-04-17 01:22:59 +00:00
|
|
|
|
|
|
|
clang_disposeCodeCompleteResults(cx_results);
|
2017-05-10 04:52:15 +00:00
|
|
|
timer.ResetAndPrint("[complete] clang_disposeCodeCompleteResults ");
|
2017-04-17 01:22:59 +00:00
|
|
|
|
|
|
|
request->on_complete(ls_result);
|
|
|
|
continue;
|
|
|
|
}
|
2017-03-26 21:40:34 +00:00
|
|
|
}
|
|
|
|
|
2017-04-17 01:22:59 +00:00
|
|
|
} // namespace
|
|
|
|
|
2017-05-10 04:52:15 +00:00
|
|
|
CompletionSession::CompletionSession(const Project::Entry& file, WorkingFiles* working_files)
|
|
|
|
: file(file), working_files(working_files) {}
|
|
|
|
|
|
|
|
CompletionSession::~CompletionSession() {}
|
|
|
|
|
2017-04-18 04:06:01 +00:00
|
|
|
CompletionManager::CompletionManager(IndexerConfig* config, Project* project, WorkingFiles* working_files)
|
|
|
|
: config(config), project(project), working_files(working_files) {
|
2017-04-17 01:22:59 +00:00
|
|
|
new std::thread([&]() {
|
2017-05-11 06:25:41 +00:00
|
|
|
SetCurrentThreadName("completequery");
|
|
|
|
CompletionQueryMain(this);
|
|
|
|
});
|
|
|
|
|
|
|
|
new std::thread([&]() {
|
|
|
|
SetCurrentThreadName("completeparse");
|
|
|
|
CompletionParseMain(this);
|
2017-04-17 01:22:59 +00:00
|
|
|
});
|
|
|
|
}
|
2017-03-26 21:40:34 +00:00
|
|
|
|
2017-04-17 01:22:59 +00:00
|
|
|
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));
|
2017-03-26 21:40:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2017-05-10 04:52:15 +00:00
|
|
|
std::cerr << "[complete] Creating new code completion session for " << filename << std::endl;
|
2017-04-20 05:01:36 +00:00
|
|
|
optional<Project::Entry> entry = project->FindCompilationEntryForFile(filename);
|
2017-03-26 21:40:34 +00:00
|
|
|
if (!entry) {
|
2017-05-10 04:52:15 +00:00
|
|
|
std::cerr << "[complete] Unable to find compilation entry" << std::endl;
|
2017-04-20 05:01:36 +00:00
|
|
|
entry = Project::Entry();
|
2017-03-26 21:40:34 +00:00
|
|
|
entry->filename = filename;
|
|
|
|
}
|
|
|
|
else {
|
2017-05-10 04:52:15 +00:00
|
|
|
std::cerr << "[complete] Found compilation entry" << std::endl;
|
2017-03-26 21:40:34 +00:00
|
|
|
}
|
2017-04-26 04:03:22 +00:00
|
|
|
sessions.push_back(MakeUnique<CompletionSession>(*entry, working_files));
|
2017-03-26 21:40:34 +00:00
|
|
|
return sessions[sessions.size() - 1].get();
|
2017-03-28 01:47:12 +00:00
|
|
|
}
|
2017-05-10 04:52:15 +00:00
|
|
|
|
2017-05-11 06:25:41 +00:00
|
|
|
void CompletionManager::UpdateActiveSession(const std::string& filename) {
|
|
|
|
// Drop all sessions except for |filename|.
|
2017-05-10 04:52:15 +00:00
|
|
|
for (auto& session : sessions) {
|
|
|
|
if (session->file.filename == filename)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(session->usage_lock);
|
|
|
|
session->active.reset();
|
|
|
|
session->active_index.reset();
|
|
|
|
}
|
2017-05-11 06:25:41 +00:00
|
|
|
|
|
|
|
// Reparse |filename|.
|
|
|
|
reparse_request.Set(MakeUnique<std::string>(filename));
|
2017-05-10 04:52:15 +00:00
|
|
|
}
|