diff --git a/CMakeLists.txt b/CMakeLists.txt index 47af70f0..29a3008f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -231,7 +231,7 @@ target_sources(ccls PRIVATE src/messages/textDocument_rename.cc src/messages/textDocument_signatureHelp.cc src/messages/textDocument_typeDefinition.cc - src/messages/workspace_didChangeConfiguration.cc + src/messages/workspace_did.cc src/messages/workspace_didChangeWatchedFiles.cc src/messages/workspace_symbol.cc ) diff --git a/src/clang_tu.cc b/src/clang_tu.cc index 9812799b..fd82f402 100644 --- a/src/clang_tu.cc +++ b/src/clang_tu.cc @@ -18,7 +18,9 @@ std::string PathFromFileEntry(const FileEntry &file) { Name = file.getName(); std::string ret = NormalizePath(Name); // Resolve /usr/include/c++/7.3.0 symlink. - if (!StartsWith(ret, g_config->projectRoot)) { + if (!llvm::any_of(g_config->workspaceFolders, [&](const std::string &root) { + return StartsWith(ret, root); + })) { SmallString<256> dest; llvm::sys::fs::real_path(ret, dest); ret = llvm::sys::path::convert_to_slash(dest.str()); diff --git a/src/config.h b/src/config.h index 87b6913a..10391fe1 100644 --- a/src/config.h +++ b/src/config.h @@ -17,8 +17,9 @@ initialization options specified by the client. For example, in shell syntax: '--init={"index": {"comments": 2, "whitelist": ["."]}}' */ struct Config { - // Root directory of the project. **Not available for configuration** - std::string projectRoot; + // **Not available for configuration** + std::string fallbackFolder; + 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/include_complete.cc b/src/include_complete.cc index 1ea18504..b7afaeaf 100644 --- a/src/include_complete.cc +++ b/src/include_complete.cc @@ -47,23 +47,27 @@ size_t TrimCommonPathPrefix(const std::string &result, } // Returns true iff angle brackets should be used. -bool TrimPath(Project *project, const std::string &project_root, - std::string *insert_path) { - size_t start = TrimCommonPathPrefix(*insert_path, project_root); +bool TrimPath(Project *project, std::string &path) { + size_t pos = 0; bool angle = false; - - for (auto &include_dir : project->quote_include_directories) - start = std::max(start, TrimCommonPathPrefix(*insert_path, include_dir)); - - for (auto &include_dir : project->angle_include_directories) { - auto len = TrimCommonPathPrefix(*insert_path, include_dir); - if (len > start) { - start = len; + for (auto &[root, folder] : project->root2folder) { + size_t pos1 = 0; + for (auto &search : folder.angle_search_list) + pos1 = std::max(pos1, TrimCommonPathPrefix(path, search)); + if (pos1 > pos) { + pos = pos1; angle = true; } - } - *insert_path = insert_path->substr(start); + pos1 = TrimCommonPathPrefix(path, root); + for (auto &search : folder.quote_search_list) + pos1 = std::max(pos1, TrimCommonPathPrefix(path, search)); + if (pos1 > pos) { + pos = pos1; + angle = false; + } + } + path = path.substr(pos); return angle; } @@ -107,13 +111,15 @@ void IncludeComplete::Rescan() { is_scanning = true; std::thread([this]() { set_thread_name("include"); - Timer timer("include", "scan include paths"); - TimeRegion region(timer); - - for (const std::string &dir : project_->quote_include_directories) - InsertIncludesFromDirectory(dir, false /*use_angle_brackets*/); - for (const std::string &dir : project_->angle_include_directories) - InsertIncludesFromDirectory(dir, true /*use_angle_brackets*/); + std::unordered_set angle_set, quote_set; + for (auto &[root, folder] : project_->root2folder) { + for (const std::string &search : folder.angle_search_list) + if (angle_set.insert(search).second) + InsertIncludesFromDirectory(search, true); + for (const std::string &search : folder.quote_search_list) + if (quote_set.insert(search).second) + InsertIncludesFromDirectory(search, false); + } is_scanning = false; }) @@ -140,22 +146,21 @@ void IncludeComplete::InsertCompletionItem(const std::string &absolute_path, } } -void IncludeComplete::AddFile(const std::string &absolute_path) { - if (!EndsWithAny(absolute_path, g_config->completion.include.suffixWhitelist)) +void IncludeComplete::AddFile(const std::string &path) { + if (!EndsWithAny(path, g_config->completion.include.suffixWhitelist)) return; - if (match_ && !match_->IsMatch(absolute_path)) + if (match_ && !match_->IsMatch(path)) return; - std::string trimmed_path = absolute_path; - bool use_angle_brackets = - TrimPath(project_, g_config->projectRoot, &trimmed_path); + std::string trimmed_path = path; + bool use_angle_brackets = TrimPath(project_, trimmed_path); lsCompletionItem item = BuildCompletionItem(trimmed_path, use_angle_brackets, false /*is_stl*/); std::unique_lock lock(completion_items_mutex, std::defer_lock); if (is_scanning) lock.lock(); - InsertCompletionItem(absolute_path, std::move(item)); + InsertCompletionItem(path, std::move(item)); } void IncludeComplete::InsertIncludesFromDirectory(std::string directory, diff --git a/src/lsp.h b/src/lsp.h index 50761ef9..722f9cbe 100644 --- a/src/lsp.h +++ b/src/lsp.h @@ -295,6 +295,12 @@ struct lsTextDocumentDidChangeParams { MAKE_REFLECT_STRUCT(lsTextDocumentDidChangeParams, textDocument, contentChanges); +struct lsWorkspaceFolder { + lsDocumentUri uri; + std::string name; +}; +MAKE_REFLECT_STRUCT(lsWorkspaceFolder, uri, name); + // Show a message to the user. enum class lsMessageType : int { Error = 1, Warning = 2, Info = 3, Log = 4 }; MAKE_REFLECT_TYPE_PROXY(lsMessageType) diff --git a/src/message_handler.cc b/src/message_handler.cc index 465d21c2..b1fb6fad 100644 --- a/src/message_handler.cc +++ b/src/message_handler.cc @@ -78,27 +78,24 @@ bool FindFileOrFail(DB *db, Project *project, std::optional id, if (out_file_id) *out_file_id = -1; - bool indexing; + bool has_entry = false; { std::lock_guard lock(project->mutex_); - indexing = project->path_to_entry_index.find(absolute_path) != - project->path_to_entry_index.end(); + for (auto &[root, folder] : project->root2folder) + has_entry |= folder.path2entry_index.count(absolute_path); } - if (indexing) - LOG_S(INFO) << "\"" << absolute_path << "\" is being indexed."; - else - LOG_S(INFO) << "unable to find file \"" << absolute_path << "\""; if (id) { Out_Error out; out.id = *id; - if (indexing) { + if (has_entry) { out.error.code = lsErrorCodes::ServerNotInitialized; - out.error.message = absolute_path + " is being indexed."; + out.error.message = absolute_path + " is being indexed"; } else { out.error.code = lsErrorCodes::InternalError; out.error.message = "Unable to find file " + absolute_path; } + LOG_S(INFO) << out.error.message; pipeline::WriteStdout(kMethodType_Unknown, out); } diff --git a/src/messages/ccls_info.cc b/src/messages/ccls_info.cc index 667948d9..15ba8bea 100644 --- a/src/messages/ccls_info.cc +++ b/src/messages/ccls_info.cc @@ -49,7 +49,9 @@ struct Handler_cclsInfo : BaseMessageHandler { out.result.db.types = db->types.size(); out.result.db.vars = db->vars.size(); out.result.pipeline.pendingIndexRequests = pipeline::pending_index_requests; - out.result.project.entries = project->entries.size(); + out.result.project.entries = 0; + for (auto &[_, folder] : project->root2folder) + out.result.project.entries += folder.entries.size(); pipeline::WriteStdout(cclsInfo, out); } }; diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index f9a79770..c2513b8e 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -164,8 +164,17 @@ struct lsServerCapabilities { struct ExecuteCommandOptions { std::vector commands{std::string(ccls_xref)}; } executeCommandProvider; + struct Workspace { + struct WorkspaceFolders { + bool supported = true; + bool changeNotifications = true; + } workspaceFolders; + } workspace; }; MAKE_REFLECT_STRUCT(lsServerCapabilities::ExecuteCommandOptions, commands); +MAKE_REFLECT_STRUCT(lsServerCapabilities::Workspace::WorkspaceFolders, + supported, changeNotifications); +MAKE_REFLECT_STRUCT(lsServerCapabilities::Workspace, workspaceFolders); MAKE_REFLECT_STRUCT(lsServerCapabilities, textDocumentSync, hoverProvider, completionProvider, signatureHelpProvider, definitionProvider, implementationProvider, @@ -175,7 +184,7 @@ MAKE_REFLECT_STRUCT(lsServerCapabilities, textDocumentSync, hoverProvider, codeLensProvider, documentFormattingProvider, documentRangeFormattingProvider, documentOnTypeFormattingProvider, renameProvider, - documentLinkProvider, executeCommandProvider); + documentLinkProvider, executeCommandProvider, workspace); // Workspace specific client capabilities. struct lsWorkspaceClientCapabilites { @@ -333,6 +342,8 @@ struct lsInitializeParams { // The initial trace setting. If omitted trace is disabled ('off'). lsTrace trace = lsTrace::Off; + + std::vector workspaceFolders; }; void Reflect(Reader &reader, lsInitializeParams::lsTrace &value) { @@ -366,7 +377,8 @@ void Reflect(Writer& writer, lsInitializeParams::lsTrace& value) { #endif MAKE_REFLECT_STRUCT(lsInitializeParams, processId, rootPath, rootUri, - initializationOptions, capabilities, trace); + initializationOptions, capabilities, trace, + workspaceFolders); struct lsInitializeError { // Indicates whether the client should retry to send the @@ -472,19 +484,28 @@ struct Handler_Initialize : BaseMessageHandler { // Set project root. EnsureEndsInSlash(project_path); - g_config->projectRoot = project_path; - if (g_config->cacheDirectory.size()) { - // Create two cache directories for files inside and outside of the - // project. - auto len = g_config->projectRoot.size(); - std::string escaped = EscapeFileName(g_config->projectRoot.substr(0, len - 1)); - sys::fs::create_directories(g_config->cacheDirectory + escaped); - sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped); + g_config->fallbackFolder = project_path; + for (const lsWorkspaceFolder &wf : request->params.workspaceFolders) { + std::string path = wf.uri.GetPath(); + EnsureEndsInSlash(path); + g_config->workspaceFolders.push_back(path); + LOG_S(INFO) << "add workspace folder " << wf.name << ": " << path; } + if (request->params.workspaceFolders.empty()) + g_config->workspaceFolders.push_back(project_path); + if (g_config->cacheDirectory.size()) + 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); + } idx::Init(); - - project->Load(project_path); + for (const std::string &folder : g_config->workspaceFolders) + project->Load(folder); // Start indexer threads. Start this after loading the project, as that // may take a long time. Indexer threads will emit status/progress diff --git a/src/messages/workspace_did.cc b/src/messages/workspace_did.cc new file mode 100644 index 00000000..949436f5 --- /dev/null +++ b/src/messages/workspace_did.cc @@ -0,0 +1,108 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "clang_complete.hh" +#include "log.hh" +#include "message_handler.h" +#include "pipeline.hh" +#include "project.h" +#include "working_files.h" + +#include +using namespace ccls; + +namespace { +MethodType didChangeConfiguration = "workspace/didChangeConfiguration", + didChangeWorkspaceFolders = "workspace/didChangeWorkspaceFolders"; + +struct lsDidChangeConfigurationParams { + bool placeholder; +}; +MAKE_REFLECT_STRUCT(lsDidChangeConfigurationParams, placeholder); + +struct In_workspaceDidChangeConfiguration : public NotificationInMessage { + MethodType GetMethodType() const override { return didChangeConfiguration; } + lsDidChangeConfigurationParams params; +}; +MAKE_REFLECT_STRUCT(In_workspaceDidChangeConfiguration, params); +REGISTER_IN_MESSAGE(In_workspaceDidChangeConfiguration); + +struct Handler_workspaceDidChangeConfiguration + : BaseMessageHandler { + MethodType GetMethodType() const override { return didChangeConfiguration; } + void Run(In_workspaceDidChangeConfiguration *request) override { + for (const std::string &folder : g_config->workspaceFolders) + project->Load(folder); + + project->Index(working_files, lsRequestId()); + + clang_complete->FlushAllSessions(); + } +}; +REGISTER_MESSAGE_HANDLER(Handler_workspaceDidChangeConfiguration); + +struct lsWorkspaceFoldersChangeEvent { + std::vector added, removed; +}; +MAKE_REFLECT_STRUCT(lsWorkspaceFoldersChangeEvent, added, removed); + +struct In_workspaceDidChangeWorkspaceFolders : public NotificationInMessage { + MethodType GetMethodType() const override { + return didChangeWorkspaceFolders; + } + struct Params { + lsWorkspaceFoldersChangeEvent event; + } params; +}; +MAKE_REFLECT_STRUCT(In_workspaceDidChangeWorkspaceFolders::Params, event); +MAKE_REFLECT_STRUCT(In_workspaceDidChangeWorkspaceFolders, params); +REGISTER_IN_MESSAGE(In_workspaceDidChangeWorkspaceFolders); + +struct Handler_workspaceDidChangeWorkspaceFolders + : BaseMessageHandler { + MethodType GetMethodType() const override { + return didChangeWorkspaceFolders; + } + void Run(In_workspaceDidChangeWorkspaceFolders *request) override { + const auto &event = request->params.event; + for (const lsWorkspaceFolder &wf : event.removed) { + 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); + if (it != g_config->workspaceFolders.end()) { + g_config->workspaceFolders.erase(it); + { + // auto &folder = project->root2folder[path]; + // FIXME delete + } + project->root2folder.erase(root); + } + } + for (const lsWorkspaceFolder &wf : 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); + } + + project->Index(working_files, lsRequestId()); + + clang_complete->FlushAllSessions(); + } +}; +REGISTER_MESSAGE_HANDLER(Handler_workspaceDidChangeWorkspaceFolders); +} // namespace diff --git a/src/messages/workspace_didChangeConfiguration.cc b/src/messages/workspace_didChangeConfiguration.cc deleted file mode 100644 index 7ae555a8..00000000 --- a/src/messages/workspace_didChangeConfiguration.cc +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2017-2018 ccls Authors -// SPDX-License-Identifier: Apache-2.0 - -#include "clang_complete.hh" -#include "message_handler.h" -#include "pipeline.hh" -#include "project.h" -#include "working_files.h" -using namespace ccls; - -namespace { -MethodType kMethodType = "workspace/didChangeConfiguration"; - -struct lsDidChangeConfigurationParams { - bool placeholder; -}; -MAKE_REFLECT_STRUCT(lsDidChangeConfigurationParams, placeholder); - -struct In_WorkspaceDidChangeConfiguration : public NotificationInMessage { - MethodType GetMethodType() const override { return kMethodType; } - lsDidChangeConfigurationParams params; -}; -MAKE_REFLECT_STRUCT(In_WorkspaceDidChangeConfiguration, params); -REGISTER_IN_MESSAGE(In_WorkspaceDidChangeConfiguration); - -struct Handler_WorkspaceDidChangeConfiguration - : BaseMessageHandler { - MethodType GetMethodType() const override { return kMethodType; } - void Run(In_WorkspaceDidChangeConfiguration *request) override { - project->Load(g_config->projectRoot); - project->Index(working_files, lsRequestId()); - - clang_complete->FlushAllSessions(); - } -}; -REGISTER_MESSAGE_HANDLER(Handler_WorkspaceDidChangeConfiguration); -} // namespace diff --git a/src/pipeline.cc b/src/pipeline.cc index c68774f2..928a5706 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -116,18 +116,17 @@ std::string AppendSerializationFormat(const std::string &base) { } std::string GetCachePath(const std::string &source_file) { - std::string cache_file; - auto len = g_config->projectRoot.size(); - if (StartsWith(source_file, g_config->projectRoot)) { - cache_file = EscapeFileName(g_config->projectRoot.substr(0, len - 1)) + '/' + - EscapeFileName(source_file.substr(len)); - } else { - cache_file = '@' + - EscapeFileName(g_config->projectRoot.substr(0, len - 1)) + '/' + - EscapeFileName(source_file); - } - - return g_config->cacheDirectory + cache_file; + for (auto &root : g_config->workspaceFolders) + if (StartsWith(source_file, root)) { + auto len = root.size(); + return g_config->cacheDirectory + + EscapeFileName(root.substr(0, len - 1)) + '/' + + EscapeFileName(source_file.substr(len)); + } + return g_config->cacheDirectory + '@' + + EscapeFileName(g_config->fallbackFolder.substr( + 0, g_config->fallbackFolder.size() - 1)) + + '/' + EscapeFileName(source_file); } std::unique_ptr RawCacheLoad(const std::string &path) { @@ -268,7 +267,7 @@ bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles, request.mode != IndexMode::NonInteractive); if (entry.id >= 0) { std::lock_guard lock2(project->mutex_); - project->path_to_entry_index[path] = entry.id; + project->root2folder[entry.root].path2entry_index[path] = entry.id; } } return true; @@ -331,8 +330,9 @@ bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles, } if (entry.id >= 0) { std::lock_guard lock(project->mutex_); + auto &folder = project->root2folder[entry.root]; for (auto &dep : curr->dependencies) - project->path_to_entry_index[dep.first.val().str()] = entry.id; + folder.path2entry_index[dep.first.val().str()] = entry.id; } } } diff --git a/src/project.cc b/src/project.cc index 4faba32c..c97e9aa5 100644 --- a/src/project.cc +++ b/src/project.cc @@ -42,7 +42,7 @@ enum class ProjectMode { CompileCommandsJson, DotCcls, ExternalCommand }; struct ProjectConfig { std::unordered_set quote_dirs; std::unordered_set angle_dirs; - std::string project_dir; + std::string root; ProjectMode mode = ProjectMode::CompileCommandsJson; }; @@ -171,7 +171,7 @@ std::vector LoadFromDirectoryListing(ProjectConfig *config) { std::vector result; config->mode = ProjectMode::DotCcls; SmallString<256> Path; - sys::path::append(Path, config->project_dir, ".ccls"); + sys::path::append(Path, config->root, ".ccls"); LOG_IF_S(WARNING, !sys::fs::exists(Path) && g_config->clang.extraArgs.empty()) << "ccls has no clang arguments. Use either " "compile_commands.json or .ccls, See ccls README for " @@ -179,9 +179,9 @@ std::vector LoadFromDirectoryListing(ProjectConfig *config) { std::unordered_map> folder_args; std::vector files; - const std::string &project_dir = config->project_dir; + const std::string &root = config->root; - GetFilesInFolder(project_dir, true /*recursive*/, + GetFilesInFolder(root, true /*recursive*/, true /*add_folder_to_path*/, [&folder_args, &files](const std::string &path) { if (SourceFileLanguage(path) != LanguageId::Unknown) { @@ -199,7 +199,7 @@ std::vector LoadFromDirectoryListing(ProjectConfig *config) { } }); - auto GetCompilerArgumentForFile = [&project_dir, + auto GetCompilerArgumentForFile = [&root, &folder_args](std::string cur) { while (!(cur = sys::path::parent_path(cur)).empty()) { auto it = folder_args.find(cur); @@ -207,17 +207,18 @@ std::vector LoadFromDirectoryListing(ProjectConfig *config) { return it->second; std::string normalized = NormalizePath(cur); // Break if outside of the project root. - if (normalized.size() <= project_dir.size() || - normalized.compare(0, project_dir.size(), project_dir) != 0) + if (normalized.size() <= root.size() || + normalized.compare(0, root.size(), root) != 0) break; } - return folder_args[project_dir]; + return folder_args[root]; }; ProjectProcessor proc(config); for (const std::string &file : files) { Project::Entry e; - e.directory = config->project_dir; + e.root = config->root; + e.directory = config->root; e.filename = file; e.args = GetCompilerArgumentForFile(file); if (e.args.empty()) @@ -235,7 +236,7 @@ LoadEntriesFromDirectory(ProjectConfig *project, const std::string &opt_compdb_dir) { // If there is a .ccls file always load using directory listing. SmallString<256> Path; - sys::path::append(Path, project->project_dir, ".ccls"); + sys::path::append(Path, project->root, ".ccls"); if (sys::fs::exists(Path)) return LoadFromDirectoryListing(project); @@ -245,8 +246,7 @@ LoadEntriesFromDirectory(ProjectConfig *project, if (g_config->compilationDatabaseCommand.empty()) { project->mode = ProjectMode::CompileCommandsJson; // Try to load compile_commands.json, but fallback to a project listing. - comp_db_dir = - opt_compdb_dir.empty() ? project->project_dir : opt_compdb_dir; + comp_db_dir = opt_compdb_dir.empty() ? project->root : opt_compdb_dir; sys::path::append(Path, comp_db_dir, "compile_commands.json"); } else { project->mode = ProjectMode::ExternalCommand; @@ -264,7 +264,7 @@ LoadEntriesFromDirectory(ProjectConfig *project, Reflect(json_writer, *g_config); std::string contents = GetExternalCommandOutput( std::vector{g_config->compilationDatabaseCommand, - project->project_dir}, + project->root}, input.GetString()); FILE *fout = fopen(Path.c_str(), "wb"); fwrite(contents.c_str(), contents.size(), 1, fout); @@ -295,6 +295,8 @@ LoadEntriesFromDirectory(ProjectConfig *project, ProjectProcessor proc(project); for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) { Project::Entry entry; + entry.root = project->root; + DoPathMapping(entry.root); entry.directory = NormalizePath(Cmd.Directory); DoPathMapping(entry.directory); entry.filename = @@ -330,77 +332,78 @@ int ComputeGuessScore(std::string_view a, std::string_view b) { } // namespace -void Project::Load(const std::string &root_directory) { +void Project::Load(const std::string &root) { + assert(root.back() == '/'); ProjectConfig project; - project.project_dir = root_directory; - entries = LoadEntriesFromDirectory(&project, - g_config->compilationDatabaseDirectory); + project.root = root; + Folder &folder = root2folder[root]; - // Cleanup / postprocess include directories. - quote_include_directories.assign(project.quote_dirs.begin(), - project.quote_dirs.end()); - angle_include_directories.assign(project.angle_dirs.begin(), - project.angle_dirs.end()); - for (std::string &path : quote_include_directories) { + folder.entries = LoadEntriesFromDirectory( + &project, g_config->compilationDatabaseDirectory); + folder.quote_search_list.assign(project.quote_dirs.begin(), + project.quote_dirs.end()); + folder.angle_search_list.assign(project.angle_dirs.begin(), + project.angle_dirs.end()); + for (std::string &path : folder.angle_search_list) { EnsureEndsInSlash(path); - LOG_S(INFO) << "quote_include_dir: " << path; + LOG_S(INFO) << "angle search: " << path; } - for (std::string &path : angle_include_directories) { + for (std::string &path : folder.quote_search_list) { EnsureEndsInSlash(path); - LOG_S(INFO) << "angle_include_dir: " << path; + LOG_S(INFO) << "quote search: " << path; } // Setup project entries. std::lock_guard lock(mutex_); - path_to_entry_index.reserve(entries.size()); - for (size_t i = 0; i < entries.size(); ++i) { - entries[i].id = i; - path_to_entry_index[entries[i].filename] = i; + folder.path2entry_index.reserve(folder.entries.size()); + for (size_t i = 0; i < folder.entries.size(); ++i) { + folder.entries[i].id = i; + folder.path2entry_index[folder.entries[i].filename] = i; } } void Project::SetArgsForFile(const std::vector &args, const std::string &path) { std::lock_guard lock(mutex_); - auto it = path_to_entry_index.find(path); - if (it != path_to_entry_index.end()) { - // The entry already exists in the project, just set the flags. - this->entries[it->second].args = args; - } else { - // Entry wasn't found, so we create a new one. - Entry entry; - entry.is_inferred = false; - entry.filename = path; - entry.args = args; - this->entries.emplace_back(entry); + for (auto &[root, folder] : root2folder) { + auto it = folder.path2entry_index.find(path); + if (it != folder.path2entry_index.end()) { + // The entry already exists in the project, just set the flags. + folder.entries[it->second].args = args; + return; + } } } Project::Entry Project::FindEntry(const std::string &path, bool can_be_inferred) { - { - std::lock_guard lock(mutex_); - auto it = path_to_entry_index.find(path); - if (it != path_to_entry_index.end()) { - Project::Entry &entry = entries[it->second]; + std::lock_guard lock(mutex_); + for (auto &[root, folder] : root2folder) { + auto it = folder.path2entry_index.find(path); + if (it != folder.path2entry_index.end()) { + Project::Entry &entry = folder.entries[it->second]; if (can_be_inferred || entry.filename == path) return entry; } } - // We couldn't find the file. Try to infer it. - // TODO: Cache inferred file in a separate array (using a lock or similar) - Entry *best_entry = nullptr; - int best_score = INT_MIN; - for (Entry &entry : entries) { - int score = ComputeGuessScore(path, entry.filename); - if (score > best_score) { - best_score = score; - best_entry = &entry; - } - } - Project::Entry result; + const Entry *best_entry = nullptr; + int best_score = INT_MIN; + for (auto &[root, folder] : root2folder) { + for (const Entry &entry : folder.entries) { + int score = ComputeGuessScore(path, entry.filename); + if (score > best_score) { + best_score = score; + best_entry = &entry; + } + } + if (StartsWith(path, root)) + result.root = root; + } + if (result.root.empty()) + result.root = g_config->fallbackFolder; + result.is_inferred = true; result.filename = path; if (!best_entry) { @@ -430,18 +433,25 @@ void Project::Index(WorkingFiles *wfiles, lsRequestId id) { auto &gi = g_config->index; GroupMatch match(gi.whitelist, gi.blacklist), match_i(gi.initialWhitelist, gi.initialBlacklist); - for (int i = 0; i < entries.size(); ++i) { - const Project::Entry &entry = entries[i]; - std::string reason; - if (match.IsMatch(entry.filename, &reason) && - match_i.IsMatch(entry.filename, &reason)) { - bool interactive = wfiles->GetFileByFilename(entry.filename) != nullptr; - pipeline::Index( - entry.filename, entry.args, - interactive ? IndexMode::Normal : IndexMode::NonInteractive, id); - } else { - LOG_V(1) << "[" << i << "/" << entries.size() << "]: " << reason - << "; skip " << entry.filename; + { + std::lock_guard lock(mutex_); + for (auto &[root, folder] : root2folder) { + int i = 0; + for (const Project::Entry &entry : folder.entries) { + std::string reason; + if (match.IsMatch(entry.filename, &reason) && + match_i.IsMatch(entry.filename, &reason)) { + bool interactive = + wfiles->GetFileByFilename(entry.filename) != nullptr; + pipeline::Index( + entry.filename, entry.args, + interactive ? IndexMode::Normal : IndexMode::NonInteractive, id); + } else { + LOG_V(1) << "[" << i << "/" << folder.entries.size() << "]: " << reason + << "; skip " << entry.filename; + } + i++; + } } } diff --git a/src/project.h b/src/project.h index 3c8ce989..de60ef69 100644 --- a/src/project.h +++ b/src/project.h @@ -16,6 +16,7 @@ struct WorkingFiles; struct Project { struct Entry { + std::string root; std::string directory; std::string filename; std::vector args; @@ -24,14 +25,18 @@ struct Project { int id = -1; }; - // Include directories for "" headers - std::vector quote_include_directories; - // Include directories for <> headers - std::vector angle_include_directories; + struct Folder { + std::string name; + // Include directories for <> headers + std::vector angle_search_list; + // Include directories for "" headers + std::vector quote_search_list; + std::vector entries; + std::unordered_map path2entry_index; + }; - std::vector entries; std::mutex mutex_; - std::unordered_map path_to_entry_index; + std::unordered_map root2folder; // Loads a project for the given |directory|. //