From c8a55c8e9fae96dc135682d50848f4f3cb2e34ae Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 4 Jan 2019 10:54:20 +0800 Subject: [PATCH 01/79] Update wiki links and delete comparison with cquery --- README.md | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 81d25dc5f..f7d8d3817 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ccls, which originates from [cquery](https://github.com/cquery-project/cquery), * formatting * hierarchies: [call (caller/callee) hierarchy](src/messages/ccls_call.cc), [inheritance (base/derived) hierarchy](src/messages/ccls_inheritance.cc), [member hierarchy](src/messages/ccls_member.cc) * [symbol rename](src/messages/textDocument_rename.cc) - * [document symbols](src/messages/textDocument_documentSymbol.cc) and approximate search of [workspace symbol](src/messages/workspace_symbol.cc) + * [document symbols](src/messages/textDocument_document.cc) and approximate search of [workspace symbol](src/messages/workspace_symbol.cc) * [hover information](src/messages/textDocument_hover.cc) * diagnostics and code actions (clang FixIts) * semantic highlighting and preprocessor skipped regions @@ -21,28 +21,11 @@ It has a global view of the code base and support a lot of cross reference featu It starts indexing the whole project (including subprojects if exist) parallelly when you open the first file, while the main thread can serve requests before the indexing is complete. Saving files will incrementally update the index. -Compared with cquery, it makes use of C++17 features, has less third-party dependencies and slimmed-down code base. -It leverages Clang C++ API as [clangd](https://clang.llvm.org/extra/clangd.html) does, which provides better support for code completion and diagnostics. -Refactoring is a non-goal as it can be provided by clang-include-fixer and other Clang based tools. - -The comparison with cquery as noted on 2018-07-15: - -| | cquery | ccls | -|------------ |--------------------------------|------------------------------| -| third_party | more | fewer | -| C++ | C++14 | C++17 | -| clang API | libclang (C) | clang/llvm C++ | -| Filesystem | AbsolutePath + custom routines | llvm/Support | -| index | libclang | clangIndex, some enhancement | -| pipeline | index merge+id remapping | simpler and more robust | - -cquery has system include path detection (through running the compiler driver) while ccls uses clangDriver. - -# >>> [Getting started](../../wiki/Getting-started) (CLICK HERE) <<< +# >>> [Getting started](../../wiki/Home) (CLICK HERE) <<< * [Build](../../wiki/Build) * [Emacs](../../wiki/Emacs) * [LanguageClient-neovim](../../wiki/LanguageClient-neovim) * [FAQ](../../wiki/FAQ) -ccls can index itself (~180MiB RSS when ide, noted on 2018-09-01), FreeBSD, glibc, Linux, LLVM (~1800MiB RSS), musl (~60MiB RSS), ... with decent memory footprint. See [wiki/compile_commands.json](../../wiki/compile_commands.json) for examples. +ccls can index itself (~180MiB RSS when idle, noted on 2018-09-01), FreeBSD, glibc, Linux, LLVM (~1800MiB RSS), musl (~60MiB RSS), ... with decent memory footprint. See [wiki/Project-Setup](../../wiki/Project-Setup) for examples. From c37417b4363521789cc880673167cb5c79ca1347 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sun, 13 Jan 2019 18:33:18 +0800 Subject: [PATCH 02/79] completion: if preamble size changes, rebuild it Fix #190 If a new header is added, the preamble size changes. Language clients may cache completion results, thus we rebuild preamble to avoid inaccurate results. --- src/sema_manager.cc | 18 +++++++++++++----- src/sema_manager.hh | 9 +++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/sema_manager.cc b/src/sema_manager.cc index afbc3a6fb..1f11fda2a 100644 --- a/src/sema_manager.cc +++ b/src/sema_manager.cc @@ -412,7 +412,9 @@ void *PreambleMain(void *manager_) { BuildCompilerInvocation(task.path, session->file.args, FS)) BuildPreamble(*session, *CI, FS, task, std::move(stat_cache)); - if (task.from_diag) { + if (task.comp_task) { + manager->comp_tasks.PushBack(std::move(task.comp_task)); + } else if (task.from_diag) { manager->ScheduleDiag(task.path, 0); } else { int debounce = @@ -463,12 +465,18 @@ void *CompletionMain(void *manager_) { DiagnosticConsumer DC; std::string content = manager->wfiles->GetContent(task->path); auto Buf = llvm::MemoryBuffer::getMemBuffer(content); + PreambleBounds Bounds = + ComputePreambleBounds(*CI->getLangOpts(), Buf.get(), 0); bool in_preamble = GetOffsetForPosition({task->position.line, task->position.character}, - content) < - ComputePreambleBounds(*CI->getLangOpts(), Buf.get(), 0).Size; - if (in_preamble) + content) < (int)Bounds.Size; + if (in_preamble) { preamble.reset(); + } else if (preamble && Bounds.Size != preamble->Preamble.getBounds().Size) { + manager->preamble_tasks.PushBack({task->path, std::move(task), false}, + true); + continue; + } auto Clang = BuildCompilerInstance(*session, std::move(CI), FS, DC, preamble.get(), task->path, Buf); if (!Clang) @@ -545,7 +553,7 @@ void *DiagnosticMain(void *manager_) { } } if (rebuild) { - manager->preamble_tasks.PushBack({task.path, true}, true); + manager->preamble_tasks.PushBack({task.path, nullptr, true}, true); continue; } } diff --git a/src/sema_manager.hh b/src/sema_manager.hh index 72dde8edd..dca18afe6 100644 --- a/src/sema_manager.hh +++ b/src/sema_manager.hh @@ -102,10 +102,6 @@ struct SemaManager { std::function; using OnDropped = std::function; - struct PreambleTask { - std::string path; - bool from_diag = false; - }; struct CompTask { CompTask(const RequestId &id, const std::string &path, const Position &position, @@ -126,6 +122,11 @@ struct SemaManager { int64_t wait_until; int64_t debounce; }; + struct PreambleTask { + std::string path; + std::unique_ptr comp_task; + bool from_diag = false; + }; SemaManager(Project *project, WorkingFiles *wfiles, OnDiagnostic on_diagnostic, OnDropped on_dropped); From d556a0803be0d753191827593b6366a8c42d02b1 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Mon, 21 Jan 2019 10:20:07 +0800 Subject: [PATCH 03/79] Add -log-file=stderr and make it default Change -log-file-append to a boolean flag --- src/log.cc | 1 + src/main.cc | 19 +++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/log.cc b/src/log.cc index fde88be34..ae3b277a8 100644 --- a/src/log.cc +++ b/src/log.cc @@ -60,6 +60,7 @@ Message::~Message() { std::lock_guard lock(mtx); stream_ << '\n'; fputs(stream_.str().c_str(), file); + fflush(file); if (verbosity_ == Verbosity_FATAL) abort(); } diff --git a/src/main.cc b/src/main.cc index 37a50f451..80f05952e 100644 --- a/src/main.cc +++ b/src/main.cc @@ -46,10 +46,10 @@ opt opt_index("index", value_desc("root"), cat(C)); list opt_init("init", desc("extra initialization options in JSON"), cat(C)); -opt opt_log_file("log-file", desc("log"), value_desc("filename"), +opt opt_log_file("log-file", desc("stderr or log file"), + value_desc("file"), init("stderr"), cat(C)); +opt opt_log_file_append("log-file-append", desc("append to log file"), cat(C)); -opt opt_log_file_append("log-file-append", desc("log"), - value_desc("filename"), cat(C)); void CloseLog() { fclose(ccls::log::file); } @@ -83,14 +83,13 @@ int main(int argc, char **argv) { bool language_server = true; - if (opt_log_file.size() || opt_log_file_append.size()) { - ccls::log::file = opt_log_file.size() - ? fopen(opt_log_file.c_str(), "wb") - : fopen(opt_log_file_append.c_str(), "ab"); + if (opt_log_file.size()) { + ccls::log::file = + opt_log_file == "stderr" + ? stderr + : fopen(opt_log_file.c_str(), opt_log_file_append ? "ab" : "wb"); if (!ccls::log::file) { - fprintf( - stderr, "failed to open %s\n", - (opt_log_file.size() ? opt_log_file : opt_log_file_append).c_str()); + fprintf(stderr, "failed to open %s\n", opt_log_file.c_str()); return 2; } setbuf(ccls::log::file, NULL); From 6a517223ebd84a529d2de17061c1be9bbd8c5c2a Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Mon, 21 Jan 2019 10:44:44 +0800 Subject: [PATCH 04/79] Drop support for clang 6 --- CMakeLists.txt | 2 +- cmake/FindClang.cmake | 1 - src/clang_tu.cc | 4 --- src/indexer.cc | 44 +++---------------------- src/messages/textDocument_completion.cc | 4 --- src/sema_manager.cc | 16 +++------ 6 files changed, 9 insertions(+), 62 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bf39ebfd1..f84d67abc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,7 +112,7 @@ endif() ### Libraries # See cmake/FindClang.cmake -find_package(Clang 6.0.0) +find_package(Clang 7.0.0) target_link_libraries(ccls PRIVATE Clang::Clang) # Enable threading support diff --git a/cmake/FindClang.cmake b/cmake/FindClang.cmake index 194029005..078395dd9 100644 --- a/cmake/FindClang.cmake +++ b/cmake/FindClang.cmake @@ -72,7 +72,6 @@ _Clang_find_library(Clang_LIBRARY clangIndex) _Clang_find_add_library(clangFormat) _Clang_find_add_library(clangTooling) -# TODO Remove after dropping clang 6 _Clang_find_library(clangToolingInclusions_LIBRARY clangToolingInclusions) if (clangToolingInclusions_LIBRARY) list(APPEND _Clang_LIBRARIES ${clangToolingInclusions_LIBRARY}) diff --git a/src/clang_tu.cc b/src/clang_tu.cc index f7fb6d0f8..449b7912a 100644 --- a/src/clang_tu.cc +++ b/src/clang_tu.cc @@ -143,7 +143,6 @@ const char *ClangBuiltinTypeName(int kind) { return "double"; case BuiltinType::LongDouble: return "long double"; -#if LLVM_VERSION_MAJOR >= 7 case BuiltinType::ShortAccum: return "short _Accum"; case BuiltinType::Accum: @@ -192,7 +191,6 @@ const char *ClangBuiltinTypeName(int kind) { return "_Sat unsigned _Fract"; case BuiltinType::BuiltinType::SatULongFract: return "_Sat unsigned long _Fract"; -#endif case BuiltinType::Float16: return "_Float16"; case BuiltinType::Float128: @@ -200,10 +198,8 @@ const char *ClangBuiltinTypeName(int kind) { case BuiltinType::WChar_S: case BuiltinType::WChar_U: return "wchar_t"; -#if LLVM_VERSION_MAJOR >= 7 case BuiltinType::Char8: return "char8_t"; -#endif case BuiltinType::Char16: return "char16_t"; case BuiltinType::Char32: diff --git a/src/indexer.cc b/src/indexer.cc index d86316928..71b6fd1ef 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -579,11 +579,7 @@ class IndexDataConsumer : public index::IndexDataConsumer { if (init) { SourceManager &SM = Ctx->getSourceManager(); const LangOptions &Lang = Ctx->getLangOpts(); - SourceRange R = SM.getExpansionRange(init->getSourceRange()) -#if LLVM_VERSION_MAJOR >= 7 - .getAsRange() -#endif - ; + SourceRange R = SM.getExpansionRange(init->getSourceRange()).getAsRange(); SourceLocation L = D->getLocation(); if (L.isMacroID() || !SM.isBeforeInTranslationUnit(L, R.getBegin())) return; @@ -674,41 +670,15 @@ class IndexDataConsumer : public index::IndexDataConsumer { } bool handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, ArrayRef Relations, -#if LLVM_VERSION_MAJOR >= 7 - SourceLocation Loc, -#else - FileID LocFID, unsigned LocOffset, -#endif - ASTNodeInfo ASTNode) override { + SourceLocation Loc, ASTNodeInfo ASTNode) override { SourceManager &SM = Ctx->getSourceManager(); const LangOptions &Lang = Ctx->getLangOpts(); -#if LLVM_VERSION_MAJOR < 7 - SourceLocation Loc; - { - const SrcMgr::SLocEntry &Entry = SM.getSLocEntry(LocFID); - unsigned off = Entry.getOffset() + LocOffset; - if (!Entry.isFile()) - off |= 1u << 31; - Loc = SourceLocation::getFromRawEncoding(off); - } -#else FileID LocFID; -#endif SourceLocation Spell = SM.getSpellingLoc(Loc); const FileEntry *FE; Range loc; -#if LLVM_VERSION_MAJOR < 7 - CharSourceRange R; - if (SM.isMacroArgExpansion(Loc)) - R = CharSourceRange::getTokenRange(Spell); - else { - auto P = SM.getExpansionRange(Loc); - R = CharSourceRange::getTokenRange(P.first, P.second); - } -#else auto R = SM.isMacroArgExpansion(Loc) ? CharSourceRange::getTokenRange(Spell) : SM.getExpansionRange(Loc); -#endif loc = FromCharSourceRange(SM, Lang, R); LocFID = SM.getFileID(R.getBegin()); FE = SM.getFileEntryForID(LocFID); @@ -1072,12 +1042,8 @@ class IndexPPCallbacks : public PPCallbacks { StringRef Included, bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, StringRef SearchPath, StringRef RelativePath, - const Module *Imported -#if LLVM_VERSION_MAJOR >= 7 - , - SrcMgr::CharacteristicKind FileType -#endif - ) override { + const Module *Imported, + SrcMgr::CharacteristicKind FileType) override { if (!File) return; llvm::sys::fs::UniqueID UniqueID; @@ -1260,9 +1226,7 @@ Index(SemaManager *manager, WorkingFiles *wfiles, VFS *vfs, IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; IndexOpts.IndexFunctionLocals = true; -#if LLVM_VERSION_MAJOR >= 7 IndexOpts.IndexImplicitInstantiation = true; -#endif std::unique_ptr Action = createIndexingAction( DataConsumer, IndexOpts, std::make_unique(param)); diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc index e3d299666..0a430bb5b 100644 --- a/src/messages/textDocument_completion.cc +++ b/src/messages/textDocument_completion.cc @@ -424,7 +424,6 @@ class CompletionConsumer : public CodeCompleteConsumer { ls_items[j].label = ls_items[j].filterText; } } -#if LLVM_VERSION_MAJOR >= 7 for (const FixItHint &FixIt : R.FixIts) { auto &AST = S.getASTContext(); TextEdit ls_edit = @@ -432,7 +431,6 @@ class CompletionConsumer : public CodeCompleteConsumer { for (size_t j = first_idx; j < ls_items.size(); j++) ls_items[j].additionalTextEdits.push_back(ls_edit); } -#endif } } @@ -462,9 +460,7 @@ void MessageHandler::textDocument_completion(CompletionParam ¶m, clang::CodeCompleteOptions CCOpts; CCOpts.IncludeBriefComments = true; CCOpts.IncludeCodePatterns = StringRef(buffer_line).ltrim().startswith("#"); -#if LLVM_VERSION_MAJOR >= 7 CCOpts.IncludeFixIts = true; -#endif CCOpts.IncludeMacros = true; if (param.context.triggerKind == CompletionTriggerKind::TriggerCharacter && diff --git a/src/sema_manager.cc b/src/sema_manager.cc index 1f11fda2a..d761d69b7 100644 --- a/src/sema_manager.cc +++ b/src/sema_manager.cc @@ -178,11 +178,8 @@ class StoreInclude : public PPCallbacks { StringRef FileName, bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, StringRef SearchPath, StringRef RelativePath, - const clang::Module *Imported -#if LLVM_VERSION_MAJOR >= 7 - , SrcMgr::CharacteristicKind FileKind -#endif - ) override { + const clang::Module *Imported, + SrcMgr::CharacteristicKind FileKind) override { (void)SM; if (File && Seen.insert(File).second) out.emplace_back(PathFromFileEntry(*File), File->getModificationTime()); @@ -292,15 +289,10 @@ std::unique_ptr BuildCompilerInstance( IntrusiveRefCntPtr FS, DiagnosticConsumer &DC, const PreambleData *preamble, const std::string &main, std::unique_ptr &Buf) { - if (preamble) { -#if LLVM_VERSION_MAJOR >= 7 + if (preamble) preamble->Preamble.OverridePreamble(*CI, FS, Buf.get()); -#else - preamble->Preamble.AddImplicitPreamble(*CI, FS, Buf.get()); -#endif - } else { + else CI->getPreprocessorOpts().addRemappedFile(main, Buf.get()); - } auto Clang = std::make_unique(session.PCH); Clang->setInvocation(std::move(CI)); From 1f0a509b31d4e9ec5571b3e9487473d4ca726c79 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Mon, 21 Jan 2019 12:15:28 +0800 Subject: [PATCH 05/79] Implement initialization option compilationDatabaseCommand on Windows --- src/platform.hh | 3 --- src/platform_posix.cc | 43 ------------------------------- src/platform_win.cc | 5 ---- src/project.cc | 60 ++++++++++++++++++++++++++++++------------- 4 files changed, 42 insertions(+), 69 deletions(-) diff --git a/src/platform.hh b/src/platform.hh index bc2ed2ceb..58ed18a52 100644 --- a/src/platform.hh +++ b/src/platform.hh @@ -16,8 +16,5 @@ void FreeUnusedMemory(); // Stop self and wait for SIGCONT. void TraceMe(); -std::string GetExternalCommandOutput(const std::vector &command, - std::string_view input); - void SpawnThread(void *(*fn)(void *), void *arg); } // namespace ccls diff --git a/src/platform_posix.cc b/src/platform_posix.cc index c20099fe6..eaead94a4 100644 --- a/src/platform_posix.cc +++ b/src/platform_posix.cc @@ -61,49 +61,6 @@ void TraceMe() { raise(traceme[0] == 's' ? SIGSTOP : SIGTSTP); } -std::string GetExternalCommandOutput(const std::vector &command, - std::string_view input) { - int pin[2], pout[2]; - if (pipe(pin) < 0) { - perror("pipe(stdin)"); - return ""; - } - if (pipe(pout) < 0) { - perror("pipe(stdout)"); - close(pin[0]); - close(pin[1]); - return ""; - } - pid_t child = fork(); - if (child == 0) { - dup2(pout[0], 0); - dup2(pin[1], 1); - close(pin[0]); - close(pin[1]); - close(pout[0]); - close(pout[1]); - auto argv = new char *[command.size() + 1]; - for (size_t i = 0; i < command.size(); i++) - argv[i] = const_cast(command[i].c_str()); - argv[command.size()] = nullptr; - execvp(argv[0], argv); - _Exit(127); - } - close(pin[1]); - close(pout[0]); - // O_NONBLOCK is disabled, write(2) blocks until all bytes are written. - (void)write(pout[1], input.data(), input.size()); - close(pout[1]); - std::string ret; - char buf[4096]; - ssize_t n; - while ((n = read(pin[0], buf, sizeof buf)) > 0) - ret.append(buf, n); - close(pin[0]); - waitpid(child, NULL, 0); - return ret; -} - void SpawnThread(void *(*fn)(void *), void *arg) { pthread_t thd; pthread_attr_t attr; diff --git a/src/platform_win.cc b/src/platform_win.cc index d10858d5c..9a51d1650 100644 --- a/src/platform_win.cc +++ b/src/platform_win.cc @@ -45,11 +45,6 @@ void FreeUnusedMemory() {} // TODO Wait for debugger to attach void TraceMe() {} -std::string GetExternalCommandOutput(const std::vector &command, - std::string_view input) { - return ""; -} - void SpawnThread(void *(*fn)(void *), void *arg) { std::thread(fn, arg).detach(); } diff --git a/src/project.cc b/src/project.cc index e81d73032..71f43bdba 100644 --- a/src/project.cc +++ b/src/project.cc @@ -19,13 +19,17 @@ #include #include #include +#include #include -#if defined(__unix__) || defined(__APPLE__) -#include +#ifdef _WIN32 +# include +#else +# include #endif +#include #include #include #include @@ -296,7 +300,8 @@ int ComputeGuessScore(std::string_view a, std::string_view b) { } // namespace void Project::LoadDirectory(const std::string &root, Project::Folder &folder) { - SmallString<256> Path, CDBDir; + SmallString<256> CDBDir, Path, StdinPath; + std::string err_msg; folder.entries.clear(); if (g_config->compilationDatabaseCommand.empty()) { CDBDir = root; @@ -307,33 +312,49 @@ void Project::LoadDirectory(const std::string &root, Project::Folder &folder) { // If `compilationDatabaseCommand` is specified, execute it to get the // compdb. #ifdef _WIN32 - // TODO + char tmpdir[L_tmpnam]; + tmpnam_s(tmpdir, L_tmpnam); + CDBDir = tmpdir; + if (sys::fs::create_directory(tmpdir, false)) + return; #else char tmpdir[] = "/tmp/ccls-compdb-XXXXXX"; if (!mkdtemp(tmpdir)) return; CDBDir = tmpdir; - sys::path::append(Path, CDBDir, "compile_commands.json"); - rapidjson::StringBuffer input; - rapidjson::Writer writer(input); - JsonWriter json_writer(&writer); - Reflect(json_writer, *g_config); - std::string contents = GetExternalCommandOutput( - std::vector{g_config->compilationDatabaseCommand, root}, - input.GetString()); - FILE *fout = fopen(Path.c_str(), "wb"); - fwrite(contents.c_str(), contents.size(), 1, fout); - fclose(fout); #endif + sys::path::append(Path, CDBDir, "compile_commands.json"); + sys::path::append(StdinPath, CDBDir, "stdin"); + { + rapidjson::StringBuffer sb; + rapidjson::Writer writer(sb); + JsonWriter json_writer(&writer); + Reflect(json_writer, *g_config); + std::string input = sb.GetString(); + FILE *fout = fopen(StdinPath.c_str(), "wb"); + fwrite(input.c_str(), input.size(), 1, fout); + fclose(fout); + } + std::array, 3> Redir{StringRef(StdinPath), + StringRef(Path), StringRef()}; + std::vector args{g_config->compilationDatabaseCommand, root}; + if (sys::ExecuteAndWait(args[0], args, llvm::None, Redir, 0, 0, &err_msg) < + 0) { + LOG_S(ERROR) << "failed to execute " << args[0].str() << " " + << args[1].str() << ": " << err_msg; + return; + } } - std::string err_msg; std::unique_ptr CDB = tooling::CompilationDatabase::loadFromDirectory(CDBDir, err_msg); if (!g_config->compilationDatabaseCommand.empty()) { #ifdef _WIN32 - // TODO + DeleteFileA(StdinPath.c_str()); + DeleteFileA(Path.c_str()); + RemoveDirectoryA(CDBDir.c_str()); #else + unlink(StdinPath.c_str()); unlink(Path.c_str()); rmdir(CDBDir.c_str()); #endif @@ -342,7 +363,10 @@ void Project::LoadDirectory(const std::string &root, Project::Folder &folder) { ProjectProcessor proc(folder); StringSet<> Seen; std::vector result; - if (CDB) { + if (!CDB) { + if (g_config->compilationDatabaseCommand.size() || sys::fs::exists(Path)) + LOG_S(ERROR) << "failed to load " << Path.c_str(); + } else { LOG_S(INFO) << "loaded " << Path.c_str(); for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) { static bool once; From e9a9fc8bb471d4319cbcfb29ff7c7823f8979598 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Tue, 22 Jan 2019 22:44:13 +0800 Subject: [PATCH 06/79] cmake: delete SYSTEM_CLANG and auto-download mechanism --- .appveyor.yml | 2 +- CMakeLists.txt | 67 +-------- .../LLVM-7.0.0-win64.exe.SHA256 | 1 - ....0.0-amd64-unknown-freebsd11.tar.xz.SHA256 | 1 - ...vm-7.0.0-x86_64-apple-darwin.tar.xz.SHA256 | 1 - ...86_64-linux-gnu-ubuntu-14.04.tar.xz.SHA256 | 1 - ...86_64-linux-gnu-ubuntu-16.04.tar.xz.SHA256 | 1 - cmake/DownloadAndExtract7zip.cmake | 52 ------- cmake/DownloadAndExtractClang.cmake | 129 ------------------ 9 files changed, 2 insertions(+), 253 deletions(-) delete mode 100644 clang_archive_hashes/LLVM-7.0.0-win64.exe.SHA256 delete mode 100644 clang_archive_hashes/clang+llvm-7.0.0-amd64-unknown-freebsd11.tar.xz.SHA256 delete mode 100644 clang_archive_hashes/clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz.SHA256 delete mode 100644 clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz.SHA256 delete mode 100644 clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz.SHA256 delete mode 100644 cmake/DownloadAndExtract7zip.cmake delete mode 100644 cmake/DownloadAndExtractClang.cmake diff --git a/.appveyor.yml b/.appveyor.yml index 0fc36d3fe..42a7a4c03 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -15,7 +15,7 @@ install: - 7z x llvm.tar - git submodule update --init --recursive build_script: - - cmake -G"Visual Studio 15 2017 Win64" -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -DSYSTEM_CLANG=ON -DCLANG_ROOT=C:\projects\ccls\llvm+clang-7.0.0-win64-msvc-release + - cmake -G"Visual Studio 15 2017 Win64" -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=C:\projects\ccls\llvm+clang-7.0.0-win64-msvc-release - cmake --build build --target ccls --config Release artifacts: diff --git a/CMakeLists.txt b/CMakeLists.txt index f84d67abc..993376d81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,13 +5,7 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) include(DefaultCMakeBuildType) # Required Clang version -set(CLANG_DOWNLOAD_LOCATION ${CMAKE_BINARY_DIR} - CACHE STRING "Downloaded Clang location") -option(SYSTEM_CLANG "Use system installation of Clang instead of \ - downloading Clang" OFF) -option(ASAN "Compile with address sanitizers" OFF) option(LLVM_ENABLE_RTTI "-fno-rtti if OFF. This should match LLVM libraries" OFF) -option(CLANG_USE_BUNDLED_LIBC++ "Let Clang use bundled libc++" OFF) option(USE_SHARED_LLVM "Link against libLLVM.so instead separate LLVM{Option,Support,...}" OFF) # Sources for the executable are specified at end of CMakeLists.txt @@ -30,10 +24,7 @@ add_executable(ccls "") # Enable C++17 (Required) set_property(TARGET ccls PROPERTY CXX_STANDARD 17) set_property(TARGET ccls PROPERTY CXX_STANDARD_REQUIRED ON) -# Disable gnu extensions except for Cygwin which needs them to build properly -if(NOT CYGWIN) - set_property(TARGET ccls PROPERTY CXX_EXTENSIONS OFF) -endif() +set_property(TARGET ccls PROPERTY CXX_EXTENSIONS OFF) if(NOT LLVM_ENABLE_RTTI) # releases.llvm.org libraries are compiled with -fno-rtti @@ -76,37 +67,6 @@ else() target_compile_options(ccls PRIVATE $<$:-fno-limit-debug-info>) endif() - - if(ASAN) - target_compile_options(ccls PRIVATE -fsanitize=address,undefined) - # target_link_libraries also takes linker flags - target_link_libraries(ccls PRIVATE -fsanitize=address,undefined) - endif() -endif() - -### Download Clang if required - -if(NOT SYSTEM_CLANG) - message(STATUS "Using downloaded Clang") - - include(DownloadAndExtractClang) - download_and_extract_clang(${CLANG_DOWNLOAD_LOCATION}) - # Used by FindClang - set(CLANG_ROOT ${DOWNLOADED_CLANG_DIR}) - - if(${CMAKE_CXX_COMPILER_ID} STREQUAL Clang AND CLANG_USE_BUNDLED_LIBC++) - message(STATUS "Using bundled libc++") - target_compile_options(ccls PRIVATE -nostdinc++ -cxx-isystem ${CLANG_ROOT}/include/c++/v1) - if(${CMAKE_SYSTEM_NAME} STREQUAL Linux) - # Don't use -stdlib=libc++ because while ccls is linked with libc++, bundled clang+llvm require libstdc++ - target_link_libraries(ccls PRIVATE -L${CLANG_ROOT}/lib c++ c++abi) - - # FreeBSD defaults to -stdlib=libc++ and uses system libcxxrt.a - endif() - endif() - -else() - message(STATUS "Using system Clang") endif() ### Libraries @@ -143,31 +103,6 @@ target_include_directories(ccls SYSTEM PRIVATE install(TARGETS ccls RUNTIME DESTINATION bin) -if(NOT SYSTEM_CLANG AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL Windows) - - if(${CMAKE_SYSTEM_NAME} MATCHES Linux|FreeBSD) - set_property(TARGET ccls APPEND PROPERTY - INSTALL_RPATH $ORIGIN/../lib) - elseif(${CMAKE_SYSTEM_NAME} STREQUAL Darwin) - set_property(TARGET ccls APPEND PROPERTY - INSTALL_RPATH @loader_path/../lib) - endif() - - file(GLOB LIBCLANG_PLUS_SYMLINKS - ${DOWNLOADED_CLANG_DIR}/lib/libclang.[so,dylib]*) - install(FILES ${LIBCLANG_PLUS_SYMLINKS} DESTINATION lib) -endif() - -# Allow running from build Windows by copying libclang.dll to build directory -if(NOT SYSTEM_CLANG AND ${CMAKE_SYSTEM_NAME} STREQUAL Windows) - add_custom_command(TARGET ccls - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${DOWNLOADED_CLANG_DIR}/bin/libclang.dll - $ - COMMENT "Copying libclang.dll to build directory ...") -endif() - ### Tools # We use glob here since source files are already manually added with diff --git a/clang_archive_hashes/LLVM-7.0.0-win64.exe.SHA256 b/clang_archive_hashes/LLVM-7.0.0-win64.exe.SHA256 deleted file mode 100644 index 54bea9e6c..000000000 --- a/clang_archive_hashes/LLVM-7.0.0-win64.exe.SHA256 +++ /dev/null @@ -1 +0,0 @@ -74b197a3959b0408adf0824be01db8dddfa2f9a967f4085af3fad900ed5fdbf6 \ No newline at end of file diff --git a/clang_archive_hashes/clang+llvm-7.0.0-amd64-unknown-freebsd11.tar.xz.SHA256 b/clang_archive_hashes/clang+llvm-7.0.0-amd64-unknown-freebsd11.tar.xz.SHA256 deleted file mode 100644 index b8a56bfa7..000000000 --- a/clang_archive_hashes/clang+llvm-7.0.0-amd64-unknown-freebsd11.tar.xz.SHA256 +++ /dev/null @@ -1 +0,0 @@ -95ceb933ccf76e3ddaa536f41ab82c442bbac07cdea6f9fbf6e3b13cc1711255 \ No newline at end of file diff --git a/clang_archive_hashes/clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz.SHA256 b/clang_archive_hashes/clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz.SHA256 deleted file mode 100644 index 86f733c96..000000000 --- a/clang_archive_hashes/clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz.SHA256 +++ /dev/null @@ -1 +0,0 @@ -b3ad93c3d69dfd528df9c5bb1a434367babb8f3baea47fbb99bf49f1b03c94ca \ No newline at end of file diff --git a/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz.SHA256 b/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz.SHA256 deleted file mode 100644 index bc8306913..000000000 --- a/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz.SHA256 +++ /dev/null @@ -1 +0,0 @@ -5c90e61b06d37270bc26edb305d7e498e2c7be22d99e0afd9f2274ef5458575a \ No newline at end of file diff --git a/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz.SHA256 b/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz.SHA256 deleted file mode 100644 index 1475a0a85..000000000 --- a/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz.SHA256 +++ /dev/null @@ -1 +0,0 @@ -69b85c833cd28ea04ce34002464f10a6ad9656dd2bba0f7133536a9927c660d2 \ No newline at end of file diff --git a/cmake/DownloadAndExtract7zip.cmake b/cmake/DownloadAndExtract7zip.cmake deleted file mode 100644 index 72bc879d5..000000000 --- a/cmake/DownloadAndExtract7zip.cmake +++ /dev/null @@ -1,52 +0,0 @@ -# Downloads and extracts the 7-Zip MSI installer from https://www.7-zip.org/. -# -# Returns the extracted 7-Zip directory in DOWNLOADED_7ZIP_DIR -function(download_and_extract_7zip 7ZIP_DOWNLOAD_LOCATION) - -set(7ZIP_VERSION 1801) -set(7ZIP_EXT .msi) -set(7ZIP_NAME 7z${7ZIP_VERSION}-x64) -set(7ZIP_FULL_NAME ${7ZIP_NAME}${7ZIP_EXT}) - -set(7ZIP_FILE ${7ZIP_DOWNLOAD_LOCATION}/${7ZIP_FULL_NAME}) -set(7ZIP_EXTRACT_DIR ${7ZIP_DOWNLOAD_LOCATION}/${7ZIP_NAME}) -set(7ZIP_URL https://www.7-zip.org/a/${7ZIP_FULL_NAME}) - -# Exit if 7-Zip is already downloaded and extracted -find_program(7ZIP_EXECUTABLE 7z NO_DEFAULT_PATH - PATHS ${7ZIP_EXTRACT_DIR}/Files/7-Zip) -if(7ZIP_EXECUTABLE) - message(STATUS "7-Zip already downloaded") - return() -endif() - -message(STATUS "Downloading 7-Zip ${7ZIP_VERSION} (${7ZIP_URL}) ...") -file(DOWNLOAD ${7ZIP_URL} ${7ZIP_FILE}) - -find_program(MSIEXEC_EXECUTABLE msiexec) -if(NOT MSIEXEC_EXECUTABLE) - message(FATAL_ERROR "Unable to find msiexec (required to extract 7-Zip msi \ -installer). Install 7-Zip yourself and make sure it is available in the path") -endif() - -message(STATUS "Extracting downloaded 7-Zip ...") - -# msiexec requires Windows path separators (\) -file(TO_NATIVE_PATH ${7ZIP_FILE} 7ZIP_FILE) -file(TO_NATIVE_PATH ${7ZIP_EXTRACT_DIR} 7ZIP_EXTRACT_DIR) - -# msiexec with /a option allows extraction of msi installers without requiring -# admin privileges. We use this to extract the 7-Zip installer without -# requiring any actions from the user -execute_process(COMMAND ${MSIEXEC_EXECUTABLE} /a ${7ZIP_FILE} /qn - TARGETDIR=${7ZIP_EXTRACT_DIR} - WORKING_DIRECTORY ${7ZIP_DOWNLOAD_LOCATION} - OUTPUT_QUIET) - -# Convert back to CMake separators (/) before returning -file(TO_CMAKE_PATH ${7ZIP_EXTRACT_DIR} 7ZIP_EXTRACT_DIR) - -# Actual 7-Zip directory is nested inside the extract directory. -set(DOWNLOADED_7ZIP_DIR ${7ZIP_EXTRACT_DIR}/Files/7-Zip PARENT_SCOPE) - -endfunction() \ No newline at end of file diff --git a/cmake/DownloadAndExtractClang.cmake b/cmake/DownloadAndExtractClang.cmake deleted file mode 100644 index 9365b1881..000000000 --- a/cmake/DownloadAndExtractClang.cmake +++ /dev/null @@ -1,129 +0,0 @@ -# Downloads and extracts the Clang archive for the current system from -# https://releases.llvm.org -# -# Returns the extracted Clang archive directory in DOWNLOADED_CLANG_DIR -# -# Downloads 7-Zip to extract Clang if it isn't available in the PATH -function(download_and_extract_clang CLANG_DOWNLOAD_LOCATION) - -set(CLANG_VERSION 7.0.0) -set(CLANG_ARCHIVE_EXT .tar.xz) - -if(${CMAKE_SYSTEM_NAME} STREQUAL Linux) - - # Default to Ubuntu 16.04 - set(CLANG_ARCHIVE_NAME - clang+llvm-${CLANG_VERSION}-x86_64-linux-gnu-ubuntu-16.04) - -elseif(${CMAKE_SYSTEM_NAME} STREQUAL Darwin) - - set(CLANG_ARCHIVE_NAME clang+llvm-${CLANG_VERSION}-x86_64-apple-darwin) - -elseif(${CMAKE_SYSTEM_NAME} STREQUAL Windows) - - set(CLANG_ARCHIVE_NAME LLVM-${CLANG_VERSION}-win64) - set(CLANG_ARCHIVE_EXT .exe) - -elseif(${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD) - - set(CLANG_ARCHIVE_NAME clang+llvm-${CLANG_VERSION}-amd64-unknown-freebsd11) - -endif() - -set(CLANG_ARCHIVE_FULL_NAME ${CLANG_ARCHIVE_NAME}${CLANG_ARCHIVE_EXT}) -set(CLANG_ARCHIVE_FILE ${CLANG_DOWNLOAD_LOCATION}/${CLANG_ARCHIVE_FULL_NAME}) -set(CLANG_ARCHIVE_EXTRACT_DIR ${CLANG_DOWNLOAD_LOCATION}/${CLANG_ARCHIVE_NAME}) -set(CLANG_ARCHIVE_URL - https://releases.llvm.org/${CLANG_VERSION}/${CLANG_ARCHIVE_FULL_NAME}) -set(CLANG_ARCHIVE_HASH_FILE - ${CMAKE_SOURCE_DIR}/clang_archive_hashes/${CLANG_ARCHIVE_FULL_NAME}.SHA256) - -# Exit if Clang is already downloaded and extracted -set(CLANG_ROOT ${CLANG_ARCHIVE_EXTRACT_DIR}) -find_package(Clang ${CLANG_VERSION} QUIET) -if(Clang_FOUND) - message(STATUS "Clang already downloaded") - set(DOWNLOADED_CLANG_DIR ${CLANG_ARCHIVE_EXTRACT_DIR} PARENT_SCOPE) - return() -endif() - -if(NOT CLANG_ARCHIVE_NAME) - message(FATAL_ERROR "No Clang archive url specified for current platform \ -(${CMAKE_SYSTEM_NAME}). Please file an issue to get it added.") -endif() - -if(NOT EXISTS ${CLANG_ARCHIVE_HASH_FILE}) - message(FATAL_ERROR "No SHA256 hash available for the current platform \ -(${CMAKE_SYSTEM_NAME}) + clang version (${CLANG_VERSION}) combination. Please \ -file an issue to get it added.") -endif() - -# Download Clang archive -message(STATUS "Downloading Clang ${CLANG_VERSION} (${CLANG_ARCHIVE_URL}) ...") -file(DOWNLOAD ${CLANG_ARCHIVE_URL} ${CLANG_ARCHIVE_FILE} - STATUS CLANG_ARCHIVE_DOWNLOAD_RESULT) - -# Abort if download failed -list(GET ${CLANG_ARCHIVE_DOWNLOAD_RESULT} 0 ERROR_CODE) -if(${ERROR_CODE}) - list(GET ${CLANG_ARCHIVE_DOWNLOAD_RESULT} 1 ERROR_STRING) - message(FATAL_ERROR ${ERROR_STRING}) -endif() - -# Retrieve expected hash from file and strip newline -file(READ ${CLANG_ARCHIVE_HASH_FILE} CLANG_ARCHIVE_EXPECTED_HASH) -string(STRIP ${CLANG_ARCHIVE_EXPECTED_HASH} CLANG_ARCHIVE_EXPECTED_HASH) -# Calculate actual hash -file(SHA256 ${CLANG_ARCHIVE_FILE} CLANG_ARCHIVE_HASH) -# Abort if hashes do not match -if(NOT ${CLANG_ARCHIVE_EXPECTED_HASH} STREQUAL ${CLANG_ARCHIVE_HASH}) - message(FATAL_ERROR "SHA256 hash of downloaded Clang does not match \ -expected hash. Remove the build directory and try running CMake again. If this \ -keeps happening, file an issue to report the problem.") -endif() - -if(${CLANG_ARCHIVE_EXT} STREQUAL .exe) - # Download and extract 7-zip if not found in PATH - find_program(7ZIP_EXECUTABLE 7z) - if(NOT 7ZIP_EXECUTABLE) - message(STATUS "7-Zip not found in PATH") - - include(DownloadAndExtract7zip) - download_and_extract_7zip(${CLANG_DOWNLOAD_LOCATION}) - find_program(7ZIP_EXECUTABLE - NAMES 7z - NO_DEFAULT_PATH - PATHS ${DOWNLOADED_7ZIP_DIR} - ) - else() - message(STATUS "7-Zip found in PATH") - endif() - - message(STATUS "Extracting downloaded Clang with 7-Zip ...") - - # Avoid running the Clang installer by extracting the exe with 7-Zip - execute_process( - COMMAND ${7ZIP_EXECUTABLE} x - -o${CLANG_ARCHIVE_EXTRACT_DIR} - -xr!$PLUGINSDIR ${CLANG_ARCHIVE_FILE} - WORKING_DIRECTORY ${CLANG_DOWNLOAD_LOCATION} - OUTPUT_QUIET - ) - -elseif(${CLANG_ARCHIVE_EXT} STREQUAL .tar.xz) - message(STATUS "Extracting downloaded Clang with CMake built-in tar ...") - - # CMake has builtin support for tar via the -E flag - execute_process( - COMMAND ${CMAKE_COMMAND} -E tar -xf ${CLANG_ARCHIVE_FILE} - # Specify working directory to allow running cmake from - # everywhere - # (example: cmake -H"$HOME/cquery" -B"$home/cquery/build") - WORKING_DIRECTORY ${CLANG_DOWNLOAD_LOCATION} - OUTPUT_QUIET - ) -endif() - -set(DOWNLOADED_CLANG_DIR ${CLANG_ARCHIVE_EXTRACT_DIR} PARENT_SCOPE) - -endfunction() From cfac08b7c6cef7039ce00c3fd09c744206eeac48 Mon Sep 17 00:00:00 2001 From: Riatre Foo Date: Wed, 30 Jan 2019 23:21:40 +0800 Subject: [PATCH 07/79] Fix completion result sorting in VSCode (#210) Fix #207 --- src/messages/textDocument_completion.cc | 40 ++++++++++++++++++------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc index 0a430bb5b..7c0800d6f 100644 --- a/src/messages/textDocument_completion.cc +++ b/src/messages/textDocument_completion.cc @@ -122,6 +122,23 @@ void FilterCandidates(CompletionList &result, const std::string &complete_text, result.isIncomplete = true; } + int overwrite_len = 0; + for (auto &item : items) { + auto &edits = item.additionalTextEdits; + if (edits.size() && edits[0].range.end == begin_pos) { + Position start = edits[0].range.start, end = edits[0].range.end; + if (start.line == begin_pos.line) { + overwrite_len = + std::max(overwrite_len, end.character - start.character); + } else { + overwrite_len = -1; + break; + } + } + } + + Position overwrite_begin = {begin_pos.line, + begin_pos.character - overwrite_len}; std::string sort(4, ' '); for (auto &item : items) { item.textEdit.range = lsRange{begin_pos, end_pos}; @@ -130,17 +147,20 @@ void FilterCandidates(CompletionList &result, const std::string &complete_text, // https://github.com/Microsoft/language-server-protocol/issues/543 // Order of textEdit and additionalTextEdits is unspecified. auto &edits = item.additionalTextEdits; - if (edits.size() && edits[0].range.end == begin_pos) { - Position start = edits[0].range.start, end = edits[0].range.end; - item.textEdit.range.start = start; - item.textEdit.newText = edits[0].newText + item.textEdit.newText; - if (start.line == begin_pos.line) { - item.filterText = - buffer_line.substr(start.character, - end.character - start.character) + - item.filterText; + if (overwrite_len > 0) { + item.textEdit.range.start = overwrite_begin; + std::string orig = buffer_line.substr(overwrite_begin.character, overwrite_len); + if (edits.size() && edits[0].range.end == begin_pos && + edits[0].range.start.line == begin_pos.line) { + int cur_edit_len = edits[0].range.end.character - edits[0].range.start.character; + item.textEdit.newText = + buffer_line.substr(overwrite_begin.character, overwrite_len - cur_edit_len) + + edits[0].newText + item.textEdit.newText; + edits.erase(edits.begin()); + } else { + item.textEdit.newText = orig + item.textEdit.newText; } - edits.erase(edits.begin()); + item.filterText = orig + item.filterText; } if (item.filterText == item.label) item.filterText.clear(); From 8a1177d3d900090fa20357db85f04a521c8a115e Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Tue, 29 Jan 2019 15:47:03 +0800 Subject: [PATCH 08/79] Log {Request,Notification}Message, and timestamp change due to dependency --- src/main.cc | 3 ++- src/pipeline.cc | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main.cc b/src/main.cc index 80f05952e..0ebcfc1a9 100644 --- a/src/main.cc +++ b/src/main.cc @@ -37,7 +37,8 @@ namespace { OptionCategory C("ccls options"); opt opt_help("h", desc("Alias for -help"), cat(C)); -opt opt_verbose("v", desc("verbosity"), init(0), cat(C)); +opt opt_verbose("v", desc("verbosity, from -3 (fatal) to 2 (verbose)"), + init(0), cat(C)); opt opt_test_index("test-index", ValueOptional, init("!"), desc("run index tests"), cat(C)); diff --git a/src/pipeline.cc b/src/pipeline.cc index 481b09f79..f78310761 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -106,8 +106,8 @@ bool CacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path, { std::lock_guard lock(vfs->mutex); if (prev->mtime < vfs->state[path].timestamp) { - LOG_S(INFO) << "timestamp changed for " << path - << (from ? " (via " + *from + ")" : std::string()); + LOG_V(1) << "timestamp changed for " << path + << (from ? " (via " + *from + ")" : std::string()); return true; } } @@ -119,8 +119,8 @@ bool CacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path, if (strcmp(prev->args[i], args[i]) && sys::path::stem(args[i]) != stem) changed = true; if (changed) - LOG_S(INFO) << "args changed for " << path - << (from ? " (via " + *from + ")" : std::string()); + LOG_V(1) << "args changed for " << path + << (from ? " (via " + *from + ")" : std::string()); return changed; }; @@ -250,10 +250,14 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, if (auto mtime1 = LastWriteTime(dep.first.val().str())) { if (dep.second < *mtime1) { reparse = 2; + LOG_V(1) << "timestamp changed for " << path_to_index << " via " + << dep.first.val().str(); break; } } else { reparse = 2; + LOG_V(1) << "timestamp changed for " << path_to_index << " via " + << dep.first.val().str(); break; } } @@ -513,6 +517,10 @@ void LaunchStdin() { std::string method; ReflectMember(reader, "id", id); ReflectMember(reader, "method", method); + if (id.Valid()) + LOG_V(2) << "receive RequestMessage: " << id.value << " " << method; + else + LOG_V(2) << "receive NotificationMessage " << method; if (method.empty()) continue; bool should_exit = method == "exit"; @@ -726,6 +734,7 @@ void NotifyOrRequest(const char *method, bool request, JsonWriter writer(&w); fn(writer); w.EndObject(); + LOG_V(2) << (request ? "RequestMessage: " : "NotificationMessage: ") << method; for_stdout->PushBack(output.GetString()); } @@ -753,6 +762,8 @@ static void Reply(RequestId id, const char *key, JsonWriter writer(&w); fn(writer); w.EndObject(); + if (id.Valid()) + LOG_V(2) << "respond to RequestMessage: " << id.value; for_stdout->PushBack(output.GetString()); } From 065071c80638c7fe332cec023fb3046145afd3d3 Mon Sep 17 00:00:00 2001 From: Riatre Foo Date: Sun, 14 Oct 2018 07:58:08 +0800 Subject: [PATCH 09/79] textDocument/signatureHelp: enable documentation --- src/messages/textDocument_signatureHelp.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/messages/textDocument_signatureHelp.cc b/src/messages/textDocument_signatureHelp.cc index b0b7955c0..f7dc0e904 100644 --- a/src/messages/textDocument_signatureHelp.cc +++ b/src/messages/textDocument_signatureHelp.cc @@ -91,10 +91,8 @@ class SignatureHelpConsumer : public CodeCompleteConsumer { const char *ret_type = nullptr; SignatureInformation &ls_sig = ls_sighelp.signatures.emplace_back(); -#if LLVM_VERSION_MAJOR >= 8 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: @@ -171,7 +169,7 @@ void MessageHandler::textDocument_signatureHelp( CodeCompleteOptions CCOpts; CCOpts.IncludeGlobals = false; CCOpts.IncludeMacros = false; - CCOpts.IncludeBriefComments = false; + CCOpts.IncludeBriefComments = true; if (cache.IsCacheValid(path, begin_pos)) { SignatureHelpConsumer Consumer(CCOpts, true); cache.WithLock([&]() { Consumer.ls_sighelp = cache.result; }); From 983b40370a9f5bd55bae5ea71e297bb1220b62a3 Mon Sep 17 00:00:00 2001 From: Riatre Foo Date: Mon, 15 Oct 2018 23:25:46 +0800 Subject: [PATCH 10/79] Fix is_local for vars with non-auto storage period --- src/indexer.hh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/indexer.hh b/src/indexer.hh index cc127b1d9..e1d577ab4 100644 --- a/src/indexer.hh +++ b/src/indexer.hh @@ -244,7 +244,8 @@ struct VarDef : NameMixin { parent_kind == SymbolKind::Method || parent_kind == SymbolKind::StaticMethod || parent_kind == SymbolKind::Constructor) && - storage == clang::SC_None; + (storage == clang::SC_None || storage == clang::SC_Auto || + storage == clang::SC_Register); } std::vector GetBases() const { return {}; } From 4fd6e11c90cee73f547d8d23b65e6da545e0dcb0 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sat, 2 Feb 2019 14:14:23 +0800 Subject: [PATCH 11/79] Compute CompletionItemKind from Declaration instead of CursorKind --- src/indexer.cc | 8 +- src/messages/textDocument_completion.cc | 176 ++++++++++++------------ 2 files changed, 95 insertions(+), 89 deletions(-) diff --git a/src/indexer.cc b/src/indexer.cc index 71b6fd1ef..0b3401a41 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -107,12 +107,11 @@ Kind GetKind(const Decl *D, SymbolKind &kind) { case Decl::LinkageSpec: return Kind::Invalid; case Decl::Namespace: - kind = SymbolKind::Namespace; - return Kind::Type; case Decl::NamespaceAlias: - kind = SymbolKind::TypeAlias; + kind = SymbolKind::Namespace; return Kind::Type; case Decl::ObjCCategory: + case Decl::ObjCCategoryImpl: case Decl::ObjCImplementation: case Decl::ObjCInterface: case Decl::ObjCProtocol: @@ -154,6 +153,9 @@ Kind GetKind(const Decl *D, SymbolKind &kind) { case Decl::ClassTemplatePartialSpecialization: kind = SymbolKind::Class; return Kind::Type; + case Decl::TemplateTypeParm: + kind = SymbolKind::TypeParameter; + return Kind::Type; case Decl::TypeAlias: case Decl::Typedef: case Decl::UnresolvedUsingTypename: diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc index 7c0800d6f..07a5b485f 100644 --- a/src/messages/textDocument_completion.cc +++ b/src/messages/textDocument_completion.cc @@ -215,88 +215,95 @@ void FilterCandidates(CompletionList &result, const std::string &complete_text, finalize(); } -CompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) { - switch (cursor_kind) { - case CXCursor_UnexposedDecl: - return CompletionItemKind::Text; - - case CXCursor_StructDecl: - case CXCursor_UnionDecl: - return CompletionItemKind::Struct; - case CXCursor_ClassDecl: - return CompletionItemKind::Class; - case CXCursor_EnumDecl: - return CompletionItemKind::Enum; - case CXCursor_FieldDecl: - return CompletionItemKind::Field; - case CXCursor_EnumConstantDecl: - return CompletionItemKind::EnumMember; - case CXCursor_FunctionDecl: - return CompletionItemKind::Function; - case CXCursor_VarDecl: - case CXCursor_ParmDecl: - return CompletionItemKind::Variable; - case CXCursor_ObjCInterfaceDecl: - return CompletionItemKind::Interface; - - case CXCursor_ObjCInstanceMethodDecl: - case CXCursor_CXXMethod: - case CXCursor_ObjCClassMethodDecl: - return CompletionItemKind::Method; - - case CXCursor_FunctionTemplate: - return CompletionItemKind::Function; - - case CXCursor_Constructor: - case CXCursor_Destructor: - case CXCursor_ConversionFunction: - return CompletionItemKind::Constructor; - - case CXCursor_ObjCIvarDecl: - return CompletionItemKind::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 CompletionItemKind::Class; - - case CXCursor_ObjCPropertyDecl: - return CompletionItemKind::Property; - - case CXCursor_MacroInstantiation: - case CXCursor_MacroDefinition: - return CompletionItemKind::Text; - - case CXCursor_Namespace: - case CXCursor_NamespaceAlias: - case CXCursor_NamespaceRef: - return CompletionItemKind::Module; - - case CXCursor_MemberRef: - case CXCursor_TypeRef: - case CXCursor_ObjCSuperClassRef: - case CXCursor_ObjCProtocolRef: - case CXCursor_ObjCClassRef: - return CompletionItemKind::Reference; - - case CXCursor_NotImplemented: - case CXCursor_OverloadCandidate: - return CompletionItemKind::Text; - - case CXCursor_TemplateTypeParameter: - case CXCursor_TemplateTemplateParameter: - return CompletionItemKind::TypeParameter; +CompletionItemKind GetCompletionKind(CodeCompletionContext::Kind K, + const CodeCompletionResult &R) { + switch (R.Kind) { + case CodeCompletionResult::RK_Declaration: { + const Decl *D = R.Declaration; + switch (D->getKind()) { + case Decl::LinkageSpec: + return CompletionItemKind::Keyword; + case Decl::Namespace: + case Decl::NamespaceAlias: + return CompletionItemKind::Module; + case Decl::ObjCCategory: + case Decl::ObjCCategoryImpl: + case Decl::ObjCImplementation: + case Decl::ObjCInterface: + case Decl::ObjCProtocol: + return CompletionItemKind::Interface; + case Decl::ObjCMethod: + return CompletionItemKind::Method; + case Decl::ObjCProperty: + return CompletionItemKind::Property; + case Decl::ClassTemplate: + return CompletionItemKind::Class; + case Decl::FunctionTemplate: + return CompletionItemKind::Function; + case Decl::TypeAliasTemplate: + return CompletionItemKind::Class; + case Decl::VarTemplate: + if (cast(D)->getTemplatedDecl()->isConstexpr()) + return CompletionItemKind::Constant; + return CompletionItemKind::Variable; + case Decl::TemplateTemplateParm: + return CompletionItemKind::TypeParameter; + case Decl::Enum: + return CompletionItemKind::Enum; + case Decl::CXXRecord: + case Decl::Record: + if (auto *RD = dyn_cast(D)) + if (RD->getTagKind() == TTK_Struct) + return CompletionItemKind::Struct; + return CompletionItemKind::Class; + case Decl::TemplateTypeParm: + case Decl::TypeAlias: + case Decl::Typedef: + return CompletionItemKind::TypeParameter; + case Decl::Binding: + return CompletionItemKind::Variable; + case Decl::Field: + case Decl::ObjCIvar: + return CompletionItemKind::Field; + case Decl::Function: + return CompletionItemKind::Function; + case Decl::CXXMethod: + return CompletionItemKind::Method; + case Decl::CXXConstructor: + return CompletionItemKind::Constructor; + case Decl::CXXConversion: + case Decl::CXXDestructor: + return CompletionItemKind::Method; + case Decl::Var: + case Decl::Decomposition: + case Decl::ImplicitParam: + case Decl::ParmVar: + case Decl::VarTemplateSpecialization: + case Decl::VarTemplatePartialSpecialization: + if (cast(D)->isConstexpr()) + return CompletionItemKind::Constant; + return CompletionItemKind::Variable; + case Decl::EnumConstant: + return CompletionItemKind::EnumMember; + case Decl::IndirectField: + return CompletionItemKind::Field; - default: - LOG_S(WARNING) << "Unhandled completion kind " << cursor_kind; - return CompletionItemKind::Text; + default: + LOG_S(WARNING) << "Unhandled " << int(D->getKind()); + return CompletionItemKind::Text; + } + break; + } + case CodeCompletionResult::RK_Keyword: + return CompletionItemKind::Keyword; + case CodeCompletionResult::RK_Macro: + return CompletionItemKind::Reference; + case CodeCompletionResult::RK_Pattern: +#if LLVM_VERSION_MAJOR >= 8 + if (K == CodeCompletionContext::CCC_IncludedFile) + return CompletionItemKind::File; +#endif + return CompletionItemKind::Snippet; } } @@ -417,15 +424,12 @@ class CompletionConsumer : public CodeCompleteConsumer { NK == DeclarationName::CXXLiteralOperatorName) continue; } + CodeCompletionString *CCS = R.CreateCodeCompletionString( S, Context, getAllocator(), getCodeCompletionTUInfo(), includeBriefComments()); CompletionItem ls_item; - ls_item.kind = GetCompletionKind(R.CursorKind); -#if LLVM_VERSION_MAJOR >= 8 - if (Context.getKind() == CodeCompletionContext::CCC_IncludedFile) - ls_item.kind = CompletionItemKind::File; -#endif + ls_item.kind = GetCompletionKind(Context.getKind(), R); if (const char *brief = CCS->getBriefComment()) ls_item.documentation = brief; ls_item.detail = CCS->getParentContextName().str(); From f54a350e0cd4407e91e7f8428783ca28911d89e8 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sun, 10 Feb 2019 11:58:18 +0800 Subject: [PATCH 12/79] GetFallback: append clang.extraArgs When compile_commands.json is absent, GetFallback is called to get default clang command line when there is no .ccls or .ccls is empty. --- src/project.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/project.cc b/src/project.cc index 71f43bdba..d04d67c28 100644 --- a/src/project.cc +++ b/src/project.cc @@ -215,6 +215,8 @@ std::vector GetFallback(const std::string path) { std::vector argv{"clang"}; if (sys::path::extension(path) == ".h") argv.push_back("-xobjective-c++-header"); + for (const std::string &arg : g_config->clang.extraArgs) + argv.push_back(Intern(arg)); argv.push_back(Intern(path)); return argv; } From 7bc952e543f6d55db41d5e8f5f1cc5dee6636fe4 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sun, 10 Feb 2019 18:17:07 +0800 Subject: [PATCH 13/79] Add initialization option `capabilities.*` and index.maxInitializerLines indexer.cc: use index.maxInitializerLines instead of kInitializerMaxLines messages/initialize.cc: some ServerCapabilities are toggable: documentOnTypeFormattingProvider.firstTriggerCharacter foldingRangeProvider workspace.workspaceFolders.supported --- src/config.hh | 42 ++++++++++++++++++++++++++++++++------ src/indexer.cc | 6 ++---- src/messages/initialize.cc | 28 +++++++++++-------------- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/config.hh b/src/config.hh index 073071db9..e1d44c78a 100644 --- a/src/config.hh +++ b/src/config.hh @@ -42,6 +42,25 @@ struct Config { // member has changed. SerializeFormat cacheFormat = SerializeFormat::Binary; + struct ServerCap { + struct DocumentOnTypeFormattingOptions { + std::string firstTriggerCharacter = "}"; + std::vector moreTriggerCharacter; + } documentOnTypeFormattingProvider; + + // Set to false if you don't want folding ranges. + bool foldingRangeProvider = true; + + struct Workspace { + struct WorkspaceFolders { + // Set to false if you don't want workspace folders. + bool supported = true; + + bool changeNotifications = true; + } workspaceFolders; + } workspace; + } capabilities; + struct Clang { // Arguments that should be excluded, e.g. ["-fopenmp", "-Wall"] // @@ -207,6 +226,10 @@ struct Config { std::vector initialBlacklist; std::vector initialWhitelist; + // If a variable initializer/macro replacement-list has fewer than this many + // lines, include the initializer in detailed_name. + int maxInitializerLines = 5; + // If not 0, a file will be indexed in each tranlation unit that includes it. int multiVersion = 0; @@ -255,6 +278,13 @@ struct Config { int maxNum = 2000; } xref; }; +REFLECT_STRUCT(Config::ServerCap::DocumentOnTypeFormattingOptions, + firstTriggerCharacter, moreTriggerCharacter); +REFLECT_STRUCT(Config::ServerCap::Workspace::WorkspaceFolders, supported, + changeNotifications); +REFLECT_STRUCT(Config::ServerCap::Workspace, workspaceFolders); +REFLECT_STRUCT(Config::ServerCap, documentOnTypeFormattingProvider, + foldingRangeProvider, workspace); REFLECT_STRUCT(Config::Clang, excludeArgs, extraArgs, pathMappings, resourceDir); REFLECT_STRUCT(Config::ClientCapability, hierarchicalDocumentSymbolSupport, @@ -269,17 +299,17 @@ REFLECT_STRUCT(Config::Diagnostics, blacklist, onChange, onOpen, onSave, spellChecking, whitelist) REFLECT_STRUCT(Config::Highlight, largeFileSize, lsRanges, blacklist, whitelist) REFLECT_STRUCT(Config::Index, blacklist, comments, initialBlacklist, - initialWhitelist, multiVersion, multiVersionBlacklist, - multiVersionWhitelist, onChange, threads, trackDependency, - whitelist); + initialWhitelist, maxInitializerLines, multiVersion, + multiVersionBlacklist, multiVersionWhitelist, onChange, threads, + trackDependency, whitelist); REFLECT_STRUCT(Config::Request, timeout); REFLECT_STRUCT(Config::Session, maxNum); REFLECT_STRUCT(Config::WorkspaceSymbol, caseSensitivity, maxNum, sort); REFLECT_STRUCT(Config::Xref, maxNum); REFLECT_STRUCT(Config, compilationDatabaseCommand, compilationDatabaseDirectory, - cacheDirectory, cacheFormat, clang, client, codeLens, completion, - diagnostics, highlight, index, request, session, workspaceSymbol, - xref); + cacheDirectory, cacheFormat, capabilities, clang, client, + codeLens, completion, diagnostics, highlight, index, request, + session, workspaceSymbol, xref); extern Config *g_config; diff --git a/src/indexer.cc b/src/indexer.cc index 0b3401a41..9a7adea2c 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -29,8 +29,6 @@ using namespace clang; namespace ccls { namespace { -constexpr int kInitializerMaxLines = 3; - GroupMatch *multiVersionMatcher; struct File { @@ -586,7 +584,7 @@ class IndexDataConsumer : public index::IndexDataConsumer { if (L.isMacroID() || !SM.isBeforeInTranslationUnit(L, R.getBegin())) return; StringRef Buf = GetSourceInRange(SM, Lang, R); - Twine Init = Buf.count('\n') <= kInitializerMaxLines - 1 + Twine Init = Buf.count('\n') <= g_config->index.maxInitializerLines - 1 ? Buf.size() && Buf[0] == ':' ? Twine(" ", Buf) : Twine(" = ", Buf) : Twine(); @@ -1085,7 +1083,7 @@ class IndexPPCallbacks : public PPCallbacks { var.def.short_name_size = Name.size(); StringRef Buf = GetSourceInRange(SM, Lang, R); var.def.hover = - Intern(Buf.count('\n') <= kInitializerMaxLines - 1 + Intern(Buf.count('\n') <= g_config->index.maxInitializerLines - 1 ? Twine("#define ", GetSourceInRange(SM, Lang, R)).str() : Twine("#define ", Name).str()); } diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 7cccd1b88..1a3c40fca 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -76,10 +76,8 @@ struct ServerCap { } codeLensProvider; bool documentFormattingProvider = true; bool documentRangeFormattingProvider = true; - struct DocumentOnTypeFormattingOptions { - std::string firstTriggerCharacter = "}"; - std::vector moreTriggerCharacter; - } documentOnTypeFormattingProvider; + Config::ServerCap::DocumentOnTypeFormattingOptions + documentOnTypeFormattingProvider; bool renameProvider = true; struct DocumentLinkOptions { bool resolveProvider = true; @@ -89,28 +87,18 @@ struct ServerCap { struct ExecuteCommandOptions { std::vector commands = {ccls_xref}; } executeCommandProvider; - struct Workspace { - struct WorkspaceFolders { - bool supported = true; - bool changeNotifications = true; - } workspaceFolders; - } workspace; + Config::ServerCap::Workspace workspace; }; REFLECT_STRUCT(ServerCap::CodeActionOptions, codeActionKinds); REFLECT_STRUCT(ServerCap::CodeLensOptions, resolveProvider); REFLECT_STRUCT(ServerCap::CompletionOptions, resolveProvider, triggerCharacters); REFLECT_STRUCT(ServerCap::DocumentLinkOptions, resolveProvider); -REFLECT_STRUCT(ServerCap::DocumentOnTypeFormattingOptions, - firstTriggerCharacter, moreTriggerCharacter); REFLECT_STRUCT(ServerCap::ExecuteCommandOptions, commands); REFLECT_STRUCT(ServerCap::SaveOptions, includeText); REFLECT_STRUCT(ServerCap::SignatureHelpOptions, triggerCharacters); REFLECT_STRUCT(ServerCap::TextDocumentSyncOptions, openClose, change, willSave, willSaveWaitUntil, save); -REFLECT_STRUCT(ServerCap::Workspace::WorkspaceFolders, supported, - changeNotifications); -REFLECT_STRUCT(ServerCap::Workspace, workspaceFolders); REFLECT_STRUCT(ServerCap, textDocumentSync, hoverProvider, completionProvider, signatureHelpProvider, declarationProvider, definitionProvider, implementationProvider, typeDefinitionProvider, @@ -318,7 +306,15 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { // Send initialization before starting indexers, so we don't send a // status update too early. - reply(InitializeResult{}); + { + InitializeResult result; + auto &c = result.capabilities; + c.documentOnTypeFormattingProvider = + g_config->capabilities.documentOnTypeFormattingProvider; + c.foldingRangeProvider = g_config->capabilities.foldingRangeProvider; + c.workspace = g_config->capabilities.workspace; + reply(result); + } // Set project root. EnsureEndsInSlash(project_path); From 4f9e7b219e280f03b1ea255db7519bb7618bf3ca Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sat, 16 Feb 2019 17:23:47 +0800 Subject: [PATCH 14/79] indexer: change Pos computation from byte offset to UTF-8 encoded code point offset --- src/clang_tu.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/clang_tu.cc b/src/clang_tu.cc index 449b7912a..d10ed7465 100644 --- a/src/clang_tu.cc +++ b/src/clang_tu.cc @@ -33,6 +33,16 @@ static Pos Decomposed2LineAndCol(const SourceManager &SM, std::pair I) { int l = (int)SM.getLineNumber(I.first, I.second) - 1, c = (int)SM.getColumnNumber(I.first, I.second) - 1; + bool Invalid = false; + StringRef Buf = SM.getBufferData(I.first, &Invalid); + if (!Invalid) { + StringRef P = Buf.substr(I.second - c, c); + c = 0; + for (size_t i = 0; i < P.size(); ) + if (c++, (uint8_t)P[i++] >= 128) + while (i < P.size() && (uint8_t)P[i] >= 128 && (uint8_t)P[i] < 192) + i++; + } return {(int16_t)std::min(l, INT16_MAX), (int16_t)std::min(c, INT16_MAX)}; } From 7e0d8a4357ffae9728335724da47349db19ae44b Mon Sep 17 00:00:00 2001 From: Leszek Swirski Date: Thu, 21 Feb 2019 02:23:21 +0100 Subject: [PATCH 15/79] Use DiagnosticRelatedInformation if client supports publishDiagnostics.relatedInformation (#276) In clients that support DiagnosticRelatedInformation, display clang notes as these nested diagnostics rather than appending them to the parent diagnostic's message. Behaviour for clients that don't support related information should be unchanged. --- src/config.hh | 6 +++-- src/lsp.hh | 6 +++++ src/message_handler.hh | 3 ++- src/messages/initialize.cc | 9 ++++++- src/sema_manager.cc | 49 ++++++++++++++++++++++++-------------- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/config.hh b/src/config.hh index e1d44c78a..94830b66f 100644 --- a/src/config.hh +++ b/src/config.hh @@ -90,6 +90,8 @@ struct Config { } clang; struct ClientCapability { + // TextDocumentClientCapabilities.publishDiagnostics.relatedInformation + bool diagnosticsRelatedInformation = true; // TextDocumentClientCapabilities.documentSymbol.hierarchicalDocumentSymbolSupport bool hierarchicalDocumentSymbolSupport = true; // TextDocumentClientCapabilities.definition.linkSupport @@ -287,8 +289,8 @@ REFLECT_STRUCT(Config::ServerCap, documentOnTypeFormattingProvider, foldingRangeProvider, workspace); REFLECT_STRUCT(Config::Clang, excludeArgs, extraArgs, pathMappings, resourceDir); -REFLECT_STRUCT(Config::ClientCapability, hierarchicalDocumentSymbolSupport, - linkSupport, snippetSupport); +REFLECT_STRUCT(Config::ClientCapability, diagnosticsRelatedInformation, + hierarchicalDocumentSymbolSupport, linkSupport, snippetSupport); REFLECT_STRUCT(Config::CodeLens, localVariables); REFLECT_STRUCT(Config::Completion::Include, blacklist, maxPathSize, suffixWhitelist, whitelist); diff --git a/src/lsp.hh b/src/lsp.hh index ad77f993b..8c507904c 100644 --- a/src/lsp.hh +++ b/src/lsp.hh @@ -226,12 +226,18 @@ struct WorkspaceFolder { enum class MessageType : int { Error = 1, Warning = 2, Info = 3, Log = 4 }; REFLECT_UNDERLYING(MessageType) +struct DiagnosticRelatedInformation { + Location location; + std::string message; +}; + struct Diagnostic { lsRange range; int severity = 0; int code = 0; std::string source = "ccls"; std::string message; + std::vector relatedInformation; std::vector fixits_; }; diff --git a/src/message_handler.hh b/src/message_handler.hh index 6c040a29f..ef428abcb 100644 --- a/src/message_handler.hh +++ b/src/message_handler.hh @@ -183,7 +183,8 @@ REFLECT_STRUCT(TextDocumentIdentifier, uri); REFLECT_STRUCT(TextDocumentItem, uri, languageId, version, text); REFLECT_STRUCT(TextEdit, range, newText); REFLECT_STRUCT(VersionedTextDocumentIdentifier, uri, version); -REFLECT_STRUCT(Diagnostic, range, severity, code, source, message); +REFLECT_STRUCT(DiagnosticRelatedInformation, location, message); +REFLECT_STRUCT(Diagnostic, range, severity, code, source, message, relatedInformation); REFLECT_STRUCT(ShowMessageParam, type, message); REFLECT_UNDERLYING_B(LanguageId); diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 1a3c40fca..3798a19d0 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -160,6 +160,10 @@ struct TextDocumentClientCap { struct DocumentSymbol { bool hierarchicalDocumentSymbolSupport = false; } documentSymbol; + + struct PublishDiagnostics { + bool relatedInformation = false; + } publishDiagnostics; }; REFLECT_STRUCT(TextDocumentClientCap::Completion::CompletionItem, @@ -168,7 +172,8 @@ REFLECT_STRUCT(TextDocumentClientCap::Completion, completionItem); REFLECT_STRUCT(TextDocumentClientCap::DocumentSymbol, hierarchicalDocumentSymbolSupport); REFLECT_STRUCT(TextDocumentClientCap::LinkSupport, linkSupport); -REFLECT_STRUCT(TextDocumentClientCap, completion, definition, documentSymbol); +REFLECT_STRUCT(TextDocumentClientCap::PublishDiagnostics, relatedInformation); +REFLECT_STRUCT(TextDocumentClientCap, completion, definition, documentSymbol, publishDiagnostics); struct ClientCap { WorkspaceClientCap workspace; @@ -295,6 +300,8 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { capabilities.textDocument.definition.linkSupport; g_config->client.snippetSupport &= capabilities.textDocument.completion.completionItem.snippetSupport; + g_config->client.diagnosticsRelatedInformation &= + capabilities.textDocument.publishDiagnostics.relatedInformation; didChangeWatchedFiles = capabilities.workspace.didChangeWatchedFiles.dynamicRegistration; diff --git a/src/sema_manager.cc b/src/sema_manager.cc index d761d69b7..0791c59cb 100644 --- a/src/sema_manager.cc +++ b/src/sema_manager.cc @@ -503,7 +503,7 @@ llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) { } } -void printDiag(llvm::raw_string_ostream &OS, const DiagBase &d) { +void PrintDiag(llvm::raw_string_ostream &OS, const DiagBase &d) { if (d.concerned) OS << llvm::sys::path::filename(d.file); else @@ -601,27 +601,40 @@ void *DiagnosticMain(void *manager_) { for (auto &d : diags) { if (!d.concerned) continue; - std::string buf; - llvm::raw_string_ostream OS(buf); Diagnostic &ls_diag = ls_diags.emplace_back(); Fill(d, ls_diag); ls_diag.fixits_ = d.edits; - OS << d.message; - for (auto &n : d.notes) { - OS << "\n\n"; - printDiag(OS, n); - } - OS.flush(); - ls_diag.message = std::move(buf); - for (auto &n : d.notes) { - if (!n.concerned) - continue; - Diagnostic &ls_diag1 = ls_diags.emplace_back(); - Fill(n, ls_diag1); - OS << n.message << "\n\n"; - printDiag(OS, d); + if (g_config->client.diagnosticsRelatedInformation) { + ls_diag.message = d.message; + for (const Note &n : d.notes) { + SmallString<256> Str(n.file); + llvm::sys::path::remove_dots(Str, true); + Location loc{DocumentUri::FromPath(Str.str()), + lsRange{{n.range.start.line, n.range.start.column}, + {n.range.end.line, n.range.end.column}}}; + ls_diag.relatedInformation.push_back({loc, n.message}); + } + } else { + std::string buf; + llvm::raw_string_ostream OS(buf); + OS << d.message; + for (const Note &n : d.notes) { + OS << "\n\n"; + PrintDiag(OS, n); + } OS.flush(); - ls_diag1.message = std::move(buf); + ls_diag.message = std::move(buf); + for (const Note &n : d.notes) { + if (!n.concerned) + continue; + Diagnostic &ls_diag1 = ls_diags.emplace_back(); + Fill(n, ls_diag1); + buf.clear(); + OS << n.message << "\n\n"; + PrintDiag(OS, d); + OS.flush(); + ls_diag1.message = std::move(buf); + } } } From f700ac7b4c97bdcf01a71d24b5b2e81d43982530 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Thu, 21 Feb 2019 23:46:20 +0800 Subject: [PATCH 16/79] Add cache.{hierarchicalPath,retainInMemory} cache.hierarchicalPath: store cache files as $directory/a/b/c.cc.blob to work around NAME_MAX limitation. cache.retainInMemory: after this number of loads, keep a copy of file index in memory. If set to 1, it avoids cache corruption if the index file is changed after the initial load, which may happen if several language clients open the same project and share the same cache directory. Also rename cacheDirectory cacheFormat to cache.{directory,format} --- src/config.hh | 56 ++++++++++++++++++++--------- src/messages/initialize.cc | 17 +++++---- src/messages/textDocument_did.cc | 1 + src/messages/workspace.cc | 4 +-- src/pipeline.cc | 61 +++++++++++++++++++++----------- src/pipeline.hh | 6 ++-- src/utils.cc | 7 ++-- 7 files changed, 101 insertions(+), 51 deletions(-) diff --git a/src/config.hh b/src/config.hh index 94830b66f..008c1e24d 100644 --- a/src/config.hh +++ b/src/config.hh @@ -28,19 +28,41 @@ struct Config { std::string compilationDatabaseCommand; // Directory containing compile_commands.json. std::string compilationDatabaseDirectory; - // Cache directory for indexed files, either absolute or relative to the - // project root. - // If empty, cache will be stored in memory. - std::string cacheDirectory = ".ccls-cache"; - // Cache serialization format. - // - // "json" generates `cacheDirectory/.../xxx.json` files which can be pretty - // printed with jq. - // - // "binary" uses a compact binary serialization format. - // It is not schema-aware and you need to re-index whenever an internal struct - // member has changed. - SerializeFormat cacheFormat = SerializeFormat::Binary; + + struct Cache { + // Cache directory for indexed files, either absolute or relative to the + // project root. + // + // If empty, retainInMemory will be set to 1 and cache will be stored in + // memory. + std::string directory = ".ccls-cache"; + + // Cache serialization format. + // + // "json" generates $directory/.../xxx.json files which can be pretty + // printed with jq. + // + // "binary" uses a compact binary serialization format. + // It is not schema-aware and you need to re-index whenever an internal + // struct member has changed. + SerializeFormat format = SerializeFormat::Binary; + + // If false, store cache files as $directory/@a@b/c.cc.blob + // + // If true, $directory/a/b/c.cc.blob. If cache.directory is absolute, make + // sure different projects use different cache.directory as they would have + // conflicting cache files for system headers. + bool hierarchicalPath = false; + + // After this number of loads, keep a copy of file index in memory (which + // increases memory usage). During incremental updates, the index subtracted + // will come from the in-memory copy, instead of the on-disk file. + // + // The initial load or a save action is counted as one load. + // 0: never retain; 1: retain after initial load; 2: retain after 2 loads + // (initial load+first save) + int retainInMemory = 2; + } cache; struct ServerCap { struct DocumentOnTypeFormattingOptions { @@ -280,6 +302,8 @@ struct Config { int maxNum = 2000; } xref; }; +REFLECT_STRUCT(Config::Cache, directory, format, hierarchicalPath, + retainInMemory); REFLECT_STRUCT(Config::ServerCap::DocumentOnTypeFormattingOptions, firstTriggerCharacter, moreTriggerCharacter); REFLECT_STRUCT(Config::ServerCap::Workspace::WorkspaceFolders, supported, @@ -309,9 +333,9 @@ REFLECT_STRUCT(Config::Session, maxNum); REFLECT_STRUCT(Config::WorkspaceSymbol, caseSensitivity, maxNum, sort); REFLECT_STRUCT(Config::Xref, maxNum); REFLECT_STRUCT(Config, compilationDatabaseCommand, compilationDatabaseDirectory, - cacheDirectory, cacheFormat, capabilities, clang, client, - codeLens, completion, diagnostics, highlight, index, request, - session, workspaceSymbol, xref); + cache, capabilities, clang, client, codeLens, completion, + diagnostics, highlight, index, request, session, workspaceSymbol, + xref); extern Config *g_config; diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 3798a19d0..d064891e8 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -282,12 +282,12 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { Reflect(json_writer, *g_config); LOG_S(INFO) << "initializationOptions: " << output.GetString(); - if (g_config->cacheDirectory.size()) { - SmallString<256> Path(g_config->cacheDirectory); + if (g_config->cache.directory.size()) { + SmallString<256> Path(g_config->cache.directory); sys::fs::make_absolute(project_path, Path); // Use upper case for the Driver letter on Windows. - g_config->cacheDirectory = NormalizePath(Path.str()); - EnsureEndsInSlash(g_config->cacheDirectory); + g_config->cache.directory = NormalizePath(Path.str()); + EnsureEndsInSlash(g_config->cache.directory); } } @@ -334,13 +334,16 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { } if (param.workspaceFolders.empty()) g_config->workspaceFolders.push_back(project_path); - if (g_config->cacheDirectory.size()) + + if (g_config->cache.directory.empty()) + g_config->cache.retainInMemory = 1; + else if (!g_config->cache.hierarchicalPath) for (const std::string &folder : g_config->workspaceFolders) { // Create two cache directories for files inside and outside of the // project. std::string escaped = EscapeFileName(folder.substr(0, folder.size() - 1)); - sys::fs::create_directories(g_config->cacheDirectory + escaped); - sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped); + sys::fs::create_directories(g_config->cache.directory + escaped); + sys::fs::create_directories(g_config->cache.directory + '@' + escaped); } idx::Init(); diff --git a/src/messages/textDocument_did.cc b/src/messages/textDocument_did.cc index 1f2a47de3..84814e191 100644 --- a/src/messages/textDocument_did.cc +++ b/src/messages/textDocument_did.cc @@ -23,6 +23,7 @@ void MessageHandler::textDocument_didClose(TextDocumentParam ¶m) { std::string path = param.textDocument.uri.GetPath(); wfiles->OnClose(path); manager->OnClose(path); + pipeline::RemoveCache(path); } void MessageHandler::textDocument_didOpen(DidOpenTextDocumentParam ¶m) { diff --git a/src/messages/workspace.cc b/src/messages/workspace.cc index baea03c2b..c7af61364 100644 --- a/src/messages/workspace.cc +++ b/src/messages/workspace.cc @@ -34,8 +34,8 @@ void MessageHandler::workspace_didChangeWatchedFiles( DidChangeWatchedFilesParam ¶m) { for (auto &event : param.changes) { std::string path = event.uri.GetPath(); - if ((g_config->cacheDirectory.size() && - StringRef(path).startswith(g_config->cacheDirectory)) || + if ((g_config->cache.directory.size() && + StringRef(path).startswith(g_config->cache.directory)) || lookupExtension(path).first == LanguageId::Unknown) return; for (std::string cur = path; cur.size(); cur = sys::path::parent_path(cur)) diff --git a/src/pipeline.cc b/src/pipeline.cc index f78310761..4f48b0a0b 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -45,7 +45,7 @@ void VFS::Clear() { state.clear(); } -bool VFS::Loaded(const std::string &path) { +int VFS::Loaded(const std::string &path) { std::lock_guard lock(mutex); return state[path].loaded; } @@ -125,7 +125,7 @@ bool CacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path, }; std::string AppendSerializationFormat(const std::string &base) { - switch (g_config->cacheFormat) { + switch (g_config->cache.format) { case SerializeFormat::Binary: return base + ".blob"; case SerializeFormat::Json: @@ -133,27 +133,35 @@ std::string AppendSerializationFormat(const std::string &base) { } } -std::string GetCachePath(const std::string &source_file) { +std::string GetCachePath(const std::string &src) { + if (g_config->cache.hierarchicalPath) { + std::string ret = src[0] == '/' ? src.substr(1) : src; +#ifdef _WIN32 + std::replace(ret.begin(), ret.end(), ':', '@'); +#endif + return g_config->cache.directory + ret; + } for (auto &root : g_config->workspaceFolders) - if (StringRef(source_file).startswith(root)) { + if (StringRef(src).startswith(root)) { auto len = root.size(); - return g_config->cacheDirectory + + return g_config->cache.directory + EscapeFileName(root.substr(0, len - 1)) + '/' + - EscapeFileName(source_file.substr(len)); + EscapeFileName(src.substr(len)); } - return g_config->cacheDirectory + '@' + + return g_config->cache.directory + '@' + EscapeFileName(g_config->fallbackFolder.substr( 0, g_config->fallbackFolder.size() - 1)) + - '/' + EscapeFileName(source_file); + '/' + EscapeFileName(src); } std::unique_ptr RawCacheLoad(const std::string &path) { - if (g_config->cacheDirectory.empty()) { + if (g_config->cache.retainInMemory) { std::shared_lock lock(g_index_mutex); auto it = g_index.find(path); - if (it == g_index.end()) + if (it != g_index.end()) + return std::make_unique(it->second.index); + if (g_config->cache.directory.empty()) return nullptr; - return std::make_unique(it->second.index); } std::string cache_path = GetCachePath(path); @@ -163,7 +171,7 @@ std::unique_ptr RawCacheLoad(const std::string &path) { if (!file_content || !serialized_indexed_content) return nullptr; - return ccls::Deserialize(g_config->cacheFormat, path, + return ccls::Deserialize(g_config->cache.format, path, *serialized_indexed_content, *file_content, IndexFile::kMajorVersion); } @@ -275,7 +283,7 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, request.mode != IndexMode::NonInteractive); { std::lock_guard lock1(vfs->mutex); - vfs->state[path_to_index].loaded = true; + vfs->state[path_to_index].loaded++; } lock.unlock(); @@ -292,7 +300,7 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, VFS::State &st = vfs->state[path]; if (st.loaded) continue; - st.loaded = true; + st.loaded++; st.timestamp = prev->mtime; } IndexUpdate update = IndexUpdate::CreateDelta(nullptr, prev.get()); @@ -355,31 +363,37 @@ bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, << " (delta: " << !!prev << ")"; { std::lock_guard lock(GetFileMutex(path)); - if (vfs->Loaded(path)) + int loaded = vfs->Loaded(path), retain = g_config->cache.retainInMemory; + if (loaded) prev = RawCacheLoad(path); else prev.reset(); - if (g_config->cacheDirectory.empty()) { + if (retain > 0 && retain <= loaded + 1) { std::lock_guard 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); - } else { + } + if (g_config->cache.directory.size()) { std::string cache_path = GetCachePath(path); if (deleted) { (void)sys::fs::remove(cache_path); (void)sys::fs::remove(AppendSerializationFormat(cache_path)); } else { + if (g_config->cache.hierarchicalPath) + sys::fs::create_directories( + sys::path::parent_path(cache_path, sys::path::Style::posix), + true); WriteToFile(cache_path, curr->file_contents); WriteToFile(AppendSerializationFormat(cache_path), - Serialize(g_config->cacheFormat, *curr)); + Serialize(g_config->cache.format, *curr)); } } on_indexed->PushBack(IndexUpdate::CreateDelta(prev.get(), curr.get()), request.mode != IndexMode::NonInteractive); { std::lock_guard lock1(vfs->mutex); - vfs->state[path].loaded = true; + vfs->state[path].loaded++; } if (entry.id >= 0) { std::lock_guard lock(project->mtx); @@ -706,8 +720,15 @@ void Index(const std::string &path, const std::vector &args, mode != IndexMode::NonInteractive); } +void RemoveCache(const std::string &path) { + if (g_config->cache.directory.size()) { + std::lock_guard lock(g_index_mutex); + g_index.erase(path); + } +} + std::optional LoadIndexedContent(const std::string &path) { - if (g_config->cacheDirectory.empty()) { + if (g_config->cache.directory.empty()) { std::shared_lock lock(g_index_mutex); auto it = g_index.find(path); if (it == g_index.end()) diff --git a/src/pipeline.hh b/src/pipeline.hh index 0ec480900..5dd35c087 100644 --- a/src/pipeline.hh +++ b/src/pipeline.hh @@ -22,13 +22,13 @@ struct VFS { struct State { int64_t timestamp; int step; - bool loaded; + int loaded; }; std::unordered_map state; std::mutex mutex; void Clear(); - bool Loaded(const std::string &path); + int Loaded(const std::string &path); bool Stamp(const std::string &path, int64_t ts, int step); }; @@ -55,7 +55,7 @@ void Standalone(const std::string &root); void Index(const std::string &path, const std::vector &args, IndexMode mode, bool must_exist, RequestId id = {}); - +void RemoveCache(const std::string &path); std::optional LoadIndexedContent(const std::string& path); void NotifyOrRequest(const char *method, bool request, diff --git a/src/utils.cc b/src/utils.cc index 6dc288b1e..c291de01d 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -112,9 +112,10 @@ void EnsureEndsInSlash(std::string &path) { std::string EscapeFileName(std::string path) { bool slash = path.size() && path.back() == '/'; - for (char &c : path) - if (c == '\\' || c == '/' || c == ':') - c = '@'; +#ifdef _WIN32 + std::replace(path.begin(), path.end(), ':', '@'); +#endif + std::replace(path.begin(), path.end(), '/', '@'); if (slash) path += '/'; return path; From 9438be32c6f8fb56cac6b35159e58f3dcf83afd8 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 22 Feb 2019 10:59:05 +0800 Subject: [PATCH 17/79] indexer: index TemplateTypeParmDecl and ParmVarDecl in declarations for clang >= 9 Index ParmVarDecl in declarations if index.parametersInDeclarations is true And support some unhandled Decl::Kind --- src/config.hh | 7 ++++-- src/indexer.cc | 31 ++++++++++++++++++++----- src/messages/textDocument_completion.cc | 3 +++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/config.hh b/src/config.hh index 008c1e24d..2761da51a 100644 --- a/src/config.hh +++ b/src/config.hh @@ -266,6 +266,9 @@ struct Config { // May be too slow for big projects, so it is off by default. bool onChange = false; + // If true, index parameters in declarations. + bool parametersInDeclarations = true; + // Number of indexer threads. If 0, 80% of cores are used. int threads = 0; @@ -326,8 +329,8 @@ REFLECT_STRUCT(Config::Diagnostics, blacklist, onChange, onOpen, onSave, REFLECT_STRUCT(Config::Highlight, largeFileSize, lsRanges, blacklist, whitelist) REFLECT_STRUCT(Config::Index, blacklist, comments, initialBlacklist, initialWhitelist, maxInitializerLines, multiVersion, - multiVersionBlacklist, multiVersionWhitelist, onChange, threads, - trackDependency, whitelist); + multiVersionBlacklist, multiVersionWhitelist, onChange, + parametersInDeclarations, threads, trackDependency, whitelist); REFLECT_STRUCT(Config::Request, timeout); REFLECT_STRUCT(Config::Session, maxNum); REFLECT_STRUCT(Config::WorkspaceSymbol, caseSensitivity, maxNum, sort); diff --git a/src/indexer.cc b/src/indexer.cc index 9a7adea2c..894286f3e 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -159,6 +159,9 @@ Kind GetKind(const Decl *D, SymbolKind &kind) { case Decl::UnresolvedUsingTypename: kind = SymbolKind::TypeAlias; return Kind::Type; + case Decl::Using: + kind = SymbolKind::Null; // ignored + return Kind::Invalid; case Decl::Binding: kind = SymbolKind::Variable; return Kind::Var; @@ -181,6 +184,10 @@ Kind GetKind(const Decl *D, SymbolKind &kind) { case Decl::CXXDestructor: kind = SymbolKind::Method; return Kind::Func; + case Decl::NonTypeTemplateParm: + // ccls extension + kind = SymbolKind::Parameter; + return Kind::Var; case Decl::Var: case Decl::Decomposition: kind = SymbolKind::Variable; @@ -204,7 +211,6 @@ Kind GetKind(const Decl *D, SymbolKind &kind) { return Kind::Invalid; default: - LOG_S(INFO) << "unhandled " << int(D->getKind()); return Kind::Invalid; } } @@ -722,7 +728,7 @@ class IndexDataConsumer : public index::IndexDataConsumer { IndexFunc *func = nullptr; IndexType *type = nullptr; IndexVar *var = nullptr; - SymbolKind ls_kind; + SymbolKind ls_kind = SymbolKind::Unknown; Kind kind = GetKind(D, ls_kind); if (is_def) @@ -772,8 +778,10 @@ class IndexDataConsumer : public index::IndexDataConsumer { }; switch (kind) { case Kind::Invalid: - LOG_S(INFO) << "Unhandled " << int(D->getKind()) << " " << info->qualified - << " in " << db->path << ":" << loc.start.line + 1; + if (ls_kind == SymbolKind::Unknown) + LOG_S(INFO) << "Unhandled " << int(D->getKind()) << " " + << info->qualified << " in " << db->path << ":" + << (loc.start.line + 1) << ":" << (loc.start.column + 1); return true; case Kind::File: return true; @@ -807,8 +815,12 @@ class IndexDataConsumer : public index::IndexDataConsumer { do_def_decl(type); if (Spell != Loc) AddMacroUse(db, SM, usr, Kind::Type, Spell); - if (type->def.detailed_name[0] == '\0' && info->short_name.size()) - SetName(D, info->short_name, info->qualified, type->def); + if (type->def.detailed_name[0] == '\0' && info->short_name.size()) { + if (D->getKind() == Decl::TemplateTypeParm) + type->def.detailed_name = Intern(info->short_name); + else + SetName(OrigD, info->short_name, info->qualified, type->def); + } if (is_def || is_decl) { const Decl *DC = cast(SemDC); if (GetKind(DC, ls_kind) == Kind::Type) @@ -840,6 +852,7 @@ class IndexDataConsumer : public index::IndexDataConsumer { if (!isa(D)) db->ToType(usr1).instances.push_back(usr); } else if (const Decl *D1 = GetAdjustedDecl(GetTypeDecl(T))) { +#if LLVM_VERSION_MAJOR < 9 if (isa(D1)) { // e.g. TemplateTypeParmDecl is not handled by // handleDeclOccurence. @@ -862,6 +875,7 @@ class IndexDataConsumer : public index::IndexDataConsumer { break; } } +#endif IndexParam::DeclInfo *info1; Usr usr1 = GetUsr(D1, &info1); @@ -1227,6 +1241,11 @@ Index(SemaManager *manager, WorkingFiles *wfiles, VFS *vfs, index::IndexingOptions::SystemSymbolFilterKind::All; IndexOpts.IndexFunctionLocals = true; IndexOpts.IndexImplicitInstantiation = true; +#if LLVM_VERSION_MAJOR >= 9 + IndexOpts.IndexParametersInDeclarations = + g_config->index.parametersInDeclarations; + IndexOpts.IndexTemplateParameters = true; +#endif std::unique_ptr Action = createIndexingAction( DataConsumer, IndexOpts, std::make_unique(param)); diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc index 07a5b485f..1372f50fe 100644 --- a/src/messages/textDocument_completion.cc +++ b/src/messages/textDocument_completion.cc @@ -260,6 +260,9 @@ CompletionItemKind GetCompletionKind(CodeCompletionContext::Kind K, case Decl::TypeAlias: case Decl::Typedef: return CompletionItemKind::TypeParameter; + case Decl::Using: + case Decl::ConstructorUsingShadow: + return CompletionItemKind::Keyword; case Decl::Binding: return CompletionItemKind::Variable; case Decl::Field: From dd9d21083b4f2cb2306009cc0776d1c40fdac73f Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 22 Feb 2019 23:49:37 +0800 Subject: [PATCH 18/79] Make hover more detailed (e.g. include inheritance info) --- src/indexer.cc | 4 +++- src/messages/textDocument_hover.cc | 38 +++++++++++++++--------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/indexer.cc b/src/indexer.cc index 894286f3e..b6653fc34 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -815,10 +815,12 @@ class IndexDataConsumer : public index::IndexDataConsumer { do_def_decl(type); if (Spell != Loc) AddMacroUse(db, SM, usr, Kind::Type, Spell); - if (type->def.detailed_name[0] == '\0' && info->short_name.size()) { + if ((is_def || type->def.detailed_name[0] == '\0') && + info->short_name.size()) { if (D->getKind() == Decl::TemplateTypeParm) type->def.detailed_name = Intern(info->short_name); else + // OrigD may be detailed, e.g. "struct D : B {}" SetName(OrigD, info->short_name, info->qualified, type->def); } if (is_def || is_decl) { diff --git a/src/messages/textDocument_hover.cc b/src/messages/textDocument_hover.cc index 50cbf620b..60c00fb9b 100644 --- a/src/messages/textDocument_hover.cc +++ b/src/messages/textDocument_hover.cc @@ -47,33 +47,33 @@ GetHover(DB *db, LanguageId lang, SymbolRef sym, int file_id) { const char *comments = nullptr; std::optional ls_comments, hover; WithEntity(db, sym, [&](const auto &entity) { - std::remove_reference_t *def = nullptr; for (auto &d : entity.def) { if (d.spell) { comments = d.comments[0] ? d.comments : nullptr; - def = &d; + if (const char *s = + d.hover[0] ? d.hover + : d.detailed_name[0] ? d.detailed_name : nullptr) { + if (!hover) + hover = {LanguageIdentifier(lang), s}; + else if (strlen(s) > hover->value.size()) + hover->value = s; + } if (d.spell->file_id == file_id) break; } } - if (!def && entity.def.size()) { - def = &entity.def[0]; - if (def->comments[0]) - comments = def->comments; - } - if (def) { - MarkedString m; - m.language = LanguageIdentifier(lang); - if (def->hover[0]) { - m.value = def->hover; - hover = m; - } else if (def->detailed_name[0]) { - m.value = def->detailed_name; - hover = m; - } - if (comments) - ls_comments = MarkedString{std::nullopt, comments}; + if (!hover && entity.def.size()) { + auto &d = entity.def[0]; + if (d.comments[0]) + comments = d.comments; + hover = {LanguageIdentifier(lang)}; + if (d.hover[0]) + hover->value = d.hover; + else if (d.detailed_name[0]) + hover->value = d.detailed_name; } + if (comments) + ls_comments = MarkedString{std::nullopt, comments}; }); return {hover, ls_comments}; } From cafd2d4f77af78c36cd6b375e08b59f870aeeae5 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sat, 23 Feb 2019 19:17:26 +0800 Subject: [PATCH 19/79] Change Pos::line from int16_t to uint16_t This allows representing line 0 ~ 65535. --- src/clang_tu.cc | 2 +- src/indexer.cc | 4 ++-- src/message_handler.cc | 4 ++-- src/messages/ccls_navigate.cc | 2 +- src/position.cc | 8 ++++---- src/position.hh | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/clang_tu.cc b/src/clang_tu.cc index d10ed7465..220327421 100644 --- a/src/clang_tu.cc +++ b/src/clang_tu.cc @@ -43,7 +43,7 @@ static Pos Decomposed2LineAndCol(const SourceManager &SM, while (i < P.size() && (uint8_t)P[i] >= 128 && (uint8_t)P[i] < 192) i++; } - return {(int16_t)std::min(l, INT16_MAX), + return {(uint16_t)std::min(l, UINT16_MAX), (int16_t)std::min(c, INT16_MAX)}; } diff --git a/src/indexer.cc b/src/indexer.cc index b6653fc34..5b730fe61 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -1151,8 +1151,8 @@ class IndexFrontendAction : public ASTFrontendAction { }; } // namespace -const int IndexFile::kMajorVersion = 19; -const int IndexFile::kMinorVersion = 1; +const int IndexFile::kMajorVersion = 20; +const int IndexFile::kMinorVersion = 0; IndexFile::IndexFile(const std::string &path, const std::string &contents) : path(path), file_contents(contents) {} diff --git a/src/message_handler.cc b/src/message_handler.cc index 7179931d2..a770eb750 100644 --- a/src/message_handler.cc +++ b/src/message_handler.cc @@ -320,9 +320,9 @@ void EmitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) { // but we still want to keep the range for jumping to definition. std::string_view concise_name = detailed_name.substr(0, detailed_name.find('<')); - int16_t start_line = sym.range.start.line; + uint16_t start_line = sym.range.start.line; int16_t start_col = sym.range.start.column; - if (start_line < 0 || start_line >= wfile->index_lines.size()) + if (start_line >= wfile->index_lines.size()) continue; std::string_view line = wfile->index_lines[start_line]; sym.range.end.line = start_line; diff --git a/src/messages/ccls_navigate.cc b/src/messages/ccls_navigate.cc index f2a6ac445..cb85ba7cb 100644 --- a/src/messages/ccls_navigate.cc +++ b/src/messages/ccls_navigate.cc @@ -38,7 +38,7 @@ void MessageHandler::ccls_navigate(JsonReader &reader, ReplyOnce &reply) { if (auto line = wf->GetIndexPosFromBufferPos(ls_pos.line, &ls_pos.character, false)) ls_pos.line = *line; - Pos pos{(int16_t)ls_pos.line, (int16_t)ls_pos.character}; + Pos pos{(uint16_t)ls_pos.line, (int16_t)ls_pos.character}; Maybe res; switch (param.direction[0]) { diff --git a/src/position.cc b/src/position.cc index c615c2b94..c572421b0 100644 --- a/src/position.cc +++ b/src/position.cc @@ -16,7 +16,7 @@ namespace ccls { Pos Pos::FromString(const std::string &encoded) { char *p = const_cast(encoded.c_str()); - int16_t line = int16_t(strtol(p, &p, 10)) - 1; + uint16_t line = uint16_t(strtoul(p, &p, 10) - 1); assert(*p == ':'); p++; int16_t column = int16_t(strtol(p, &p, 10)) - 1; @@ -32,14 +32,14 @@ std::string Pos::ToString() { Range Range::FromString(const std::string &encoded) { Pos start, end; char *p = const_cast(encoded.c_str()); - start.line = int16_t(strtol(p, &p, 10)) - 1; + start.line = uint16_t(strtoul(p, &p, 10) - 1); assert(*p == ':'); p++; start.column = int16_t(strtol(p, &p, 10)) - 1; assert(*p == '-'); p++; - end.line = int16_t(strtol(p, &p, 10)) - 1; + end.line = uint16_t(strtoul(p, &p, 10) - 1); assert(*p == ':'); p++; end.column = int16_t(strtol(p, nullptr, 10)) - 1; @@ -49,7 +49,7 @@ Range Range::FromString(const std::string &encoded) { bool Range::Contains(int line, int column) const { if (line > INT16_MAX) return false; - Pos p{(int16_t)line, (int16_t)std::min(column, INT16_MAX)}; + Pos p{(uint16_t)line, (int16_t)std::min(column, INT16_MAX)}; return !(p < start) && p < end; } diff --git a/src/position.hh b/src/position.hh index 0a08452b0..35b1df9e0 100644 --- a/src/position.hh +++ b/src/position.hh @@ -10,12 +10,12 @@ namespace ccls { struct Pos { - int16_t line = -1; + uint16_t line = 0; int16_t column = -1; static Pos FromString(const std::string &encoded); - bool Valid() const { return line >= 0; } + bool Valid() const { return column >= 0; } std::string ToString(); // Compare two Positions and check if they are equal. Ignores the value of From 3594b66eee67c4acc4febad41ef2417df8f983dd Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 1 Mar 2019 17:30:53 -0800 Subject: [PATCH 20/79] working_files: normalize \r\n and \n to \n Clients may normalize end-of-line sequences, thus cause a mismatch between index_lines and buffer_lines. Thanks to CXuesong for reporting this issue! --- src/working_files.cc | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/working_files.cc b/src/working_files.cc index 771a208b3..7348d35a9 100644 --- a/src/working_files.cc +++ b/src/working_files.cc @@ -12,7 +12,6 @@ #include #include #include -#include namespace chrono = std::chrono; using namespace clang; @@ -43,13 +42,17 @@ Position GetPositionForOffset(const std::string &content, int offset) { return {line, col}; } -std::vector ToLines(const std::string &content) { - std::vector result; - std::istringstream lines(content); - std::string line; - while (getline(lines, line)) - result.push_back(line); - return result; +std::vector ToLines(const std::string &c) { + std::vector ret; + int last = 0, e = c.size(); + for (int i = 0; i < e; i++) + if (c[i] == '\n') { + ret.emplace_back(&c[last], i - last - (i && c[i - 1] == '\r')); + last = i + 1; + } + if (last < e) + ret.emplace_back(&c[last], e - last); + return ret; } // Computes the edit distance of strings [a,a+la) and [b,b+lb) with Eugene W. From bae9ecfe6130a697b8ddb16d04fe2683b8bb3a81 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 1 Mar 2019 17:37:37 -0800 Subject: [PATCH 21/79] Add .github/ISSUE_TEMPLATE Adapted from https://github.com/hlissner/doom-emacs --- .github/ISSUE_TEMPLATE | 36 ++++++++++++++++++++++++++++++++++++ .gitignore | 1 + 2 files changed, 37 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 000000000..1a27d0173 --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1,36 @@ +Here are some things you should try before filing a bug report: + ++ For client issues related to [emacs-ccls](https://github.com/MaskRay/emacs-ccls) or [vscode-ccls](https://github.com/MaskRay/vscode-ccls), report in their own repository. ++ For build problems, check https://github.com/MaskRay/ccls/wiki/Build ++ Check https://github.com/MaskRay/ccls/wiki/Debugging ++ Check [the FAQ](https://github.com/MaskRay/ccls/wiki/FAQ) to see if your issue is mentioned. + +If none of those help, remove this section and fill out the four sections in the template below. + +--- + +### Observed behavior + +Describe what happened. Any aids you can include (that you think could be relevant) are a tremendous help. + +* `compile_commands.json` or `.ccls` ([wiki/Project-Setup](https://github.com/MaskRay/ccls/wiki/Project-Setup)) +* Reduce to A minimal set of `.c` `.cc` `.h` `.hh` files that can still demonstrate the issue. +* Consider a screencast gif. + +### Expected behavior + +Describe what you expected to happen. + +### Steps to reproduce + +1. Select these example steps, +2. Delete them, +3. And replace them with precise steps to reproduce your issue. + +### System information + +* ccls version (`git describe --tags --long`): +* clang version: +* OS: +* Editor: +* Language client (and version): diff --git a/.gitignore b/.gitignore index 87cc1bce2..6c3c5f19e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build debug release /compile_commands.json +!.github/ From 75684f088346769c770f5a575d2002a72e8d335d Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 1 Mar 2019 18:35:13 -0800 Subject: [PATCH 22/79] textDocument/rename: mitigate edits in the same place and edits in macro replacement Mitigate edits in the same place (#294) and: // textDocument/rename on `f` void f(); void g() { m(); } // incorrectly rewrote m() before --- src/messages/textDocument_completion.cc | 2 +- src/messages/textDocument_rename.cc | 67 +++++++++++++--------- src/messages/textDocument_signatureHelp.cc | 2 +- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc index 1372f50fe..862bdafdf 100644 --- a/src/messages/textDocument_completion.cc +++ b/src/messages/textDocument_completion.cc @@ -514,7 +514,7 @@ void MessageHandler::textDocument_completion(CompletionParam ¶m, } std::string filter; - Position end_pos = param.position; + Position end_pos; Position begin_pos = wf->GetCompletionPosition(param.position, &filter, &end_pos); diff --git a/src/messages/textDocument_rename.cc b/src/messages/textDocument_rename.cc index a2fa9cf2a..a124dafcf 100644 --- a/src/messages/textDocument_rename.cc +++ b/src/messages/textDocument_rename.cc @@ -4,54 +4,65 @@ #include "message_handler.hh" #include "query.hh" +#include + +#include + +using namespace clang; + namespace ccls { namespace { WorkspaceEdit BuildWorkspaceEdit(DB *db, WorkingFiles *wfiles, SymbolRef sym, + std::string_view old_text, const std::string &new_text) { - std::unordered_map path_to_edit; + std::unordered_map> path2edit; + std::unordered_map> edited; EachOccurrence(db, sym, true, [&](Use use) { - std::optional ls_location = GetLsLocation(db, wfiles, use); - if (!ls_location) - return; - int file_id = use.file_id; - if (path_to_edit.find(file_id) == path_to_edit.end()) { - path_to_edit[file_id] = TextDocumentEdit(); - - QueryFile &file = db->files[file_id]; - if (!file.def) - return; + QueryFile &file = db->files[file_id]; + if (!file.def || !edited[file_id].insert(use.range).second) + return; + std::optional loc = GetLsLocation(db, wfiles, use); + if (!loc) + return; + auto [it, inserted] = path2edit.try_emplace(file_id); + auto &edit = it->second.second; + if (inserted) { const std::string &path = file.def->path; - path_to_edit[file_id].textDocument.uri = DocumentUri::FromPath(path); - - WorkingFile *wf = wfiles->GetFile(path); - if (wf) - path_to_edit[file_id].textDocument.version = wf->version; + edit.textDocument.uri = DocumentUri::FromPath(path); + if ((it->second.first = wfiles->GetFile(path))) + edit.textDocument.version = it->second.first->version; } - - TextEdit &edit = path_to_edit[file_id].edits.emplace_back(); - edit.range = ls_location->range; - edit.newText = new_text; + // TODO LoadIndexedContent if wf is nullptr. + if (WorkingFile *wf = it->second.first) { + int start = GetOffsetForPosition(loc->range.start, wf->buffer_content), + end = GetOffsetForPosition(loc->range.end, wf->buffer_content); + if (wf->buffer_content.compare(start, end - start, old_text)) + return; + } + edit.edits.push_back({loc->range, new_text}); }); - WorkspaceEdit edit; - for (const auto &changes : path_to_edit) - edit.documentChanges.push_back(changes.second); - return edit; + WorkspaceEdit ret; + for (auto &x : path2edit) + ret.documentChanges.push_back(std::move(x.second.second)); + return ret; } } // namespace void MessageHandler::textDocument_rename(RenameParam ¶m, ReplyOnce &reply) { auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply); - if (!wf) { + if (!wf) return; - } - WorkspaceEdit result; + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { - result = BuildWorkspaceEdit(db, wfiles, sym, param.newName); + result = BuildWorkspaceEdit( + db, wfiles, sym, + LexIdentifierAroundPos(param.position, wf->buffer_content), + param.newName); break; } diff --git a/src/messages/textDocument_signatureHelp.cc b/src/messages/textDocument_signatureHelp.cc index f7dc0e904..3993b4ef5 100644 --- a/src/messages/textDocument_signatureHelp.cc +++ b/src/messages/textDocument_signatureHelp.cc @@ -147,7 +147,7 @@ void MessageHandler::textDocument_signatureHelp( } { std::string filter; - Position end_pos = param.position; + Position end_pos; begin_pos = wf->GetCompletionPosition(param.position, &filter, &end_pos); } From fcfd0dddcf00c7f75cb5a8683c99755c03f16882 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Mon, 4 Mar 2019 06:52:07 -0800 Subject: [PATCH 23/79] stdin: synthesize an "exit" NotificationMessage in abnormal termination --- src/pipeline.cc | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/pipeline.cc b/src/pipeline.cc index 4f48b0a0b..dfd09d186 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -491,13 +491,14 @@ void LaunchStdin() { set_thread_name("stdin"); std::string str; const std::string_view kContentLength("Content-Length: "); + bool received_exit = false; while (true) { int len = 0; str.clear(); while (true) { int c = getchar(); if (c == EOF) - return; + goto quit; if (c == '\n') { if (str.empty()) break; @@ -513,7 +514,7 @@ void LaunchStdin() { for (int i = 0; i < len; ++i) { int c = getchar(); if (c == EOF) - return; + goto quit; str[i] = c; } @@ -537,16 +538,28 @@ void LaunchStdin() { LOG_V(2) << "receive NotificationMessage " << method; if (method.empty()) continue; - bool should_exit = method == "exit"; + received_exit = method == "exit"; // g_config is not available before "initialize". Use 0 in that case. on_request->PushBack( {id, std::move(method), std::move(message), std::move(document), chrono::steady_clock::now() + chrono::milliseconds(g_config ? g_config->request.timeout : 0)}); - if (should_exit) + if (received_exit) break; } + + quit: + if (!received_exit) { + const std::string_view str("{\"jsonrpc\":\"2.0\",\"method\":\"exit\"}"); + auto message = std::make_unique(str.size()); + std::copy(str.begin(), str.end(), message.get()); + auto document = std::make_unique(); + document->Parse(message.get(), str.size()); + on_request->PushBack({RequestId(), std::string("exit"), + std::move(message), std::move(document), + chrono::steady_clock::now()}); + } ThreadLeave(); }).detach(); } From 38f4a8ac784ff8254c828e55b09ccae03a6f81bf Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Mon, 4 Mar 2019 18:21:53 -0800 Subject: [PATCH 24/79] Make clang.excludeArgs accept glob patterns --- src/config.hh | 5 ++--- src/project.cc | 21 +++++++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/config.hh b/src/config.hh index 2761da51a..63f3b38ae 100644 --- a/src/config.hh +++ b/src/config.hh @@ -84,9 +84,8 @@ struct Config { } capabilities; struct Clang { - // Arguments that should be excluded, e.g. ["-fopenmp", "-Wall"] - // - // e.g. If your project is built by GCC and has an option thag clang does not understand. + // Arguments matching any of these glob patterns will be excluded, e.g. + // ["-fopenmp", "-m*", "-Wall"]. std::vector excludeArgs; // Additional arguments to pass to clang. diff --git a/src/project.cc b/src/project.cc index d04d67c28..249141280 100644 --- a/src/project.cc +++ b/src/project.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -69,10 +70,22 @@ enum OptionClass { struct ProjectProcessor { Project::Folder &folder; std::unordered_set command_set; - StringSet<> excludeArgs; + StringSet<> exclude_args; + std::vector exclude_globs; + ProjectProcessor(Project::Folder &folder) : folder(folder) { for (auto &arg : g_config->clang.excludeArgs) - excludeArgs.insert(arg); + if (arg.find_first_of("?*[") == std::string::npos) + exclude_args.insert(arg); + else if (Expected glob_or_err = GlobPattern::create(arg)) + exclude_globs.push_back(std::move(*glob_or_err)); + else + LOG_S(WARNING) << toString(glob_or_err.takeError()); + } + + bool ExcludesArg(StringRef arg) { + return exclude_args.count(arg) || any_of(exclude_globs, + [&](const GlobPattern &glob) { return glob.match(arg); }); } // Expand %c %cpp ... in .ccls @@ -103,7 +116,7 @@ struct ProjectProcessor { } if (ok) args.push_back(A.data()); - } else if (!excludeArgs.count(A)) { + } else if (!ExcludesArg(A)) { args.push_back(arg); } } @@ -384,7 +397,7 @@ void Project::LoadDirectory(const std::string &root, Project::Folder &folder) { entry.args.reserve(args.size()); for (std::string &arg : args) { DoPathMapping(arg); - if (!proc.excludeArgs.count(arg)) + if (!proc.ExcludesArg(arg)) entry.args.push_back(Intern(arg)); } entry.compdb_size = entry.args.size(); From be391ee72dc4752db965f17d6ec709644d4125e4 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sat, 2 Mar 2019 18:18:02 -0800 Subject: [PATCH 25/79] Misc --- README.md | 4 +--- src/indexer.cc | 3 ++- src/messages/textDocument_document.cc | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f7d8d3817..3cf69e41c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ccls, which originates from [cquery](https://github.com/cquery-project/cquery), * formatting * hierarchies: [call (caller/callee) hierarchy](src/messages/ccls_call.cc), [inheritance (base/derived) hierarchy](src/messages/ccls_inheritance.cc), [member hierarchy](src/messages/ccls_member.cc) * [symbol rename](src/messages/textDocument_rename.cc) - * [document symbols](src/messages/textDocument_document.cc) and approximate search of [workspace symbol](src/messages/workspace_symbol.cc) + * [document symbols](src/messages/textDocument_document.cc) and approximate search of [workspace symbol](src/messages/workspace.cc) * [hover information](src/messages/textDocument_hover.cc) * diagnostics and code actions (clang FixIts) * semantic highlighting and preprocessor skipped regions @@ -24,8 +24,6 @@ Saving files will incrementally update the index. # >>> [Getting started](../../wiki/Home) (CLICK HERE) <<< * [Build](../../wiki/Build) -* [Emacs](../../wiki/Emacs) -* [LanguageClient-neovim](../../wiki/LanguageClient-neovim) * [FAQ](../../wiki/FAQ) ccls can index itself (~180MiB RSS when idle, noted on 2018-09-01), FreeBSD, glibc, Linux, LLVM (~1800MiB RSS), musl (~60MiB RSS), ... with decent memory footprint. See [wiki/Project-Setup](../../wiki/Project-Setup) for examples. diff --git a/src/indexer.cc b/src/indexer.cc index 5b730fe61..00ed85222 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -459,7 +459,8 @@ class IndexDataConsumer : public index::IndexDataConsumer { info.usr = HashUsr(USR); if (auto *ND = dyn_cast(D)) { info.short_name = ND->getNameAsString(); - info.qualified = ND->getQualifiedNameAsString(); + llvm::raw_string_ostream OS(info.qualified); + ND->printQualifiedName(OS, GetDefaultPolicy()); SimplifyAnonymous(info.qualified); } } diff --git a/src/messages/textDocument_document.cc b/src/messages/textDocument_document.cc index 2a3452760..8697b34eb 100644 --- a/src/messages/textDocument_document.cc +++ b/src/messages/textDocument_document.cc @@ -76,14 +76,14 @@ REFLECT_STRUCT(DocumentLink, range, target); void MessageHandler::textDocument_documentLink(TextDocumentParam ¶m, ReplyOnce &reply) { auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply); - if (!wf) { + if (!wf) return; - } std::vector result; + int column; for (const IndexInclude &include : file->def->includes) if (std::optional bline = - wf->GetBufferPosFromIndexPos(include.line, nullptr, false)) { + wf->GetBufferPosFromIndexPos(include.line, &column, false)) { const std::string &line = wf->buffer_lines[*bline]; auto start = line.find_first_of("\"<"), end = line.find_last_of("\">"); if (start < end) From 09b6f64cca00622a546b927029370adcb5a994ae Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 8 Mar 2019 22:46:43 -0800 Subject: [PATCH 26/79] cmake: add option to use system rapidjson if exists --- CMakeLists.txt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 993376d81..8277d3943 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ include(DefaultCMakeBuildType) # Required Clang version option(LLVM_ENABLE_RTTI "-fno-rtti if OFF. This should match LLVM libraries" OFF) option(USE_SHARED_LLVM "Link against libLLVM.so instead separate LLVM{Option,Support,...}" OFF) +option(USE_SYSTEM_RAPIDJSON "Use system RapidJSON instead of the git submodule if exists" ON) # Sources for the executable are specified at end of CMakeLists.txt add_executable(ccls "") @@ -95,9 +96,15 @@ target_compile_definitions(ccls PRIVATE ### Includes target_include_directories(ccls PRIVATE src) -target_include_directories(ccls SYSTEM PRIVATE - third_party - third_party/rapidjson/include) +target_include_directories(ccls SYSTEM PRIVATE third_party) + +if(USE_SYSTEM_RAPIDJSON) + find_package(RapidJSON QUIET) +endif() +if(NOT RapidJSON_FOUND) + set(RapidJSON_INCLUDE_DIRS third_party/rapidjson/include) +endif() +target_include_directories(ccls SYSTEM PRIVATE ${RapidJSON_INCLUDE_DIRS}) ### Install From 5c9b055eaf800d2a79d6fa40e62c1e5b9a796a82 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 8 Mar 2019 23:37:08 -0800 Subject: [PATCH 27/79] Add excludeRole to documentSymbol and override declaration's range/selectionRange with definition's --- src/messages/textDocument_document.cc | 73 ++++++++++++++------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src/messages/textDocument_document.cc b/src/messages/textDocument_document.cc index 8697b34eb..4d066529d 100644 --- a/src/messages/textDocument_document.cc +++ b/src/messages/textDocument_document.cc @@ -95,13 +95,15 @@ void MessageHandler::textDocument_documentLink(TextDocumentParam ¶m, namespace { struct DocumentSymbolParam : TextDocumentParam { - // false: outline; true: all symbols - bool all = false; + // Include sym if `!(sym.role & excludeRole)`. + Role excludeRole = Role((int)Role::All - (int)Role::Definition - + (int)Role::Declaration - (int)Role::Dynamic); // If >= 0, return Range[] instead of SymbolInformation[] to reduce output. int startLine = -1; int endLine = -1; }; -REFLECT_STRUCT(DocumentSymbolParam, textDocument, all, startLine, endLine); +REFLECT_STRUCT(DocumentSymbolParam, textDocument, excludeRole, startLine, + endLine); struct DocumentSymbol { std::string name; @@ -149,18 +151,22 @@ void MessageHandler::textDocument_documentSymbol(JsonReader &reader, int file_id; auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply, &file_id); - if (!wf) { + if (!wf) return; - } + auto Allows = [&](SymbolRef sym) { + return !(sym.role & param.excludeRole); + }; if (param.startLine >= 0) { std::vector result; - for (auto [sym, refcnt] : file->symbol2refcnt) - if (refcnt > 0 && (param.all || sym.extent.Valid()) && - param.startLine <= sym.range.start.line && - sym.range.start.line <= param.endLine) - if (auto loc = GetLsLocation(db, wfiles, sym, file_id)) - result.push_back(loc->range); + for (auto [sym, refcnt] : file->symbol2refcnt) { + if (refcnt <= 0 || !Allows(sym) || + !(param.startLine <= sym.range.start.line && + sym.range.start.line <= param.endLine)) + continue; + if (auto loc = GetLsLocation(db, wfiles, sym, file_id)) + result.push_back(loc->range); + } std::sort(result.begin(), result.end()); reply(result); } else if (g_config->client.hierarchicalDocumentSymbolSupport) { @@ -171,22 +177,26 @@ void MessageHandler::textDocument_documentSymbol(JsonReader &reader, if (refcnt <= 0 || !sym.extent.Valid()) continue; auto r = sym2ds.try_emplace(SymbolIdx{sym.usr, sym.kind}); - if (!r.second) - continue; auto &ds = r.first->second; - ds = std::make_unique(); - if (auto range = GetLsRange(wf, sym.range)) { - ds->selectionRange = *range; - ds->range = ds->selectionRange; - // For a macro expansion, M(name), we may use `M` for extent and `name` - // for spell, do the check as selectionRange must be a subrange of - // range. - if (sym.extent.Valid()) - if (auto range1 = GetLsRange(wf, sym.extent); - range1 && range1->Includes(*range)) - ds->range = *range1; + if (!ds || sym.role & Role::Definition) { + if (!ds) + ds = std::make_unique(); + if (auto range = GetLsRange(wf, sym.range)) { + ds->selectionRange = *range; + ds->range = ds->selectionRange; + // For a macro expansion, M(name), we may use `M` for extent and + // `name` for spell, do the check as selectionRange must be a subrange + // of range. + if (sym.extent.Valid()) + if (auto range1 = GetLsRange(wf, sym.extent); + range1 && range1->Includes(*range)) + ds->range = *range1; + } } + if (!r.second) + continue; std::vector def_ptrs; + SymbolKind kind = SymbolKind::Unknown; WithEntity(db, sym, [&](const auto &entity) { auto *def = entity.AnyDef(); if (!def) @@ -195,20 +205,14 @@ void MessageHandler::textDocument_documentSymbol(JsonReader &reader, ds->detail = def->detailed_name; for (auto &def : entity.def) if (def.file_id == file_id && !Ignore(&def)) { - ds->kind = def.kind; - if (def.spell || def.kind == SymbolKind::Namespace) - def_ptrs.push_back(&def); + kind = ds->kind = def.kind; + def_ptrs.push_back(&def); } }); - if (!(param.all || sym.role & Role::Definition || - ds->kind == SymbolKind::Function || - ds->kind == SymbolKind::Method || - ds->kind == SymbolKind::Namespace)) { + if (def_ptrs.empty() || !(kind == SymbolKind::Namespace || Allows(sym))) { ds.reset(); continue; } - if (def_ptrs.empty()) - continue; if (sym.kind == Kind::Func) funcs.emplace_back(std::move(def_ptrs), ds.get()); else if (sym.kind == Kind::Type) @@ -252,8 +256,7 @@ void MessageHandler::textDocument_documentSymbol(JsonReader &reader, } else { std::vector result; for (auto [sym, refcnt] : file->symbol2refcnt) { - if (refcnt <= 0 || !sym.extent.Valid() || - !(param.all || sym.role & Role::Definition)) + if (refcnt <= 0 || !Allows(sym)) continue; if (std::optional info = GetSymbolInfo(db, sym, false)) { From bbb28c93eb61dd41ed31ac2879af39ce04423640 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sat, 9 Mar 2019 19:45:37 -0800 Subject: [PATCH 28/79] cmake: use {LLVM,Clang}Config.cmake Combined Daan De Meyer's #227 with other simplification * USE_SHARED_LLVM is deleted in favor of LLVM_LINK_LLVM_DYLIB * LLVM_ENABLE_RTTI is deleted as it is provided by LLVMConfig.cmake * Only direct Clang/LLVM dependencies are required in target_link_libraries * Restrict -DCLANG_RESOURCE_DIRECTORY= to src/utils.cc --- CMakeLists.txt | 110 ++++++++++++++------- cmake/DefaultCMakeBuildType.cmake | 14 --- cmake/FindClang.cmake | 153 ------------------------------ src/utils.cc | 4 +- 4 files changed, 78 insertions(+), 203 deletions(-) delete mode 100644 cmake/DefaultCMakeBuildType.cmake delete mode 100644 cmake/FindClang.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8277d3943..173a373ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,39 +1,35 @@ cmake_minimum_required(VERSION 3.8) project(ccls LANGUAGES CXX) -list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) -include(DefaultCMakeBuildType) - -# Required Clang version -option(LLVM_ENABLE_RTTI "-fno-rtti if OFF. This should match LLVM libraries" OFF) -option(USE_SHARED_LLVM "Link against libLLVM.so instead separate LLVM{Option,Support,...}" OFF) option(USE_SYSTEM_RAPIDJSON "Use system RapidJSON instead of the git submodule if exists" ON) # Sources for the executable are specified at end of CMakeLists.txt add_executable(ccls "") -### Compile options +### Default build type + +set(DEFAULT_CMAKE_BUILD_TYPE Release) -# CMake default compile flags: -# MSVC + Clang(Windows): -# debug: /MDd /Zi /Ob0 /Od /RTC1 -# release: /MD /O2 /Ob2 /DNDEBUG -# GCC + Clang(Linux): -# debug: -g -# release: -O3 -DNDEBUG +# CMAKE_BUILD_TYPE is not available if a multi-configuration generator is used +# (eg Visual Studio generators) +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to '${DEFAULT_CMAKE_BUILD_TYPE}' as none \ +was specified.") + set(CMAKE_BUILD_TYPE ${DEFAULT_CMAKE_BUILD_TYPE} + CACHE STRING "Choose the type of build." FORCE) + + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE + PROPERTY STRINGS Debug Release MinSizeRel RelWithDebInfo) +endif() + +### Compile options # Enable C++17 (Required) set_property(TARGET ccls PROPERTY CXX_STANDARD 17) set_property(TARGET ccls PROPERTY CXX_STANDARD_REQUIRED ON) set_property(TARGET ccls PROPERTY CXX_EXTENSIONS OFF) -if(NOT LLVM_ENABLE_RTTI) - # releases.llvm.org libraries are compiled with -fno-rtti - # The mismatch between lib{clang,LLVM}* and ccls can make libstdc++ std::make_shared return nullptr - # _Sp_counted_ptr_inplace::_M_get_deleter - target_compile_options(ccls PRIVATE -fno-rtti) -endif() - # CMake sets MSVC for both MSVC and Clang(Windows) if(MSVC) # Common MSVC/Clang(Windows) options @@ -72,9 +68,41 @@ endif() ### Libraries -# See cmake/FindClang.cmake -find_package(Clang 7.0.0) -target_link_libraries(ccls PRIVATE Clang::Clang) +find_package(Clang REQUIRED) + +target_link_libraries(ccls PRIVATE + clangIndex + clangFormat + clangTooling + clangToolingInclusions + clangToolingCore + clangFrontend + clangParse + clangSerialization + clangSema + clangAST + clangLex + clangDriver + clangBasic +) + +if(LLVM_LINK_LLVM_DYLIB) + target_link_libraries(ccls PRIVATE LLVM) +else() + # In llvm 7, clangDriver headers reference LLVMOption + target_link_libraries(ccls PRIVATE LLVMOption LLVMSupport) +endif() + +if(NOT LLVM_ENABLE_RTTI) + # releases.llvm.org libraries are compiled with -fno-rtti + # The mismatch between lib{clang,LLVM}* and ccls can make libstdc++ std::make_shared return nullptr + # _Sp_counted_ptr_inplace::_M_get_deleter + if(MSVC) + target_compile_options(ccls PRIVATE /GR-) + else() + target_compile_options(ccls PRIVATE -fno-rtti) + endif() +endif() # Enable threading support set(THREADS_PREFER_PTHREAD_FLAG ON) @@ -90,13 +118,34 @@ endif() ### Definitions -target_compile_definitions(ccls PRIVATE - DEFAULT_RESOURCE_DIRECTORY=R"\(${Clang_RESOURCE_DIR}\)") +# Find Clang resource directory with Clang executable. + +find_program(CLANG_EXECUTABLE clang) +if(NOT CLANG_EXECUTABLE) + message(FATAL_ERROR "clang executable not found.") +endif() + +execute_process( + COMMAND ${CLANG_EXECUTABLE} -print-resource-dir + RESULT_VARIABLE CLANG_FIND_RESOURCE_DIR_RESULT + OUTPUT_VARIABLE CLANG_RESOURCE_DIR + ERROR_VARIABLE CLANG_FIND_RESOURCE_DIR_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +if(CLANG_FIND_RESOURCE_DIR_RESULT) + message(FATAL_ERROR "Error retrieving Clang resource directory with Clang \ + executable. Output:\n ${CLANG_FIND_RESOURCE_DIR_ERROR}") +endif() + +set_property(SOURCE src/utils.cc APPEND PROPERTY COMPILE_DEFINITIONS + CLANG_RESOURCE_DIRECTORY=R"\(${CLANG_RESOURCE_DIR}\)") ### Includes target_include_directories(ccls PRIVATE src) -target_include_directories(ccls SYSTEM PRIVATE third_party) +target_include_directories(ccls SYSTEM PRIVATE + third_party ${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS}) if(USE_SYSTEM_RAPIDJSON) find_package(RapidJSON QUIET) @@ -110,13 +159,6 @@ target_include_directories(ccls SYSTEM PRIVATE ${RapidJSON_INCLUDE_DIRS}) install(TARGETS ccls RUNTIME DESTINATION bin) -### Tools - -# We use glob here since source files are already manually added with -# target_sources further down -file(GLOB SOURCES src/*.cc src/*.h src/serializers/*.cc src/serializers/*.h - src/messages/*.h src/messages/*.cc) - ### Sources target_sources(ccls PRIVATE third_party/siphash.cc) diff --git a/cmake/DefaultCMakeBuildType.cmake b/cmake/DefaultCMakeBuildType.cmake deleted file mode 100644 index ae440bf54..000000000 --- a/cmake/DefaultCMakeBuildType.cmake +++ /dev/null @@ -1,14 +0,0 @@ -set(DEFAULT_CMAKE_BUILD_TYPE Release) - -# CMAKE_BUILD_TYPE is not available if a multi-configuration generator is used -# (eg Visual Studio generators) -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - message(STATUS "Setting build type to '${DEFAULT_CMAKE_BUILD_TYPE}' as none \ -was specified.") - set(CMAKE_BUILD_TYPE ${DEFAULT_CMAKE_BUILD_TYPE} - CACHE STRING "Choose the type of build." FORCE) - - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE - PROPERTY STRINGS Debug Release MinSizeRel RelWithDebInfo) -endif() diff --git a/cmake/FindClang.cmake b/cmake/FindClang.cmake deleted file mode 100644 index 078395dd9..000000000 --- a/cmake/FindClang.cmake +++ /dev/null @@ -1,153 +0,0 @@ -#.rst -# FindClang -# --------- -# -# Find Clang and LLVM libraries required by ccls -# -# Results are reported in the following variables:: -# -# Clang_FOUND - True if headers and requested libraries were found -# Clang_EXECUTABLE - Clang executable -# Clang_RESOURCE_DIR - Clang resource directory -# Clang_VERSION - Clang version as reported by Clang executable -# -# The following :prop_tgt:`IMPORTED` targets are also defined:: -# -# Clang::Clang - Target for all required Clang libraries and headers -# -# This module reads hints about which libraries to look for and where to find -# them from the following variables:: -# -# CLANG_ROOT - If set, only look for Clang components in CLANG_ROOT -# -# Example to link against Clang target:: -# -# target_link_libraries( PRIVATE Clang::Clang) - -### Definitions - -# Wrapper macro's around the find_* macro's from CMake that only search in -# CLANG_ROOT if it is defined - -macro(_Clang_find_library VAR NAME) - # Windows needs lib prefix - if (CLANG_ROOT) - find_library(${VAR} NAMES ${NAME} lib${NAME} - NO_DEFAULT_PATH PATHS ${CLANG_ROOT} PATH_SUFFIXES lib) - else() - find_library(${VAR} NAMES ${NAME} lib${NAME}) - endif() -endmacro() - -macro(_Clang_find_add_library NAME) - _Clang_find_library(${NAME}_LIBRARY ${NAME}) - list(APPEND _Clang_LIBRARIES ${${NAME}_LIBRARY}) -endmacro() - -macro(_Clang_find_path VAR INCLUDE_FILE) - if (CLANG_ROOT) - find_path(${VAR} ${INCLUDE_FILE} - NO_DEFAULT_PATH PATHS ${CLANG_ROOT} PATH_SUFFIXES include) - else() - find_path(${VAR} ${INCLUDE_FILE}) - endif() -endmacro() - -macro(_Clang_find_program VAR NAME) - if (CLANG_ROOT) - find_program(${VAR} ${NAME} - NO_DEFAULT_PATH PATHS ${CLANG_ROOT} PATH_SUFFIXES bin) - else() - find_program(${VAR} ${NAME}) - endif() -endmacro() - -### Start - -set(_Clang_REQUIRED_VARS Clang_LIBRARY Clang_INCLUDE_DIR Clang_EXECUTABLE - Clang_RESOURCE_DIR Clang_VERSION - LLVM_INCLUDE_DIR LLVM_BUILD_INCLUDE_DIR) - -_Clang_find_library(Clang_LIBRARY clangIndex) -_Clang_find_add_library(clangFormat) -_Clang_find_add_library(clangTooling) - -_Clang_find_library(clangToolingInclusions_LIBRARY clangToolingInclusions) -if (clangToolingInclusions_LIBRARY) - list(APPEND _Clang_LIBRARIES ${clangToolingInclusions_LIBRARY}) -endif() - -_Clang_find_add_library(clangToolingCore) -_Clang_find_add_library(clangRewrite) -_Clang_find_add_library(clangFrontend) -_Clang_find_add_library(clangParse) -_Clang_find_add_library(clangSerialization) -_Clang_find_add_library(clangSema) -_Clang_find_add_library(clangAnalysis) -_Clang_find_add_library(clangEdit) -_Clang_find_add_library(clangAST) -_Clang_find_add_library(clangLex) -_Clang_find_add_library(clangDriver) -_Clang_find_add_library(clangBasic) -if(USE_SHARED_LLVM) - _Clang_find_add_library(LLVM) -else() - _Clang_find_add_library(LLVMMCParser) - _Clang_find_add_library(LLVMMC) - _Clang_find_add_library(LLVMBitReader) - _Clang_find_add_library(LLVMOption) - _Clang_find_add_library(LLVMProfileData) - _Clang_find_add_library(LLVMCore) - _Clang_find_add_library(LLVMBinaryFormat) - _Clang_find_add_library(LLVMSupport) - _Clang_find_add_library(LLVMDemangle) -endif() -_Clang_find_path(Clang_INCLUDE_DIR clang-c/Index.h) -_Clang_find_path(Clang_BUILD_INCLUDE_DIR clang/Driver/Options.inc) -_Clang_find_path(LLVM_INCLUDE_DIR llvm/PassInfo.h) -_Clang_find_path(LLVM_BUILD_INCLUDE_DIR llvm/Config/llvm-config.h) - -_Clang_find_program(Clang_EXECUTABLE clang) -if(Clang_EXECUTABLE) - # Find Clang resource directory with Clang executable - execute_process(COMMAND ${Clang_EXECUTABLE} -print-resource-dir - RESULT_VARIABLE _Clang_FIND_RESOURCE_DIR_RESULT - OUTPUT_VARIABLE Clang_RESOURCE_DIR - ERROR_VARIABLE _Clang_FIND_RESOURCE_DIR_ERROR - OUTPUT_STRIP_TRAILING_WHITESPACE) - - if(_Clang_FIND_RESOURCE_DIR_RESULT) - message(FATAL_ERROR "Error retrieving Clang resource directory with Clang \ -executable. Output:\n ${_Clang_FIND_RESOURCE_DIR_ERROR}") - endif() - - # Find Clang version - set(_Clang_VERSION_REGEX "([0-9]+)\\.([0-9]+)\\.([0-9]+)") - execute_process( - COMMAND ${Clang_EXECUTABLE} --version - OUTPUT_VARIABLE Clang_VERSION - ) - string(REGEX MATCH ${_Clang_VERSION_REGEX} Clang_VERSION ${Clang_VERSION}) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Clang - FOUND_VAR Clang_FOUND - REQUIRED_VARS ${_Clang_REQUIRED_VARS} - VERSION_VAR Clang_VERSION -) - -if(Clang_FOUND AND NOT TARGET Clang::Clang) - add_library(Clang::Clang UNKNOWN IMPORTED) - set_target_properties(Clang::Clang PROPERTIES - IMPORTED_LOCATION ${Clang_LIBRARY} - INTERFACE_INCLUDE_DIRECTORIES "${Clang_INCLUDE_DIR};${Clang_BUILD_INCLUDE_DIR};${LLVM_INCLUDE_DIR};${LLVM_BUILD_INCLUDE_DIR}") - if(NOT MSVC) - find_package(Curses REQUIRED) - find_package(ZLIB REQUIRED) - endif() - set_property(TARGET Clang::Clang PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES "${_Clang_LIBRARIES};${CURSES_LIBRARIES};${ZLIB_LIBRARIES}") - if(MINGW) - set_property(TARGET Clang::Clang APPEND_STRING PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES ";version") - endif() -endif() diff --git a/src/utils.cc b/src/utils.cc index c291de01d..09f14be97 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -177,5 +177,5 @@ int ReverseSubseqMatch(std::string_view pat, std::string_view text, return -1; } -std::string GetDefaultResourceDirectory() { return DEFAULT_RESOURCE_DIRECTORY; } -} +std::string GetDefaultResourceDirectory() { return CLANG_RESOURCE_DIRECTORY; } +} // namespace ccls From c74d3df3e449fbb8a87a399024e35ef39304badf Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sun, 10 Mar 2019 00:27:50 -0800 Subject: [PATCH 29/79] If the workspace folder is a symlink, convert paths relative to it (#314) If the workspace folder is a symlink and the client doesn't follow it. Treat /tmp/symlink/ as canonical and convert every /tmp/real/ path to /tmp/symlink/. --- src/clang_tu.cc | 11 ++--------- src/config.hh | 2 +- src/lsp.cc | 2 ++ src/messages/initialize.cc | 23 +++++++++++++++++------ src/messages/workspace.cc | 24 +++++++++++++++++------- src/pipeline.cc | 4 ++-- src/project.cc | 10 ++++++++-- src/utils.cc | 18 +++++++++++++++++- src/utils.hh | 2 ++ 9 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/clang_tu.cc b/src/clang_tu.cc index 220327421..224e885c9 100644 --- a/src/clang_tu.cc +++ b/src/clang_tu.cc @@ -18,15 +18,8 @@ std::string PathFromFileEntry(const FileEntry &file) { if (Name.empty()) Name = file.getName(); std::string ret = NormalizePath(Name); - // Resolve /usr/include/c++/7.3.0 symlink. - if (!llvm::any_of(g_config->workspaceFolders, [&](const std::string &root) { - return StringRef(ret).startswith(root); - })) { - SmallString<256> dest; - llvm::sys::fs::real_path(ret, dest); - ret = llvm::sys::path::convert_to_slash(dest.str()); - } - return ret; + // Resolve symlinks outside of workspace folders, e.g. /usr/include/c++/7.3.0 + return NormalizeFolder(ret) ? ret : RealPath(ret); } static Pos Decomposed2LineAndCol(const SourceManager &SM, diff --git a/src/config.hh b/src/config.hh index 63f3b38ae..145093caf 100644 --- a/src/config.hh +++ b/src/config.hh @@ -20,7 +20,7 @@ initialization options specified by the client. For example, in shell syntax: struct Config { // **Not available for configuration** std::string fallbackFolder; - std::vector workspaceFolders; + std::vector> workspaceFolders; // If specified, this option overrides compile_commands.json and this // external command will be executed with an option |projectRoot|. // The initialization options will be provided as stdin. diff --git a/src/lsp.cc b/src/lsp.cc index 182fd2ebf..1559ef42b 100644 --- a/src/lsp.cc +++ b/src/lsp.cc @@ -119,6 +119,8 @@ std::string DocumentUri::GetPath() const { ret[0] = toupper(ret[0]); } #endif + if (g_config) + NormalizeFolder(ret); return ret; } diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index d064891e8..3f561e2ab 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -326,19 +326,30 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { // Set project root. EnsureEndsInSlash(project_path); g_config->fallbackFolder = project_path; + auto &workspaceFolders = g_config->workspaceFolders; for (const WorkspaceFolder &wf : param.workspaceFolders) { std::string path = wf.uri.GetPath(); EnsureEndsInSlash(path); - g_config->workspaceFolders.push_back(path); - LOG_S(INFO) << "add workspace folder " << wf.name << ": " << path; + std::string real = RealPath(path) + '/'; + workspaceFolders.emplace_back(path, path == real ? "" : real); } - if (param.workspaceFolders.empty()) - g_config->workspaceFolders.push_back(project_path); + if (workspaceFolders.empty()) { + std::string real = RealPath(project_path) + '/'; + workspaceFolders.emplace_back(project_path, + project_path == real ? "" : real); + } + std::sort(workspaceFolders.begin(), workspaceFolders.end(), + [](auto &l, auto &r) { return l.first.size() > r.first.size(); }); + for (auto &[folder, real] : workspaceFolders) + if (real.empty()) + LOG_S(INFO) << "workspace folder: " << folder; + else + LOG_S(INFO) << "workspace folder: " << folder << " -> " << real; if (g_config->cache.directory.empty()) g_config->cache.retainInMemory = 1; else if (!g_config->cache.hierarchicalPath) - for (const std::string &folder : g_config->workspaceFolders) { + for (auto &[folder, _] : workspaceFolders) { // Create two cache directories for files inside and outside of the // project. std::string escaped = EscapeFileName(folder.substr(0, folder.size() - 1)); @@ -347,7 +358,7 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { } idx::Init(); - for (const std::string &folder : g_config->workspaceFolders) + for (auto &[folder, _] : workspaceFolders) m->project->Load(folder); // Start indexer threads. Start this after loading the project, as that diff --git a/src/messages/workspace.cc b/src/messages/workspace.cc index c7af61364..9468478c7 100644 --- a/src/messages/workspace.cc +++ b/src/messages/workspace.cc @@ -23,7 +23,7 @@ namespace ccls { REFLECT_STRUCT(SymbolInformation, name, kind, location, containerName); void MessageHandler::workspace_didChangeConfiguration(EmptyParam &) { - for (const std::string &folder : g_config->workspaceFolders) + for (auto &[folder, _] : g_config->workspaceFolders) project->Load(folder); project->Index(wfiles, RequestId()); @@ -70,7 +70,8 @@ void MessageHandler::workspace_didChangeWorkspaceFolders( std::string root = wf.uri.GetPath(); EnsureEndsInSlash(root); LOG_S(INFO) << "delete workspace folder " << wf.name << ": " << root; - auto it = llvm::find(g_config->workspaceFolders, root); + auto it = llvm::find_if(g_config->workspaceFolders, + [&](auto &folder) { return folder.first == root; }); if (it != g_config->workspaceFolders.end()) { g_config->workspaceFolders.erase(it); { @@ -80,12 +81,21 @@ void MessageHandler::workspace_didChangeWorkspaceFolders( project->root2folder.erase(root); } } + auto &workspaceFolders = g_config->workspaceFolders; for (const WorkspaceFolder &wf : param.event.added) { - std::string root = wf.uri.GetPath(); - EnsureEndsInSlash(root); - LOG_S(INFO) << "add workspace folder " << wf.name << ": " << root; - g_config->workspaceFolders.push_back(root); - project->Load(root); + std::string folder = wf.uri.GetPath(); + EnsureEndsInSlash(folder); + std::string real = RealPath(folder) + '/'; + if (folder == real) + real.clear(); + LOG_S(INFO) << "add workspace folder " << wf.name << ": " + << (real.empty() ? folder : folder + " -> " + real); + workspaceFolders.emplace_back(); + auto it = workspaceFolders.end() - 1; + for (; it != workspaceFolders.begin() && folder < it[-1].first; --it) + *it = it[-1]; + *it = {folder, real}; + project->Load(folder); } project->Index(wfiles, RequestId()); diff --git a/src/pipeline.cc b/src/pipeline.cc index dfd09d186..b74e1ff5d 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -133,7 +133,7 @@ std::string AppendSerializationFormat(const std::string &base) { } } -std::string GetCachePath(const std::string &src) { +std::string GetCachePath(std::string src) { if (g_config->cache.hierarchicalPath) { std::string ret = src[0] == '/' ? src.substr(1) : src; #ifdef _WIN32 @@ -141,7 +141,7 @@ std::string GetCachePath(const std::string &src) { #endif return g_config->cache.directory + ret; } - for (auto &root : g_config->workspaceFolders) + for (auto &[root, _] : g_config->workspaceFolders) if (StringRef(src).startswith(root)) { auto len = root.size(); return g_config->cache.directory + diff --git a/src/project.cc b/src/project.cc index 249141280..614172579 100644 --- a/src/project.cc +++ b/src/project.cc @@ -388,11 +388,17 @@ void Project::LoadDirectory(const std::string &root, Project::Folder &folder) { Project::Entry entry; entry.root = root; DoPathMapping(entry.root); - entry.directory = NormalizePath(Cmd.Directory); + + // If workspace folder is real/ but entries use symlink/, convert to + // real/. + entry.directory = RealPath(Cmd.Directory); + NormalizeFolder(entry.directory); DoPathMapping(entry.directory); entry.filename = - NormalizePath(ResolveIfRelative(entry.directory, Cmd.Filename)); + RealPath(ResolveIfRelative(entry.directory, Cmd.Filename)); + NormalizeFolder(entry.filename); DoPathMapping(entry.filename); + std::vector args = std::move(Cmd.CommandLine); entry.args.reserve(args.size()); for (std::string &arg : args) { diff --git a/src/utils.cc b/src/utils.cc index 09f14be97..06b243120 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -13,7 +13,6 @@ #include #include #include -using namespace llvm; #include #include @@ -24,6 +23,8 @@ using namespace llvm; #include #include +using namespace llvm; + namespace ccls { struct Matcher::Impl { std::regex regex; @@ -130,6 +131,21 @@ std::string ResolveIfRelative(const std::string &directory, return NormalizePath(Ret.str()); } +std::string RealPath(const std::string &path) { + SmallString<256> buf; + sys::fs::real_path(path, buf); + return buf.empty() ? path : llvm::sys::path::convert_to_slash(buf); +} + +bool NormalizeFolder(std::string &path) { + for (auto &[root, real] : g_config->workspaceFolders) + if (real.size() && llvm::StringRef(path).startswith(real)) { + path = root + path.substr(real.size()); + return true; + } + return false; +} + std::optional LastWriteTime(const std::string &path) { sys::fs::file_status Status; if (sys::fs::status(path, Status)) diff --git a/src/utils.hh b/src/utils.hh index 330259c96..9de85c8c7 100644 --- a/src/utils.hh +++ b/src/utils.hh @@ -50,6 +50,8 @@ std::string EscapeFileName(std::string path); std::string ResolveIfRelative(const std::string &directory, const std::string &path); +std::string RealPath(const std::string &path); +bool NormalizeFolder(std::string &path); std::optional LastWriteTime(const std::string &path); std::optional ReadContent(const std::string &filename); From 89ec8ed2367a786fc506c3c28535a5fc47816e2d Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sun, 10 Mar 2019 08:55:01 -0700 Subject: [PATCH 30/79] Add initialization option index.name.suppressUnwrittenScope (default: false) --- src/config.hh | 8 +++++++- src/indexer.cc | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/config.hh b/src/config.hh index 145093caf..085217f18 100644 --- a/src/config.hh +++ b/src/config.hh @@ -261,6 +261,11 @@ struct Config { std::vector multiVersionBlacklist; std::vector multiVersionWhitelist; + struct Name { + // Suppress inline and unnamed namespaces in identifier names. + bool suppressUnwrittenScope = false; + } name; + // Allow indexing on textDocument/didChange. // May be too slow for big projects, so it is off by default. bool onChange = false; @@ -326,9 +331,10 @@ REFLECT_STRUCT(Config::Completion, caseSensitivity, detailedLabel, REFLECT_STRUCT(Config::Diagnostics, blacklist, onChange, onOpen, onSave, spellChecking, whitelist) REFLECT_STRUCT(Config::Highlight, largeFileSize, lsRanges, blacklist, whitelist) +REFLECT_STRUCT(Config::Index::Name, suppressUnwrittenScope); REFLECT_STRUCT(Config::Index, blacklist, comments, initialBlacklist, initialWhitelist, maxInitializerLines, multiVersion, - multiVersionBlacklist, multiVersionWhitelist, onChange, + multiVersionBlacklist, multiVersionWhitelist, name, onChange, parametersInDeclarations, threads, trackDependency, whitelist); REFLECT_STRUCT(Config::Request, timeout); REFLECT_STRUCT(Config::Session, maxNum); diff --git a/src/indexer.cc b/src/indexer.cc index 00ed85222..70a5bfce1 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -476,6 +476,7 @@ class IndexDataConsumer : public index::IndexDataConsumer { PP.PolishForDeclaration = true; PP.ConstantsAsWritten = true; PP.SuppressTagKeyword = true; + PP.SuppressUnwrittenScope = g_config->index.name.suppressUnwrittenScope; PP.SuppressInitializers = true; PP.FullyQualifiedName = false; return PP; From 4d68101d0a3b9ebc03ef70c3a56d9fa7942dbcf2 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 15 Mar 2019 09:33:44 -0700 Subject: [PATCH 31/79] Change containers of Query*::Def fields from std::vector to ccls::Vec Query*::Def contain several immutable std::vector fields. Change them to ccls::Vec to save bytes which were wasted by `capacity`. --- src/indexer.hh | 48 ++++++++++------- src/messages/textDocument_references.cc | 6 ++- src/query.cc | 72 +++++++++++++++++++++---- src/query.hh | 15 +++--- src/utils.hh | 23 ++++++++ 5 files changed, 126 insertions(+), 38 deletions(-) diff --git a/src/indexer.hh b/src/indexer.hh index e1d577ab4..83c740059 100644 --- a/src/indexer.hh +++ b/src/indexer.hh @@ -131,6 +131,9 @@ void Reflect(BinaryWriter &visitor, SymbolRef &value); void Reflect(BinaryWriter &visitor, Use &value); void Reflect(BinaryWriter &visitor, DeclRef &value); +template +using VectorAdapter = std::vector>; + template struct NameMixin { std::string_view Name(bool qualified) const { auto self = static_cast(this); @@ -144,7 +147,8 @@ template struct NameMixin { } }; -struct FuncDef : NameMixin { +template