From 2a638592ad6ad4f22d6f1901bb106cd63fcdb5db Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sat, 8 Sep 2018 16:00:14 -0700 Subject: [PATCH 1/5] Add diagnostics.onSave --- src/config.h | 5 ++++- src/messages/initialize.cc | 3 +-- src/messages/textDocument_didSave.cc | 2 ++ src/pipeline.cc | 11 ++++------- src/pipeline.hh | 5 ++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/config.h b/src/config.h index 565c671c1..20e76f5e0 100644 --- a/src/config.h +++ b/src/config.h @@ -154,6 +154,9 @@ struct Config { // If true, diagnostics will be reported for textDocument/didOpen. bool onOpen = true; + // If true, diagnostics will be reported for textDocument/didSave. + bool onSave = false; + bool spellChecking = true; std::vector whitelist; @@ -240,7 +243,7 @@ MAKE_REFLECT_STRUCT(Config::Completion, caseSensitivity, dropOldRequests, includeMaxPathSize, includeSuffixWhitelist, includeWhitelist); MAKE_REFLECT_STRUCT(Config::Diagnostics, blacklist, frequencyMs, onChange, - onOpen, spellChecking, whitelist) + onOpen, onSave, spellChecking, whitelist) MAKE_REFLECT_STRUCT(Config::Highlight, lsRanges, blacklist, whitelist) MAKE_REFLECT_STRUCT(Config::Index, blacklist, comments, enabled, multiVersion, multiVersionBlacklist, multiVersionWhitelist, onChange, diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 332f8c5b5..c4516ef13 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -496,8 +496,7 @@ struct Handler_Initialize : BaseMessageHandler { g_thread_id = i + 1; std::string name = "indexer" + std::to_string(i); set_thread_name(name.c_str()); - pipeline::Indexer_Main(clang_complete, diag_pub, vfs, project, - working_files); + pipeline::Indexer_Main(clang_complete, vfs, project, working_files); }) .detach(); } diff --git a/src/messages/textDocument_didSave.cc b/src/messages/textDocument_didSave.cc index 0badb21c1..08520a92e 100644 --- a/src/messages/textDocument_didSave.cc +++ b/src/messages/textDocument_didSave.cc @@ -49,6 +49,8 @@ struct Handler_TextDocumentDidSave Project::Entry entry = project->FindCompilationEntryForFile(path); pipeline::Index(entry.filename, entry.args, IndexMode::Normal); clang_complete->NotifySave(path); + if (g_config->diagnostics.onSave) + clang_complete->DiagnosticsUpdate(params.textDocument); } }; REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidSave); diff --git a/src/pipeline.cc b/src/pipeline.cc index ffab9e296..e6df6fd9b 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -165,8 +165,7 @@ std::unique_ptr RawCacheLoad(const std::string &path) { IndexFile::kMajorVersion); } -bool Indexer_Parse(CompletionManager *completion, - DiagnosticsPublisher *diag_pub, WorkingFiles *wfiles, +bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles, Project *project, VFS *vfs, const GroupMatch &matcher) { std::optional opt_request = index_request->TryPopFront(); if (!opt_request) @@ -349,13 +348,11 @@ void Init() { for_stdout = new ThreadedQueue(stdout_waiter); } -void Indexer_Main(CompletionManager *completion, - DiagnosticsPublisher *diag_pub, VFS *vfs, Project *project, - WorkingFiles *working_files) { +void Indexer_Main(CompletionManager *completion, VFS *vfs, Project *project, + WorkingFiles *wfiles) { GroupMatch matcher(g_config->index.whitelist, g_config->index.blacklist); while (true) - if (!Indexer_Parse(completion, diag_pub, working_files, project, vfs, - matcher)) + if (!Indexer_Parse(completion, wfiles, project, vfs, matcher)) indexer_waiter->Wait(index_request); } diff --git a/src/pipeline.hh b/src/pipeline.hh index da9bfc9e0..6e311d5ca 100644 --- a/src/pipeline.hh +++ b/src/pipeline.hh @@ -38,9 +38,8 @@ namespace pipeline { void Init(); void LaunchStdin(); void LaunchStdout(); -void Indexer_Main(CompletionManager *complete, - DiagnosticsPublisher *diag_pub, VFS *vfs, Project *project, - WorkingFiles *working_files); +void Indexer_Main(CompletionManager *completion, VFS *vfs, Project *project, + WorkingFiles *wfiles); void MainLoop(); void Index(const std::string &path, const std::vector &args, From cbbf1155d8eff5585706593bdfaccce59bb0acd8 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sat, 8 Sep 2018 17:49:52 -0700 Subject: [PATCH 2/5] Make CXXConversion references wider; use getTypedefNameForAnonDecl; improve CXXDestructor CXXConversion spell --- index_tests/constructors/destructor.cc | 2 +- .../inheritance/multiple_base_functions.cc | 6 +-- src/indexer.cc | 42 +++++++++++++++++-- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/index_tests/constructors/destructor.cc b/index_tests/constructors/destructor.cc index 3bb394d20..97207f379 100644 --- a/index_tests/constructors/destructor.cc +++ b/index_tests/constructors/destructor.cc @@ -56,7 +56,7 @@ void foo() { "kind": 6, "storage": 0, "declarations": [], - "spell": "4:3-4:4|15041163540773201510|2|1026|-1", + "spell": "4:3-4:7|15041163540773201510|2|1026|-1", "extent": "4:3-4:12|15041163540773201510|2|0|-1", "bases": [], "derived": [], diff --git a/index_tests/inheritance/multiple_base_functions.cc b/index_tests/inheritance/multiple_base_functions.cc index 117194751..76cd27eef 100644 --- a/index_tests/inheritance/multiple_base_functions.cc +++ b/index_tests/inheritance/multiple_base_functions.cc @@ -21,7 +21,7 @@ struct Derived : Base0, Base1 { "kind": 6, "storage": 0, "declarations": [], - "spell": "5:11-5:12|15826803741381445676|2|1090|-1", + "spell": "5:11-5:17|15826803741381445676|2|1090|-1", "extent": "5:3-5:23|15826803741381445676|2|0|-1", "bases": [], "derived": [], @@ -36,7 +36,7 @@ struct Derived : Base0, Base1 { "kind": 6, "storage": 0, "declarations": [], - "spell": "8:3-8:4|10963370434658308541|2|5186|-1", + "spell": "8:3-8:11|10963370434658308541|2|5186|-1", "extent": "8:3-8:26|10963370434658308541|2|0|-1", "bases": [], "derived": [], @@ -51,7 +51,7 @@ struct Derived : Base0, Base1 { "kind": 6, "storage": 0, "declarations": [], - "spell": "2:11-2:12|11628904180681204356|2|1090|-1", + "spell": "2:11-2:17|11628904180681204356|2|1090|-1", "extent": "2:3-2:23|11628904180681204356|2|0|-1", "bases": [], "derived": [], diff --git a/src/indexer.cc b/src/indexer.cc index 55586691e..3d9e2d67a 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -675,6 +675,21 @@ class IndexDataConsumer : public index::IndexDataConsumer { IndexParam::DeclInfo *info; Usr usr = GetUsr(D, &info); + if (is_def) + switch (OrigD->getKind()) { + case Decl::CXXConversion: // *operator* int => *operator int* + case Decl::CXXDestructor: // *~*A => *~A* + if (Loc.isFileID()) { + SourceRange R = + cast(OrigD)->getNameInfo().getSourceRange(); + if (R.getEnd().isFileID()) + loc = FromTokenRange(SM, Lang, R); + } + break; + default: + break; + } + auto do_def_decl = [&](auto *entity) { if (is_def) { entity->def.spell = GetUse(db, lid, loc, SemDC, role); @@ -704,8 +719,10 @@ class IndexDataConsumer : public index::IndexDataConsumer { return true; case SymbolKind::Func: func = &db->ToFunc(usr); - // Span one more column to the left/right if D is CXXConstructor. - if (!is_def && !is_decl && D->getKind() == Decl::CXXConstructor) + // Mark as Role::Implicit to span one more column to the left/right. + if (!is_def && !is_decl && + (D->getKind() == Decl::CXXConstructor || + D->getKind() == Decl::CXXConversion)) role = Role(role | Role::Implicit); do_def_decl(func); if (Spell != Loc) @@ -728,7 +745,7 @@ class IndexDataConsumer : public index::IndexDataConsumer { do_def_decl(type); if (Spell != Loc) AddMacroUse(db, SM, usr, SymbolKind::Type, Spell); - if (type->def.detailed_name[0] == '\0') + if (type->def.detailed_name[0] == '\0' && info->short_name.size()) SetName(OrigD, info->short_name, info->qualified, type->def); if (is_def || is_decl) { const Decl *DC = cast(SemDC); @@ -885,6 +902,25 @@ class IndexDataConsumer : public index::IndexDataConsumer { // spec has no Union, use Class type->def.kind = RD->getTagKind() == TTK_Struct ? lsSymbolKind::Struct : lsSymbolKind::Class; + if (type->def.detailed_name[0] == '\0' && info->short_name.empty()) { + if (TypedefNameDecl *TD = RD->getTypedefNameForAnonDecl()) { + StringRef Name = TD->getName(); + StringRef Tag; + switch (RD->getTagKind()) { + case TTK_Struct: Tag = "struct "; break; + case TTK_Interface: Tag = "__interface "; break; + case TTK_Union: Tag = "union "; break; + case TTK_Class: Tag = "class "; break; + case TTK_Enum: Tag = "enum "; break; + } + std::string name = ("anon " + Tag + Name).str(); + type->def.detailed_name = Intern(name); + type->def.short_name_size = name.size(); + } else { + // e.g. "struct {}" + SetName(OrigD, "", "", type->def); + } + } if (is_def) { SmallVector, 2> Stack{{RD, 0}}; llvm::DenseSet Seen; From 4d76970614917746d3e7866a698a8695d3847810 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sun, 9 Sep 2018 23:46:13 -0700 Subject: [PATCH 3/5] Misc --- src/message_handler.cc | 4 ++-- src/messages/workspace_didChangeWatchedFiles.cc | 4 ++-- src/pipeline.cc | 12 +++++++++--- src/project.cc | 16 ++++++++++++---- src/project.h | 2 +- src/query.cc | 17 +++++++++++------ 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/message_handler.cc b/src/message_handler.cc index f4d6aae03..ab6d937b6 100644 --- a/src/message_handler.cc +++ b/src/message_handler.cc @@ -160,8 +160,8 @@ bool FindFileOrFail(DB *db, Project *project, std::optional id, bool indexing; { std::lock_guard lock(project->mutex_); - indexing = project->absolute_path_to_entry_index_.find(absolute_path) != - project->absolute_path_to_entry_index_.end(); + indexing = project->path_to_entry_index.find(absolute_path) != + project->path_to_entry_index.end(); } if (indexing) LOG_S(INFO) << "\"" << absolute_path << "\" is being indexed."; diff --git a/src/messages/workspace_didChangeWatchedFiles.cc b/src/messages/workspace_didChangeWatchedFiles.cc index 5d8728597..8ba464083 100644 --- a/src/messages/workspace_didChangeWatchedFiles.cc +++ b/src/messages/workspace_didChangeWatchedFiles.cc @@ -57,8 +57,8 @@ struct Handler_WorkspaceDidChangeWatchedFiles Project::Entry entry; { std::lock_guard lock(project->mutex_); - auto it = project->absolute_path_to_entry_index_.find(path); - if (it == project->absolute_path_to_entry_index_.end()) + auto it = project->path_to_entry_index.find(path); + if (it == project->path_to_entry_index.end()) continue; entry = project->entries[it->second]; } diff --git a/src/pipeline.cc b/src/pipeline.cc index e6df6fd9b..8fea4f969 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -32,6 +32,8 @@ limitations under the License. using namespace llvm; #include +#include +#include #include #ifndef _WIN32 #include @@ -99,6 +101,7 @@ struct InMemoryIndexFile { std::string content; IndexFile index; }; +std::shared_mutex g_index_mutex; std::unordered_map g_index; bool CacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path, @@ -147,6 +150,7 @@ std::string GetCachePath(const std::string &source_file) { std::unique_ptr RawCacheLoad(const std::string &path) { if (g_config->cacheDirectory.empty()) { + std::shared_lock lock(g_index_mutex); auto it = g_index.find(path); if (it == g_index.end()) return nullptr; @@ -189,8 +193,8 @@ bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles, Project::Entry entry; { std::lock_guard lock(project->mutex_); - auto it = project->absolute_path_to_entry_index_.find(request.path); - if (it != project->absolute_path_to_entry_index_.end()) + auto it = project->path_to_entry_index.find(request.path); + if (it != project->path_to_entry_index.end()) entry = project->entries[it->second]; else { entry.filename = request.path; @@ -307,6 +311,7 @@ bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles, LOG_IF_S(INFO, loud) << "store index for " << path << " (delta: " << !!prev << ")"; if (g_config->cacheDirectory.empty()) { + std::unique_lock lock(g_index_mutex); auto it = g_index.insert_or_assign( path, InMemoryIndexFile{curr->file_contents, *curr}); std::string().swap(it.first->second.index.file_contents); @@ -321,7 +326,7 @@ bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles, if (entry.id >= 0) { std::lock_guard lock(project->mutex_); for (auto &dep : curr->dependencies) - project->absolute_path_to_entry_index_[dep.first()] = entry.id; + project->path_to_entry_index[dep.first()] = entry.id; } // Build delta update. @@ -548,6 +553,7 @@ std::optional LastWriteTime(const std::string &path) { std::optional LoadIndexedContent(const std::string &path) { if (g_config->cacheDirectory.empty()) { + std::shared_lock lock(g_index_mutex); auto it = g_index.find(path); if (it == g_index.end()) return {}; diff --git a/src/project.cc b/src/project.cc index 173ec597d..3cd4aa524 100644 --- a/src/project.cc +++ b/src/project.cc @@ -377,13 +377,21 @@ void Project::Load(const std::string &root_directory) { EnsureEndsInSlash(path); LOG_S(INFO) << "angle_include_dir: " << path; } + + // Setup project entries. + std::lock_guard lock(mutex_); + path_to_entry_index.reserve(entries.size()); + for (size_t i = 0; i < entries.size(); ++i) { + entries[i].id = i; + path_to_entry_index[entries[i].filename] = i; + } } void Project::SetFlagsForFile(const std::vector &flags, const std::string &path) { std::lock_guard lock(mutex_); - auto it = absolute_path_to_entry_index_.find(path); - if (it != absolute_path_to_entry_index_.end()) { + auto it = path_to_entry_index.find(path); + if (it != path_to_entry_index.end()) { // The entry already exists in the project, just set the flags. this->entries[it->second].args = flags; } else { @@ -400,8 +408,8 @@ Project::Entry Project::FindCompilationEntryForFile(const std::string &filename) { { std::lock_guard lock(mutex_); - auto it = absolute_path_to_entry_index_.find(filename); - if (it != absolute_path_to_entry_index_.end()) + auto it = path_to_entry_index.find(filename); + if (it != path_to_entry_index.end()) return entries[it->second]; } diff --git a/src/project.h b/src/project.h index 3baa545ee..b431d1d48 100644 --- a/src/project.h +++ b/src/project.h @@ -43,7 +43,7 @@ struct Project { std::vector entries; std::mutex mutex_; - std::unordered_map absolute_path_to_entry_index_; + std::unordered_map path_to_entry_index; // Loads a project for the given |directory|. // diff --git a/src/query.cc b/src/query.cc index be657184e..6ec741436 100644 --- a/src/query.cc +++ b/src/query.cc @@ -227,16 +227,21 @@ void DB::ApplyIndexUpdate(IndexUpdate *u) { SymbolKind kind, Use &use, int delta, int k = 1) { use.file_id = use.file_id == -1 ? u->file_id : lid2fid.find(use.file_id)->second; + SymbolRef sym{{use.range, usr, kind, use.role}}; if (k & 1) { - int &v = - files[use.file_id] - .symbol2refcnt[SymbolRef{{use.range, usr, kind, use.role}}]; + int &v = files[use.file_id].symbol2refcnt[sym]; v += delta; assert(v >= 0); + if (!v) + files[use.file_id].symbol2refcnt.erase(sym); + } + if (k & 2) { + int &v = files[use.file_id].outline2refcnt[sym]; + v += delta; + assert(v >= 0); + if (!v) + files[use.file_id].outline2refcnt.erase(sym); } - if (k & 2) - files[use.file_id] - .outline2refcnt[SymbolRef{{use.range, usr, kind, use.role}}] += delta; }; auto RefDecl = [&](std::unordered_map &lid2fid, Usr usr, SymbolKind kind, DeclRef &dr, int delta) { From 4c499c93e2d48cd94bcde34be0da6f668aa60ec2 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Mon, 10 Sep 2018 22:37:01 -0700 Subject: [PATCH 4/5] Revamp completion and signatureHelp --- src/clang_complete.cc | 345 +---------------- src/clang_complete.hh | 14 +- src/message_handler.h | 3 +- src/messages/textDocument_completion.cc | 409 +++++++++++++++++---- src/messages/textDocument_signatureHelp.cc | 258 +++++++------ src/pipeline.cc | 10 +- 6 files changed, 504 insertions(+), 535 deletions(-) diff --git a/src/clang_complete.cc b/src/clang_complete.cc index f6a848201..67986f245 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -23,6 +23,7 @@ limitations under the License. #include #include #include +#include #include #include #include @@ -42,268 +43,6 @@ std::string StripFileType(const std::string &path) { return Ret.str(); } -unsigned GetCompletionPriority(const CodeCompletionString &CCS, - CXCursorKind result_kind, - const std::optional &typedText) { - unsigned priority = CCS.getPriority(); - if (CCS.getAvailability() != CXAvailability_Available || - result_kind == CXCursor_Destructor || - result_kind == CXCursor_ConversionFunction || - (result_kind == CXCursor_CXXMethod && typedText && - StartsWith(*typedText, "operator"))) - priority *= 100; - return priority; -} - -lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) { - switch (cursor_kind) { - case CXCursor_UnexposedDecl: - return lsCompletionItemKind::Text; - - case CXCursor_StructDecl: - case CXCursor_UnionDecl: - return lsCompletionItemKind::Struct; - case CXCursor_ClassDecl: - return lsCompletionItemKind::Class; - case CXCursor_EnumDecl: - return lsCompletionItemKind::Enum; - case CXCursor_FieldDecl: - return lsCompletionItemKind::Field; - case CXCursor_EnumConstantDecl: - return lsCompletionItemKind::EnumMember; - case CXCursor_FunctionDecl: - return lsCompletionItemKind::Function; - case CXCursor_VarDecl: - case CXCursor_ParmDecl: - return lsCompletionItemKind::Variable; - case CXCursor_ObjCInterfaceDecl: - return lsCompletionItemKind::Interface; - - case CXCursor_ObjCInstanceMethodDecl: - case CXCursor_CXXMethod: - case CXCursor_ObjCClassMethodDecl: - return lsCompletionItemKind::Method; - - case CXCursor_FunctionTemplate: - return lsCompletionItemKind::Function; - - case CXCursor_Constructor: - case CXCursor_Destructor: - case CXCursor_ConversionFunction: - return lsCompletionItemKind::Constructor; - - case CXCursor_ObjCIvarDecl: - return lsCompletionItemKind::Variable; - - case CXCursor_ClassTemplate: - case CXCursor_ClassTemplatePartialSpecialization: - case CXCursor_UsingDeclaration: - case CXCursor_TypedefDecl: - case CXCursor_TypeAliasDecl: - case CXCursor_TypeAliasTemplateDecl: - case CXCursor_ObjCCategoryDecl: - case CXCursor_ObjCProtocolDecl: - case CXCursor_ObjCImplementationDecl: - case CXCursor_ObjCCategoryImplDecl: - return lsCompletionItemKind::Class; - - case CXCursor_ObjCPropertyDecl: - return lsCompletionItemKind::Property; - - case CXCursor_MacroInstantiation: - case CXCursor_MacroDefinition: - return lsCompletionItemKind::Interface; - - case CXCursor_Namespace: - case CXCursor_NamespaceAlias: - case CXCursor_NamespaceRef: - return lsCompletionItemKind::Module; - - case CXCursor_MemberRef: - case CXCursor_TypeRef: - case CXCursor_ObjCSuperClassRef: - case CXCursor_ObjCProtocolRef: - case CXCursor_ObjCClassRef: - return lsCompletionItemKind::Reference; - - // return lsCompletionItemKind::Unit; - // return lsCompletionItemKind::Value; - // return lsCompletionItemKind::Keyword; - // return lsCompletionItemKind::Snippet; - // return lsCompletionItemKind::Color; - // return lsCompletionItemKind::File; - - case CXCursor_NotImplemented: - case CXCursor_OverloadCandidate: - return lsCompletionItemKind::Text; - - case CXCursor_TemplateTypeParameter: - case CXCursor_TemplateTemplateParameter: - return lsCompletionItemKind::TypeParameter; - - default: - LOG_S(WARNING) << "Unhandled completion kind " << cursor_kind; - return lsCompletionItemKind::Text; - } -} - -void BuildCompletionItemTexts(std::vector &out, - CodeCompletionString &CCS, - bool include_snippets) { - assert(!out.empty()); - auto out_first = out.size() - 1; - - std::string result_type; - - for (unsigned i = 0, num_chunks = CCS.size(); i < num_chunks; ++i) { - const CodeCompletionString::Chunk &Chunk = CCS[i]; - CodeCompletionString::ChunkKind Kind = Chunk.Kind; - std::string text; - switch (Kind) { - case CodeCompletionString::CK_TypedText: - case CodeCompletionString::CK_Text: - case CodeCompletionString::CK_Placeholder: - case CodeCompletionString::CK_Informative: - if (Chunk.Text) - text = Chunk.Text; - for (auto i = out_first; i < out.size(); i++) { - // first TypedText is used for filtering - if (Kind == CodeCompletionString::CK_TypedText && !out[i].filterText) - out[i].filterText = text; - if (Kind == CodeCompletionString::CK_Placeholder) - out[i].parameters_.push_back(text); - } - break; - case CodeCompletionString::CK_ResultType: - if (Chunk.Text) - result_type = Chunk.Text; - continue; - case CodeCompletionString::CK_CurrentParameter: - // We have our own parsing logic for active parameter. This doesn't seem - // to be very reliable. - continue; - case CodeCompletionString::CK_Optional: { - // duplicate last element, the recursive call will complete it - out.push_back(out.back()); - BuildCompletionItemTexts(out, *Chunk.Optional, include_snippets); - continue; - } - // clang-format off - case CodeCompletionString::CK_LeftParen: text = '('; break; - case CodeCompletionString::CK_RightParen: text = ')'; break; - case CodeCompletionString::CK_LeftBracket: text = '['; break; - case CodeCompletionString::CK_RightBracket: text = ']'; break; - case CodeCompletionString::CK_LeftBrace: text = '{'; break; - case CodeCompletionString::CK_RightBrace: text = '}'; break; - case CodeCompletionString::CK_LeftAngle: text = '<'; break; - case CodeCompletionString::CK_RightAngle: text = '>'; break; - case CodeCompletionString::CK_Comma: text = ", "; break; - case CodeCompletionString::CK_Colon: text = ':'; break; - case CodeCompletionString::CK_SemiColon: text = ';'; break; - case CodeCompletionString::CK_Equal: text = '='; break; - case CodeCompletionString::CK_HorizontalSpace: text = ' '; break; - case CodeCompletionString::CK_VerticalSpace: text = ' '; break; - // clang-format on - } - - if (Kind != CodeCompletionString::CK_Informative) - for (auto i = out_first; i < out.size(); ++i) { - out[i].label += text; - if (!include_snippets && !out[i].parameters_.empty()) - continue; - - if (Kind == CodeCompletionString::CK_Placeholder) { - out[i].insertText += "${" + - std::to_string(out[i].parameters_.size()) + ":" + - text + "}"; - out[i].insertTextFormat = lsInsertTextFormat::Snippet; - } else { - out[i].insertText += text; - } - } - } - - if (result_type.size()) - for (auto i = out_first; i < out.size(); ++i) { - // ' : ' for variables, - // ' -> ' (trailing return type-like) for functions - out[i].label += (out[i].label == out[i].filterText ? " : " : " -> "); - out[i].label += result_type; - } -} - -// |do_insert|: if |!do_insert|, do not append strings to |insert| after -// a placeholder. -void BuildDetailString(const CodeCompletionString &CCS, lsCompletionItem &item, - bool &do_insert, std::vector *parameters, - bool include_snippets, int &angle_stack) { - for (unsigned i = 0, num_chunks = CCS.size(); i < num_chunks; ++i) { - const CodeCompletionString::Chunk &Chunk = CCS[i]; - CodeCompletionString::ChunkKind Kind = Chunk.Kind; - const char *text = nullptr; - switch (Kind) { - case CodeCompletionString::CK_TypedText: - item.label = Chunk.Text; - [[fallthrough]]; - case CodeCompletionString::CK_Text: - item.detail += Chunk.Text; - if (do_insert) - item.insertText += Chunk.Text; - break; - case CodeCompletionString::CK_Placeholder: { - parameters->push_back(Chunk.Text); - item.detail += Chunk.Text; - // Add parameter declarations as snippets if enabled - if (include_snippets) { - item.insertText += - "${" + std::to_string(parameters->size()) + ":" + Chunk.Text + "}"; - item.insertTextFormat = lsInsertTextFormat::Snippet; - } else - do_insert = false; - break; - } - case CodeCompletionString::CK_Informative: - item.detail += Chunk.Text; - break; - case CodeCompletionString::CK_Optional: { - // Do not add text to insert string if we're in angle brackets. - bool should_insert = do_insert && angle_stack == 0; - BuildDetailString(*Chunk.Optional, item, should_insert, parameters, - include_snippets, angle_stack); - break; - } - case CodeCompletionString::CK_ResultType: - item.detail = Chunk.Text + item.detail + " "; - break; - case CodeCompletionString::CK_CurrentParameter: - // We have our own parsing logic for active parameter. This doesn't seem - // to be very reliable. - break; - // clang-format off - case CodeCompletionString::CK_LeftParen: text = "("; break; - case CodeCompletionString::CK_RightParen: text = ")"; break; - case CodeCompletionString::CK_LeftBracket: text = "["; break; - case CodeCompletionString::CK_RightBracket: text = "]"; break; - case CodeCompletionString::CK_LeftBrace: text = "{"; break; - case CodeCompletionString::CK_RightBrace: text = "}"; break; - case CodeCompletionString::CK_LeftAngle: text = "<"; angle_stack++; break; - case CodeCompletionString::CK_RightAngle: text = ">"; angle_stack--; break; - case CodeCompletionString::CK_Comma: text = ", "; break; - case CodeCompletionString::CK_Colon: text = ":"; break; - case CodeCompletionString::CK_SemiColon: text = ";"; break; - case CodeCompletionString::CK_Equal: text = "="; break; - case CodeCompletionString::CK_HorizontalSpace: - case CodeCompletionString::CK_VerticalSpace: text = " "; break; - // clang-format on - } - if (text) { - item.detail += text; - if (do_insert && include_snippets) - item.insertText += text; - } - } -} - bool LocationInRange(SourceLocation L, CharSourceRange R, const SourceManager &M) { assert(R.isCharRange()); @@ -336,70 +75,6 @@ CharSourceRange DiagnosticRange(const clang::Diagnostic &D, const LangOptions &L } -class CaptureCompletionResults : public CodeCompleteConsumer { - std::shared_ptr Alloc; - CodeCompletionTUInfo CCTUInfo; - -public: - std::vector ls_items; - - CaptureCompletionResults(const CodeCompleteOptions &Opts) - : CodeCompleteConsumer(Opts, false), - Alloc(std::make_shared()), - CCTUInfo(Alloc) {} - - void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, - CodeCompletionResult *Results, - unsigned NumResults) override { - ls_items.reserve(NumResults); - for (unsigned i = 0; i != NumResults; i++) { - auto &R = Results[i]; - if (R.Availability == CXAvailability_NotAccessible || - R.Availability == CXAvailability_NotAvailable) - continue; - CodeCompletionString *CCS = R.CreateCodeCompletionString( - S, Context, getAllocator(), getCodeCompletionTUInfo(), - includeBriefComments()); - lsCompletionItem ls_item; - ls_item.kind = GetCompletionKind(R.CursorKind); - if (const char *brief = CCS->getBriefComment()) - ls_item.documentation = brief; - - // label/detail/filterText/insertText/priority - if (g_config->completion.detailedLabel) { - ls_item.detail = CCS->getParentContextName().str(); - - size_t first_idx = ls_items.size(); - ls_items.push_back(ls_item); - BuildCompletionItemTexts(ls_items, *CCS, - g_config->client.snippetSupport); - - for (size_t j = first_idx; j < ls_items.size(); j++) { - if (g_config->client.snippetSupport && - ls_items[j].insertTextFormat == lsInsertTextFormat::Snippet) - ls_items[j].insertText += "$0"; - ls_items[j].priority_ = GetCompletionPriority( - *CCS, Results[i].CursorKind, ls_items[j].filterText); - } - } else { - bool do_insert = true; - int angle_stack = 0; - BuildDetailString(*CCS, ls_item, do_insert, &ls_item.parameters_, - g_config->client.snippetSupport, angle_stack); - if (g_config->client.snippetSupport && - ls_item.insertTextFormat == lsInsertTextFormat::Snippet) - ls_item.insertText += "$0"; - ls_item.priority_ = - GetCompletionPriority(*CCS, Results[i].CursorKind, ls_item.label); - ls_items.push_back(ls_item); - } - } - } - - CodeCompletionAllocator &getAllocator() override { return *Alloc; } - - CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } -}; class StoreDiags : public DiagnosticConsumer { const LangOptions *LangOpts; @@ -560,6 +235,8 @@ void CompletionMain(CompletionManager *completion_manager) { while (g_config->completion.dropOldRequests && !completion_manager->completion_request_.IsEmpty()) { completion_manager->on_dropped_(request->id); + request->Consumer.reset(); + request->on_complete(nullptr); request = completion_manager->completion_request_.Dequeue(); } @@ -573,14 +250,8 @@ void CompletionMain(CompletionManager *completion_manager) { BuildCompilerInvocation(session->file.args, session->FS); if (!CI) continue; - clang::CodeCompleteOptions CCOpts; - CCOpts.IncludeBriefComments = true; -#if LLVM_VERSION_MAJOR >= 7 - CCOpts.IncludeFixIts = true; -#endif - CCOpts.IncludeCodePatterns = true; auto &FOpts = CI->getFrontendOpts(); - FOpts.CodeCompleteOpts = CCOpts; + FOpts.CodeCompleteOpts = request->CCOpts; FOpts.CodeCompletionAt.FileName = session->file.filename; FOpts.CodeCompletionAt.Line = request->position.line + 1; FOpts.CodeCompletionAt.Column = request->position.character + 1; @@ -595,14 +266,13 @@ void CompletionMain(CompletionManager *completion_manager) { if (!Clang) continue; - auto Consumer = new CaptureCompletionResults(CCOpts); - Clang->setCodeCompletionConsumer(Consumer); + Clang->setCodeCompletionConsumer(request->Consumer.release()); if (!Parse(*Clang)) continue; for (auto &Buf : Bufs) Buf.release(); - request->on_complete(Consumer->ls_items, false /*is_cached_result*/); + request->on_complete(&Clang->getCodeCompletionConsumer()); } } @@ -729,9 +399,6 @@ void CompletionManager::CodeComplete( const lsRequestId &id, const lsTextDocumentPositionParams &completion_location, const OnComplete &on_complete) { - completion_request_.PushBack(std::make_unique( - id, completion_location.textDocument, completion_location.position, - on_complete)); } void CompletionManager::DiagnosticsUpdate( diff --git a/src/clang_complete.hh b/src/clang_complete.hh index 94e9fc36d..6299487f4 100644 --- a/src/clang_complete.hh +++ b/src/clang_complete.hh @@ -25,6 +25,7 @@ limitations under the License. #include #include +#include #include #include @@ -79,8 +80,9 @@ struct CompletionSession struct CompletionManager { using OnDiagnostic = std::function diagnostics)>; - using OnComplete = std::function &results, bool is_cached_result)>; + // If OptConsumer is nullptr, the request has been cancelled. + using OnComplete = + std::function; using OnDropped = std::function; struct PreloadRequest { @@ -93,13 +95,19 @@ struct CompletionManager { struct CompletionRequest { CompletionRequest(const lsRequestId &id, const lsTextDocumentIdentifier &document, - const lsPosition &position, const OnComplete &on_complete) + const lsPosition &position, + std::unique_ptr Consumer, + clang::CodeCompleteOptions CCOpts, + const OnComplete &on_complete) : id(id), document(document), position(position), + Consumer(std::move(Consumer)), CCOpts(CCOpts), on_complete(on_complete) {} lsRequestId id; lsTextDocumentIdentifier document; lsPosition position; + std::unique_ptr Consumer; + clang::CodeCompleteOptions CCOpts; OnComplete on_complete; }; struct DiagnosticRequest { diff --git a/src/message_handler.h b/src/message_handler.h index 3d314222d..a19ddf84c 100644 --- a/src/message_handler.h +++ b/src/message_handler.h @@ -121,8 +121,7 @@ struct MessageHandler { WorkingFiles *working_files = nullptr; CompletionManager *clang_complete = nullptr; IncludeComplete *include_complete = nullptr; - CodeCompleteCache *global_code_complete_cache = nullptr; - CodeCompleteCache *non_global_code_complete_cache = nullptr; + CodeCompleteCache *completion_cache = nullptr; CodeCompleteCache *signature_cache = nullptr; virtual MethodType GetMethodType() const = 0; diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc index 78383969a..075a100d5 100644 --- a/src/messages/textDocument_completion.cc +++ b/src/messages/textDocument_completion.cc @@ -16,12 +16,16 @@ limitations under the License. #include "clang_complete.hh" #include "fuzzy_match.h" #include "include_complete.h" +#include "log.hh" #include "message_handler.h" #include "pipeline.hh" #include "working_files.h" using namespace ccls; +#include +#include #include +using namespace clang; using namespace llvm; #include @@ -261,24 +265,320 @@ bool IsOpenParenOrAngle(const std::vector &lines, return false; } -struct Handler_TextDocumentCompletion : MessageHandler { +unsigned GetCompletionPriority(const CodeCompletionString &CCS, + CXCursorKind result_kind, + const std::optional &typedText) { + unsigned priority = CCS.getPriority(); + if (CCS.getAvailability() != CXAvailability_Available || + result_kind == CXCursor_Destructor || + result_kind == CXCursor_ConversionFunction || + (result_kind == CXCursor_CXXMethod && typedText && + StartsWith(*typedText, "operator"))) + priority *= 100; + return priority; +} + +lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) { + switch (cursor_kind) { + case CXCursor_UnexposedDecl: + return lsCompletionItemKind::Text; + + case CXCursor_StructDecl: + case CXCursor_UnionDecl: + return lsCompletionItemKind::Struct; + case CXCursor_ClassDecl: + return lsCompletionItemKind::Class; + case CXCursor_EnumDecl: + return lsCompletionItemKind::Enum; + case CXCursor_FieldDecl: + return lsCompletionItemKind::Field; + case CXCursor_EnumConstantDecl: + return lsCompletionItemKind::EnumMember; + case CXCursor_FunctionDecl: + return lsCompletionItemKind::Function; + case CXCursor_VarDecl: + case CXCursor_ParmDecl: + return lsCompletionItemKind::Variable; + case CXCursor_ObjCInterfaceDecl: + return lsCompletionItemKind::Interface; + + case CXCursor_ObjCInstanceMethodDecl: + case CXCursor_CXXMethod: + case CXCursor_ObjCClassMethodDecl: + return lsCompletionItemKind::Method; + + case CXCursor_FunctionTemplate: + return lsCompletionItemKind::Function; + + case CXCursor_Constructor: + case CXCursor_Destructor: + case CXCursor_ConversionFunction: + return lsCompletionItemKind::Constructor; + + case CXCursor_ObjCIvarDecl: + return lsCompletionItemKind::Variable; + + case CXCursor_ClassTemplate: + case CXCursor_ClassTemplatePartialSpecialization: + case CXCursor_UsingDeclaration: + case CXCursor_TypedefDecl: + case CXCursor_TypeAliasDecl: + case CXCursor_TypeAliasTemplateDecl: + case CXCursor_ObjCCategoryDecl: + case CXCursor_ObjCProtocolDecl: + case CXCursor_ObjCImplementationDecl: + case CXCursor_ObjCCategoryImplDecl: + return lsCompletionItemKind::Class; + + case CXCursor_ObjCPropertyDecl: + return lsCompletionItemKind::Property; + + case CXCursor_MacroInstantiation: + case CXCursor_MacroDefinition: + return lsCompletionItemKind::Interface; + + case CXCursor_Namespace: + case CXCursor_NamespaceAlias: + case CXCursor_NamespaceRef: + return lsCompletionItemKind::Module; + + case CXCursor_MemberRef: + case CXCursor_TypeRef: + case CXCursor_ObjCSuperClassRef: + case CXCursor_ObjCProtocolRef: + case CXCursor_ObjCClassRef: + return lsCompletionItemKind::Reference; + + // return lsCompletionItemKind::Unit; + // return lsCompletionItemKind::Value; + // return lsCompletionItemKind::Keyword; + // return lsCompletionItemKind::Snippet; + // return lsCompletionItemKind::Color; + // return lsCompletionItemKind::File; + + case CXCursor_NotImplemented: + case CXCursor_OverloadCandidate: + return lsCompletionItemKind::Text; + + case CXCursor_TemplateTypeParameter: + case CXCursor_TemplateTemplateParameter: + return lsCompletionItemKind::TypeParameter; + + default: + LOG_S(WARNING) << "Unhandled completion kind " << cursor_kind; + return lsCompletionItemKind::Text; + } +} + +void BuildCompletionItemTexts(std::vector &out, + CodeCompletionString &CCS, + bool include_snippets) { + assert(!out.empty()); + auto out_first = out.size() - 1; + + std::string result_type; + + for (unsigned i = 0, num_chunks = CCS.size(); i < num_chunks; ++i) { + const CodeCompletionString::Chunk &Chunk = CCS[i]; + CodeCompletionString::ChunkKind Kind = Chunk.Kind; + std::string text; + switch (Kind) { + case CodeCompletionString::CK_TypedText: + case CodeCompletionString::CK_Text: + case CodeCompletionString::CK_Placeholder: + case CodeCompletionString::CK_Informative: + if (Chunk.Text) + text = Chunk.Text; + for (auto i = out_first; i < out.size(); i++) { + // first TypedText is used for filtering + if (Kind == CodeCompletionString::CK_TypedText && !out[i].filterText) + out[i].filterText = text; + if (Kind == CodeCompletionString::CK_Placeholder) + out[i].parameters_.push_back(text); + } + break; + case CodeCompletionString::CK_ResultType: + if (Chunk.Text) + result_type = Chunk.Text; + continue; + case CodeCompletionString::CK_CurrentParameter: + // We have our own parsing logic for active parameter. This doesn't seem + // to be very reliable. + continue; + case CodeCompletionString::CK_Optional: { + // duplicate last element, the recursive call will complete it + out.push_back(out.back()); + BuildCompletionItemTexts(out, *Chunk.Optional, include_snippets); + continue; + } + default: + text = Chunk.Text; + break; + } + + if (Kind != CodeCompletionString::CK_Informative) + for (auto i = out_first; i < out.size(); ++i) { + out[i].label += text; + if (!include_snippets && !out[i].parameters_.empty()) + continue; + + if (Kind == CodeCompletionString::CK_Placeholder) { + out[i].insertText += "${" + + std::to_string(out[i].parameters_.size()) + ":" + + text + "}"; + out[i].insertTextFormat = lsInsertTextFormat::Snippet; + } else { + out[i].insertText += text; + } + } + } + + if (result_type.size()) + for (auto i = out_first; i < out.size(); ++i) { + // ' : ' for variables, + // ' -> ' (trailing return type-like) for functions + out[i].label += (out[i].label == out[i].filterText ? " : " : " -> "); + out[i].label += result_type; + } +} + +// |do_insert|: if |!do_insert|, do not append strings to |insert| after +// a placeholder. +void BuildDetailString(const CodeCompletionString &CCS, lsCompletionItem &item, + bool &do_insert, std::vector *parameters, + bool include_snippets, int &angle_stack) { + for (unsigned i = 0, num_chunks = CCS.size(); i < num_chunks; ++i) { + const CodeCompletionString::Chunk &Chunk = CCS[i]; + CodeCompletionString::ChunkKind Kind = Chunk.Kind; + const char *text = nullptr; + switch (Kind) { + case CodeCompletionString::CK_TypedText: + item.label = Chunk.Text; + [[fallthrough]]; + case CodeCompletionString::CK_Text: + item.detail += Chunk.Text; + if (do_insert) + item.insertText += Chunk.Text; + break; + case CodeCompletionString::CK_Placeholder: { + parameters->push_back(Chunk.Text); + item.detail += Chunk.Text; + // Add parameter declarations as snippets if enabled + if (include_snippets) { + item.insertText += + "${" + std::to_string(parameters->size()) + ":" + Chunk.Text + "}"; + item.insertTextFormat = lsInsertTextFormat::Snippet; + } else + do_insert = false; + break; + } + case CodeCompletionString::CK_Informative: + item.detail += Chunk.Text; + break; + case CodeCompletionString::CK_Optional: { + // Do not add text to insert string if we're in angle brackets. + bool should_insert = do_insert && angle_stack == 0; + BuildDetailString(*Chunk.Optional, item, should_insert, parameters, + include_snippets, angle_stack); + break; + } + case CodeCompletionString::CK_ResultType: + item.detail = Chunk.Text + item.detail + " "; + break; + case CodeCompletionString::CK_CurrentParameter: + // We have our own parsing logic for active parameter. This doesn't seem + // to be very reliable. + break; + default: + text = Chunk.Text; + break; + } + if (text) { + item.detail += text; + if (do_insert && include_snippets) + item.insertText += text; + } + } +} + +class CompletionConsumer : public CodeCompleteConsumer { + std::shared_ptr Alloc; + CodeCompletionTUInfo CCTUInfo; + +public: + bool from_cache; + std::vector ls_items; + + CompletionConsumer(const CodeCompleteOptions &Opts, bool from_cache) + : CodeCompleteConsumer(Opts, false), + Alloc(std::make_shared()), + CCTUInfo(Alloc), from_cache(from_cache) {} + + void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, + CodeCompletionResult *Results, + unsigned NumResults) override { + ls_items.reserve(NumResults); + for (unsigned i = 0; i != NumResults; i++) { + auto &R = Results[i]; + if (R.Availability == CXAvailability_NotAccessible || + R.Availability == CXAvailability_NotAvailable) + continue; + CodeCompletionString *CCS = R.CreateCodeCompletionString( + S, Context, getAllocator(), getCodeCompletionTUInfo(), + includeBriefComments()); + lsCompletionItem ls_item; + ls_item.kind = GetCompletionKind(R.CursorKind); + if (const char *brief = CCS->getBriefComment()) + ls_item.documentation = brief; + + // label/detail/filterText/insertText/priority + if (g_config->completion.detailedLabel) { + ls_item.detail = CCS->getParentContextName().str(); + + size_t first_idx = ls_items.size(); + ls_items.push_back(ls_item); + BuildCompletionItemTexts(ls_items, *CCS, + g_config->client.snippetSupport); + + for (size_t j = first_idx; j < ls_items.size(); j++) { + if (g_config->client.snippetSupport && + ls_items[j].insertTextFormat == lsInsertTextFormat::Snippet) + ls_items[j].insertText += "$0"; + ls_items[j].priority_ = GetCompletionPriority( + *CCS, Results[i].CursorKind, ls_items[j].filterText); + } + } else { + bool do_insert = true; + int angle_stack = 0; + BuildDetailString(*CCS, ls_item, do_insert, &ls_item.parameters_, + g_config->client.snippetSupport, angle_stack); + if (g_config->client.snippetSupport && + ls_item.insertTextFormat == lsInsertTextFormat::Snippet) + ls_item.insertText += "$0"; + ls_item.priority_ = + GetCompletionPriority(*CCS, Results[i].CursorKind, ls_item.label); + ls_items.push_back(ls_item); + } + } + } + + CodeCompletionAllocator &getAllocator() override { return *Alloc; } + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } +}; + +struct Handler_TextDocumentCompletion + : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } - void Run(std::unique_ptr message) override { - auto request = std::shared_ptr( - static_cast(message.release())); + void Run(In_TextDocumentComplete *request) override { auto ¶ms = request->params; - - auto write_empty_result = [request]() { - Out_TextDocumentComplete out; - out.id = request->id; - pipeline::WriteStdout(kMethodType, out); - }; + Out_TextDocumentComplete out; + out.id = request->id; std::string path = params.textDocument.uri.GetPath(); WorkingFile *file = working_files->GetFileByFilename(path); if (!file) { - write_empty_result(); + pipeline::WriteStdout(kMethodType, out); return; } @@ -323,7 +623,7 @@ struct Handler_TextDocumentCompletion : MessageHandler { } if (did_fail_check) { - write_empty_result(); + pipeline::WriteStdout(kMethodType, out); return; } } @@ -380,69 +680,46 @@ struct Handler_TextDocumentCompletion : MessageHandler { pipeline::WriteStdout(kMethodType, out); } else { - CompletionManager::OnComplete callback = std::bind( - [this, request, params, is_global_completion, existing_completion, - has_open_paren](const std::vector &results, - bool is_cached_result) { + CompletionManager::OnComplete callback = + [this, existing_completion, has_open_paren, id = request->id, + params = request->params](CodeCompleteConsumer *OptConsumer) { + if (!OptConsumer) + return; + auto *Consumer = static_cast(OptConsumer); Out_TextDocumentComplete out; - out.id = request->id; - out.result.items = results; + out.id = id; + out.result.items = Consumer->ls_items; - // Emit completion results. FilterAndSortCompletionResponse(&out, existing_completion, has_open_paren); pipeline::WriteStdout(kMethodType, out); - - // Cache completion results. - if (!is_cached_result) { + if (!Consumer->from_cache) { std::string path = params.textDocument.uri.GetPath(); - if (is_global_completion) { - global_code_complete_cache->WithLock([&]() { - global_code_complete_cache->cached_path_ = path; - global_code_complete_cache->cached_results_ = results; - }); - } else { - non_global_code_complete_cache->WithLock([&]() { - non_global_code_complete_cache->cached_path_ = path; - non_global_code_complete_cache->cached_completion_position_ = - params.position; - non_global_code_complete_cache->cached_results_ = results; - }); - } - } - }, - std::placeholders::_1, std::placeholders::_2); - - bool is_cache_match = false; - global_code_complete_cache->WithLock([&]() { - is_cache_match = is_global_completion && - global_code_complete_cache->cached_path_ == path && - !global_code_complete_cache->cached_results_.empty(); - }); - if (is_cache_match) { - CompletionManager::OnComplete freshen_global = - [this](std::vector results, - bool is_cached_result) { - assert(!is_cached_result); - - // note: path is updated in the normal completion handler. - global_code_complete_cache->WithLock([&]() { - global_code_complete_cache->cached_results_ = results; + completion_cache->WithLock([&]() { + completion_cache->cached_path_ = path; + completion_cache->cached_completion_position_ = params.position; + completion_cache->cached_results_ = Consumer->ls_items; }); - }; - - global_code_complete_cache->WithLock([&]() { - callback(global_code_complete_cache->cached_results_, - true /*is_cached_result*/); - }); - clang_complete->CodeComplete(request->id, params, freshen_global); - } else if (non_global_code_complete_cache->IsCacheValid(params)) { - non_global_code_complete_cache->WithLock([&]() { - callback(non_global_code_complete_cache->cached_results_, - true /*is_cached_result*/); + } + }; + + clang::CodeCompleteOptions CCOpts; + CCOpts.IncludeBriefComments = true; +#if LLVM_VERSION_MAJOR >= 7 + CCOpts.IncludeFixIts = true; +#endif + if (completion_cache->IsCacheValid(params)) { + CompletionConsumer Consumer(CCOpts, true); + completion_cache->WithLock([&]() { + Consumer.ls_items = completion_cache->cached_results_; }); + callback(&Consumer); } else { - clang_complete->CodeComplete(request->id, params, callback); + clang_complete->completion_request_.PushBack( + std::make_unique( + request->id, params.textDocument, params.position, + std::make_unique(CCOpts, false), CCOpts, + callback)); } } } diff --git a/src/messages/textDocument_signatureHelp.cc b/src/messages/textDocument_signatureHelp.cc index 1a763e218..ab08b52cd 100644 --- a/src/messages/textDocument_signatureHelp.cc +++ b/src/messages/textDocument_signatureHelp.cc @@ -18,44 +18,29 @@ limitations under the License. #include "pipeline.hh" using namespace ccls; +#include +using namespace clang; + #include namespace { MethodType kMethodType = "textDocument/signatureHelp"; -struct In_TextDocumentSignatureHelp : public RequestInMessage { - MethodType GetMethodType() const override { return kMethodType; } - lsTextDocumentPositionParams params; -}; -MAKE_REFLECT_STRUCT(In_TextDocumentSignatureHelp, id, params); -REGISTER_IN_MESSAGE(In_TextDocumentSignatureHelp); - // Represents a parameter of a callable-signature. A parameter can // have a label and a doc-comment. struct lsParameterInformation { - // The label of this parameter. Will be shown in - // the UI. std::string label; - - // The human-readable doc-comment of this parameter. Will be shown - // in the UI but can be omitted. - std::optional documentation; + // Not available in clang + // std::optional documentation; }; -MAKE_REFLECT_STRUCT(lsParameterInformation, label, documentation); +MAKE_REFLECT_STRUCT(lsParameterInformation, label); // Represents the signature of something callable. A signature // can have a label, like a function-name, a doc-comment, and // a set of parameters. struct lsSignatureInformation { - // The label of this signature. Will be shown in - // the UI. std::string label; - - // The human-readable doc-comment of this signature. Will be shown - // in the UI but can be omitted. std::optional documentation; - - // The parameters of this signature. std::vector parameters; }; MAKE_REFLECT_STRUCT(lsSignatureInformation, label, documentation, parameters); @@ -64,30 +49,21 @@ MAKE_REFLECT_STRUCT(lsSignatureInformation, label, documentation, parameters); // callable. There can be multiple signature but only one // active and only one active parameter. struct lsSignatureHelp { - // One or more signatures. std::vector signatures; - - // The active signature. If omitted or the value lies outside the - // range of `signatures` the value defaults to zero or is ignored if - // `signatures.length === 0`. Whenever possible implementors should - // make an active decision about the active signature and shouldn't - // rely on a default value. - // In future version of the protocol this property might become - // mandantory to better express this. - std::optional activeSignature; - - // The active parameter of the active signature. If omitted or the value - // lies outside the range of `signatures[activeSignature].parameters` - // defaults to 0 if the active signature has parameters. If - // the active signature has no parameters it is ignored. - // In future version of the protocol this property might become - // mandantory to better express the active parameter if the - // active signature does have any. - std::optional activeParameter; + int activeSignature = 0; + int activeParameter; }; MAKE_REFLECT_STRUCT(lsSignatureHelp, signatures, activeSignature, activeParameter); + +struct In_TextDocumentSignatureHelp : public RequestInMessage { + MethodType GetMethodType() const override { return kMethodType; } + lsTextDocumentPositionParams params; +}; +MAKE_REFLECT_STRUCT(In_TextDocumentSignatureHelp, id, params); +REGISTER_IN_MESSAGE(In_TextDocumentSignatureHelp); + struct Out_TextDocumentSignatureHelp : public lsOutMessage { lsRequestId id; @@ -95,89 +71,135 @@ struct Out_TextDocumentSignatureHelp }; MAKE_REFLECT_STRUCT(Out_TextDocumentSignatureHelp, jsonrpc, id, result); -struct Handler_TextDocumentSignatureHelp : MessageHandler { - MethodType GetMethodType() const override { return kMethodType; } - - void Run(std::unique_ptr message) override { - auto request = static_cast(message.get()); - lsTextDocumentPositionParams ¶ms = request->params; - WorkingFile *file = - working_files->GetFileByFilename(params.textDocument.uri.GetPath()); - std::string search; - int active_param = 0; - if (file) { - lsPosition completion_position; - search = file->FindClosestCallNameInBuffer(params.position, &active_param, - &completion_position); - params.position = completion_position; +std::string BuildOptional(const CodeCompletionString &CCS, + std::vector &ls_params) { + std::string ret; + for (const auto &Chunk : CCS) { + switch (Chunk.Kind) { + case CodeCompletionString::CK_Optional: + ret += BuildOptional(*Chunk.Optional, ls_params); + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + // Intentional fallthrough here. + case CodeCompletionString::CK_CurrentParameter: { + // A piece of text that describes the parameter that corresponds to + // the code-completion location within a function call, message send, + // macro invocation, etc. + ret += Chunk.Text; + ls_params.push_back(lsParameterInformation{Chunk.Text}); + break; + } + case CodeCompletionString::CK_VerticalSpace: + break; + default: + ret += Chunk.Text; + break; + } + } + return ret; +} + +class SignatureHelpConsumer : public CodeCompleteConsumer { + std::shared_ptr Alloc; + CodeCompletionTUInfo CCTUInfo; +public: + lsSignatureHelp ls_sighelp; + SignatureHelpConsumer(const clang::CodeCompleteOptions &CCOpts) + : CodeCompleteConsumer(CCOpts, false), + Alloc(std::make_shared()), + CCTUInfo(Alloc) {} + void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates +#if LLVM_VERSION_MAJOR >= 7 + , + SourceLocation OpenParLoc +#endif + ) override { + ls_sighelp.activeParameter = (int)CurrentArg; + for (unsigned i = 0; i < NumCandidates; i++) { + OverloadCandidate Cand = Candidates[i]; + // We want to avoid showing instantiated signatures, because they may be + // long in some cases (e.g. when 'T' is substituted with 'std::string', we + // would get 'std::basic_string'). + if (auto *Func = Cand.getFunction()) + if (auto *Pattern = Func->getTemplateInstantiationPattern()) + Cand = OverloadCandidate(Pattern); + + const auto *CCS = + Cand.CreateSignatureString(CurrentArg, S, *Alloc, CCTUInfo, true); + + const char *ret_type = nullptr; + lsSignatureInformation &ls_sig = ls_sighelp.signatures.emplace_back(); +#if LLVM_VERSION_MAJOR >= 7 + const RawComment *RC = getCompletionComment(S.getASTContext(), Cand.getFunction()); + ls_sig.documentation = RC ? RC->getBriefText(S.getASTContext()) : ""; +#endif + for (const auto &Chunk : *CCS) + switch (Chunk.Kind) { + case CodeCompletionString::CK_ResultType: + ret_type = Chunk.Text; + break; + case CodeCompletionString::CK_Placeholder: + case CodeCompletionString::CK_CurrentParameter: { + ls_sig.label += Chunk.Text; + ls_sig.parameters.push_back(lsParameterInformation{Chunk.Text}); + break; + } + case CodeCompletionString::CK_Optional: + ls_sig.label += BuildOptional(*Chunk.Optional, ls_sig.parameters); + break; + case CodeCompletionString::CK_VerticalSpace: + break; + default: + ls_sig.label += Chunk.Text; + break; + } + if (ret_type) { + ls_sig.label += " -> "; + ls_sig.label += ret_type; + } } - if (search.empty()) - return; + std::sort( + ls_sighelp.signatures.begin(), ls_sighelp.signatures.end(), + [](const lsSignatureInformation &l, const lsSignatureInformation &r) { + if (l.parameters.size() != r.parameters.size()) + return l.parameters.size() < r.parameters.size(); + return l.label.size() < r.label.size(); + }); + } - CompletionManager::OnComplete callback = std::bind( - [this](InMessage *message, std::string search, int active_param, - const std::vector &results, - bool is_cached_result) { - auto msg = static_cast(message); + CodeCompletionAllocator &getAllocator() override { return *Alloc; } + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } +}; - Out_TextDocumentSignatureHelp out; - out.id = msg->id; - - for (auto &result : results) { - if (result.label != search) - continue; - - lsSignatureInformation signature; - signature.label = result.detail; - for (auto ¶meter : result.parameters_) { - lsParameterInformation ls_param; - ls_param.label = parameter; - signature.parameters.push_back(ls_param); - } - out.result.signatures.push_back(signature); - } - - // Prefer the signature with least parameter count but still larger - // than active_param. - out.result.activeSignature = 0; - if (out.result.signatures.size()) { - size_t num_parameters = SIZE_MAX; - for (size_t i = 0; i < out.result.signatures.size(); ++i) { - size_t t = out.result.signatures[i].parameters.size(); - if (active_param < t && t < num_parameters) { - out.result.activeSignature = int(i); - num_parameters = t; - } - } - } - - // Set signature to what we parsed from the working file. - out.result.activeParameter = active_param; +struct Handler_TextDocumentSignatureHelp + : BaseMessageHandler { + MethodType GetMethodType() const override { return kMethodType; } + void Run(In_TextDocumentSignatureHelp *request) override { + const auto ¶ms = request->params; + CompletionManager::OnComplete callback = + [id = request->id](CodeCompleteConsumer *OptConsumer) { + if (!OptConsumer) + return; + auto *Consumer = static_cast(OptConsumer); + Out_TextDocumentSignatureHelp out; + out.id = id; + out.result = Consumer->ls_sighelp; pipeline::WriteStdout(kMethodType, out); - - if (!is_cached_result) { - signature_cache->WithLock([&]() { - signature_cache->cached_path_ = - msg->params.textDocument.uri.GetPath(); - signature_cache->cached_completion_position_ = - msg->params.position; - signature_cache->cached_results_ = results; - }); - } - - delete message; - }, - message.release(), search, active_param, std::placeholders::_1, - std::placeholders::_2); - - if (signature_cache->IsCacheValid(params)) { - signature_cache->WithLock([&]() { - callback(signature_cache->cached_results_, true /*is_cached_result*/); - }); - } else { - clang_complete->CodeComplete(request->id, params, std::move(callback)); - } + }; + + CodeCompleteOptions CCOpts; + CCOpts.IncludeGlobals = false; + CCOpts.IncludeMacros = false; + CCOpts.IncludeBriefComments = false; + clang_complete->completion_request_.PushBack( + std::make_unique( + request->id, params.textDocument, params.position, + std::make_unique(CCOpts), CCOpts, callback)); } }; REGISTER_MESSAGE_HANDLER(Handler_TextDocumentSignatureHelp); diff --git a/src/pipeline.cc b/src/pipeline.cc index 8fea4f969..46e867ee7 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -486,9 +486,7 @@ void MainLoop() { }); IncludeComplete include_complete(&project); - auto global_code_complete_cache = std::make_unique(); - auto non_global_code_complete_cache = std::make_unique(); - auto signature_cache = std::make_unique(); + CodeCompleteCache completion_cache, signature_cache; DB db; // Setup shared references. @@ -502,10 +500,8 @@ void MainLoop() { handler->working_files = &working_files; handler->clang_complete = &clang_complete; handler->include_complete = &include_complete; - handler->global_code_complete_cache = global_code_complete_cache.get(); - handler->non_global_code_complete_cache = - non_global_code_complete_cache.get(); - handler->signature_cache = signature_cache.get(); + handler->completion_cache = &completion_cache; + handler->signature_cache = &signature_cache; } while (true) { From be9be38551ff5117cd53f3eb891fdf0ff1bf3b34 Mon Sep 17 00:00:00 2001 From: Riatre Foo Date: Wed, 12 Sep 2018 03:13:54 +0800 Subject: [PATCH 5/5] Normalize paths on Windows 1. Normalize paths in LSP document URIs and project root to forward slash and uppercase drive letters. 2. Normalize paths in compile_commands.json to forward slash and uppercase drive letters. 3. Normalize paths from directory listing to forward slash. (Drive letter should be same as input dir path, which is already uppercase since path of project root dir is normalized) 4. Add llvm::sys::path::convert_to_slash after certain llvm::sys::path and llvm::fs calls. --- src/clang_complete.cc | 2 +- src/clang_utils.cc | 2 +- src/filesystem.cc | 2 +- src/indexer.cc | 2 +- src/lsp.cc | 15 +++++++++++++-- src/platform_win.cc | 12 +++++++++--- src/project.cc | 22 +++++++++++++--------- 7 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/clang_complete.cc b/src/clang_complete.cc index 67986f245..c54ca4330 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -40,7 +40,7 @@ namespace { std::string StripFileType(const std::string &path) { SmallString<128> Ret; sys::path::append(Ret, sys::path::parent_path(path), sys::path::stem(path)); - return Ret.str(); + return sys::path::convert_to_slash(Ret); } bool LocationInRange(SourceLocation L, CharSourceRange R, diff --git a/src/clang_utils.cc b/src/clang_utils.cc index 6a92b73ba..a58237e12 100644 --- a/src/clang_utils.cc +++ b/src/clang_utils.cc @@ -34,7 +34,7 @@ std::string FileName(const FileEntry &file) { if (!StartsWith(ret, g_config->projectRoot)) { SmallString<256> dest; sys::fs::real_path(ret, dest); - ret = dest.str(); + ret = sys::path::convert_to_slash(dest.str()); } return ret; } diff --git a/src/filesystem.cc b/src/filesystem.cc index 74eb836b8..613666ade 100644 --- a/src/filesystem.cc +++ b/src/filesystem.cc @@ -59,7 +59,7 @@ void GetFilesInFolder(std::string folder, bool recursive, bool dir_prefix, if (sys::fs::is_regular_file(Status)) { if (!dir_prefix) path = path.substr(folder.size()); - handler(path); + handler(sys::path::convert_to_slash(path)); } else if (recursive && sys::fs::is_directory(Status) && !seen.count(ID = Status.getUniqueID())) { curr.push_back(path); diff --git a/src/indexer.cc b/src/indexer.cc index 3d9e2d67a..703692aa0 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -567,7 +567,7 @@ class IndexDataConsumer : public index::IndexDataConsumer { if (!llvm::sys::path::is_absolute(Path) && !SM.getFileManager().makeAbsolutePath(Path)) return -1; - it->second.second = Path.str(); + it->second.second = llvm::sys::path::convert_to_slash(Path.str()); } return it->second.first; } diff --git a/src/lsp.cc b/src/lsp.cc index afb0b76c2..6fac3d6c0 100644 --- a/src/lsp.cc +++ b/src/lsp.cc @@ -220,10 +220,15 @@ void lsDocumentUri::SetPath(const std::string &path) { } std::string lsDocumentUri::GetPath() const { - if (raw_uri.compare(0, 8, "file:///")) + if (raw_uri.compare(0, 7, "file://")) { + LOG_S(WARNING) + << "Received potentially bad URI (not starting with file://): " + << raw_uri; return raw_uri; + } std::string ret; #ifdef _WIN32 + // Skipping the initial "/" on Windows size_t i = 8; #else size_t i = 7; @@ -236,8 +241,14 @@ std::string lsDocumentUri::GetPath() const { ret.push_back(from_hex(raw_uri[i + 1]) * 16 + from_hex(raw_uri[i + 2])); i += 2; } else - ret.push_back(raw_uri[i] == '\\' ? '/' : raw_uri[i]); + ret.push_back(raw_uri[i]); + } +#ifdef _WIN32 + std::replace(ret.begin(), ret.end(), '\\', '/'); + if (ret.size() > 1 && ret[0] >= 'a' && ret[0] <= 'z' && ret[1] == ':') { + ret[0] = toupper(ret[0]); } +#endif return ret; } diff --git a/src/platform_win.cc b/src/platform_win.cc index be03055ce..d324f6e5a 100644 --- a/src/platform_win.cc +++ b/src/platform_win.cc @@ -35,14 +35,20 @@ std::string NormalizePath(const std::string &path) { TCHAR buffer[MAX_PATH] = TEXT(""); TCHAR **lpp_part = {NULL}; + std::string result; retval = GetFullPathName(path.c_str(), MAX_PATH, buffer, lpp_part); // fail, return original if (retval == 0) - return path; + result = path; + else + result = buffer; - std::string result = buffer; std::replace(result.begin(), result.end(), '\\', '/'); - // std::transform(result.begin(), result.end(), result.begin(), ::tolower); + // Normalize drive letter. + if (result.size() > 1 && result[0] >= 'a' && result[0] <= 'z' && + result[1] == ':') { + result[0] = toupper(result[0]); + } return result; } diff --git a/src/project.cc b/src/project.cc index 3cd4aa524..aa726d940 100644 --- a/src/project.cc +++ b/src/project.cc @@ -172,7 +172,8 @@ struct ProjectProcessor { HeaderSearchOptions &HeaderOpts = CI->getHeaderSearchOpts(); for (auto &E : HeaderOpts.UserEntries) { - std::string path = ResolveIfRelative(entry.directory, E.Path); + std::string path = + NormalizePath(ResolveIfRelative(entry.directory, E.Path)); switch (E.Group) { default: config->angle_dirs.insert(path); @@ -327,8 +328,9 @@ LoadEntriesFromDirectory(ProjectConfig *project, ProjectProcessor proc(project); for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) { Project::Entry entry; - entry.directory = std::move(Cmd.Directory); - entry.filename = ResolveIfRelative(entry.directory, Cmd.Filename); + entry.directory = NormalizePath(Cmd.Directory); + entry.filename = + NormalizePath(ResolveIfRelative(entry.directory, Cmd.Filename)); entry.args = std::move(Cmd.CommandLine); proc.Process(entry); if (Seen.insert(entry.filename).second) @@ -378,12 +380,14 @@ void Project::Load(const std::string &root_directory) { LOG_S(INFO) << "angle_include_dir: " << path; } - // Setup project entries. - std::lock_guard lock(mutex_); - path_to_entry_index.reserve(entries.size()); - for (size_t i = 0; i < entries.size(); ++i) { - entries[i].id = i; - path_to_entry_index[entries[i].filename] = i; + { + // Setup project entries. + std::lock_guard lock(mutex_); + path_to_entry_index.reserve(entries.size()); + for (size_t i = 0; i < entries.size(); ++i) { + entries[i].id = i; + path_to_entry_index[entries[i].filename] = i; + } } }