Support workspace folders

This commit is contained in:
Fangrui Song 2018-10-07 22:02:28 -07:00
parent de9c77e1cc
commit 5a1ed4c943
13 changed files with 300 additions and 192 deletions

View File

@ -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
)

View File

@ -30,7 +30,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());

View File

@ -29,8 +29,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<std::string> 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.

View File

@ -59,23 +59,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;
}
@ -119,13 +123,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<std::string> 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;
})
@ -152,22 +158,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<std::mutex> 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,

View File

@ -307,6 +307,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)

View File

@ -90,27 +90,24 @@ bool FindFileOrFail(DB *db, Project *project, std::optional<lsRequestId> id,
if (out_file_id)
*out_file_id = -1;
bool indexing;
bool has_entry = false;
{
std::lock_guard<std::mutex> 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);
}

View File

@ -61,7 +61,9 @@ struct Handler_cclsInfo : BaseMessageHandler<In_cclsInfo> {
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);
}
};

View File

@ -176,8 +176,17 @@ struct lsServerCapabilities {
struct ExecuteCommandOptions {
std::vector<std::string> 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,
@ -187,7 +196,7 @@ MAKE_REFLECT_STRUCT(lsServerCapabilities, textDocumentSync, hoverProvider,
codeLensProvider, documentFormattingProvider,
documentRangeFormattingProvider,
documentOnTypeFormattingProvider, renameProvider,
documentLinkProvider, executeCommandProvider);
documentLinkProvider, executeCommandProvider, workspace);
// Workspace specific client capabilities.
struct lsWorkspaceClientCapabilites {
@ -345,6 +354,8 @@ struct lsInitializeParams {
// The initial trace setting. If omitted trace is disabled ('off').
lsTrace trace = lsTrace::Off;
std::vector<lsWorkspaceFolder> workspaceFolders;
};
void Reflect(Reader &reader, lsInitializeParams::lsTrace &value) {
@ -378,7 +389,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
@ -484,19 +496,28 @@ struct Handler_Initialize : BaseMessageHandler<In_InitializeRequest> {
// 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

View File

@ -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 <llvm/ADT/STLExtras.h>
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<In_workspaceDidChangeConfiguration> {
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<lsWorkspaceFolder> 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<In_workspaceDidChangeWorkspaceFolders> {
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

View File

@ -1,49 +0,0 @@
/* 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 "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<In_WorkspaceDidChangeConfiguration> {
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

View File

@ -128,18 +128,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<IndexFile> RawCacheLoad(const std::string &path) {
@ -280,7 +279,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;
@ -343,8 +342,9 @@ bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles,
}
if (entry.id >= 0) {
std::lock_guard<std::mutex> 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;
}
}
}

View File

@ -54,7 +54,7 @@ enum class ProjectMode { CompileCommandsJson, DotCcls, ExternalCommand };
struct ProjectConfig {
std::unordered_set<std::string> quote_dirs;
std::unordered_set<std::string> angle_dirs;
std::string project_dir;
std::string root;
ProjectMode mode = ProjectMode::CompileCommandsJson;
};
@ -183,7 +183,7 @@ std::vector<Project::Entry> LoadFromDirectoryListing(ProjectConfig *config) {
std::vector<Project::Entry> 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 "
@ -191,9 +191,9 @@ std::vector<Project::Entry> LoadFromDirectoryListing(ProjectConfig *config) {
std::unordered_map<std::string, std::vector<const char *>> folder_args;
std::vector<std::string> 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) {
@ -211,7 +211,7 @@ std::vector<Project::Entry> 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);
@ -219,17 +219,18 @@ std::vector<Project::Entry> 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())
@ -247,7 +248,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);
@ -257,8 +258,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;
@ -276,7 +276,7 @@ LoadEntriesFromDirectory(ProjectConfig *project,
Reflect(json_writer, *g_config);
std::string contents = GetExternalCommandOutput(
std::vector<std::string>{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);
@ -307,6 +307,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 =
@ -342,77 +344,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<const char *> &args,
const std::string &path) {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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) {
@ -442,18 +445,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++;
}
}
}

View File

@ -28,6 +28,7 @@ struct WorkingFiles;
struct Project {
struct Entry {
std::string root;
std::string directory;
std::string filename;
std::vector<const char *> args;
@ -36,14 +37,18 @@ struct Project {
int id = -1;
};
// Include directories for "" headers
std::vector<std::string> quote_include_directories;
// Include directories for <> headers
std::vector<std::string> angle_include_directories;
struct Folder {
std::string name;
// Include directories for <> headers
std::vector<std::string> angle_search_list;
// Include directories for "" headers
std::vector<std::string> quote_search_list;
std::vector<Entry> entries;
std::unordered_map<std::string, int> path2entry_index;
};
std::vector<Entry> entries;
std::mutex mutex_;
std::unordered_map<std::string, int> path_to_entry_index;
std::unordered_map<std::string, Folder> root2folder;
// Loads a project for the given |directory|.
//