Support workspace folders

This commit is contained in:
Fangrui Song 2018-10-07 22:02:28 -07:00
parent e352604ee4
commit 49dd0ed558
13 changed files with 300 additions and 180 deletions

View File

@ -231,7 +231,7 @@ target_sources(ccls PRIVATE
src/messages/textDocument_rename.cc src/messages/textDocument_rename.cc
src/messages/textDocument_signatureHelp.cc src/messages/textDocument_signatureHelp.cc
src/messages/textDocument_typeDefinition.cc src/messages/textDocument_typeDefinition.cc
src/messages/workspace_didChangeConfiguration.cc src/messages/workspace_did.cc
src/messages/workspace_didChangeWatchedFiles.cc src/messages/workspace_didChangeWatchedFiles.cc
src/messages/workspace_symbol.cc src/messages/workspace_symbol.cc
) )

View File

@ -18,7 +18,9 @@ std::string PathFromFileEntry(const FileEntry &file) {
Name = file.getName(); Name = file.getName();
std::string ret = NormalizePath(Name); std::string ret = NormalizePath(Name);
// Resolve /usr/include/c++/7.3.0 symlink. // 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; SmallString<256> dest;
llvm::sys::fs::real_path(ret, dest); llvm::sys::fs::real_path(ret, dest);
ret = llvm::sys::path::convert_to_slash(dest.str()); ret = llvm::sys::path::convert_to_slash(dest.str());

View File

@ -17,8 +17,9 @@ initialization options specified by the client. For example, in shell syntax:
'--init={"index": {"comments": 2, "whitelist": ["."]}}' '--init={"index": {"comments": 2, "whitelist": ["."]}}'
*/ */
struct Config { struct Config {
// Root directory of the project. **Not available for configuration** // **Not available for configuration**
std::string projectRoot; std::string fallbackFolder;
std::vector<std::string> workspaceFolders;
// If specified, this option overrides compile_commands.json and this // If specified, this option overrides compile_commands.json and this
// external command will be executed with an option |projectRoot|. // external command will be executed with an option |projectRoot|.
// The initialization options will be provided as stdin. // The initialization options will be provided as stdin.

View File

@ -47,23 +47,27 @@ size_t TrimCommonPathPrefix(const std::string &result,
} }
// Returns true iff angle brackets should be used. // Returns true iff angle brackets should be used.
bool TrimPath(Project *project, const std::string &project_root, bool TrimPath(Project *project, std::string &path) {
std::string *insert_path) { size_t pos = 0;
size_t start = TrimCommonPathPrefix(*insert_path, project_root);
bool angle = false; bool angle = false;
for (auto &[root, folder] : project->root2folder) {
for (auto &include_dir : project->quote_include_directories) size_t pos1 = 0;
start = std::max(start, TrimCommonPathPrefix(*insert_path, include_dir)); for (auto &search : folder.angle_search_list)
pos1 = std::max(pos1, TrimCommonPathPrefix(path, search));
for (auto &include_dir : project->angle_include_directories) { if (pos1 > pos) {
auto len = TrimCommonPathPrefix(*insert_path, include_dir); pos = pos1;
if (len > start) {
start = len;
angle = true; 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; return angle;
} }
@ -107,13 +111,15 @@ void IncludeComplete::Rescan() {
is_scanning = true; is_scanning = true;
std::thread([this]() { std::thread([this]() {
set_thread_name("include"); set_thread_name("include");
Timer timer("include", "scan include paths"); std::unordered_set<std::string> angle_set, quote_set;
TimeRegion region(timer); for (auto &[root, folder] : project_->root2folder) {
for (const std::string &search : folder.angle_search_list)
for (const std::string &dir : project_->quote_include_directories) if (angle_set.insert(search).second)
InsertIncludesFromDirectory(dir, false /*use_angle_brackets*/); InsertIncludesFromDirectory(search, true);
for (const std::string &dir : project_->angle_include_directories) for (const std::string &search : folder.quote_search_list)
InsertIncludesFromDirectory(dir, true /*use_angle_brackets*/); if (quote_set.insert(search).second)
InsertIncludesFromDirectory(search, false);
}
is_scanning = false; is_scanning = false;
}) })
@ -140,22 +146,21 @@ void IncludeComplete::InsertCompletionItem(const std::string &absolute_path,
} }
} }
void IncludeComplete::AddFile(const std::string &absolute_path) { void IncludeComplete::AddFile(const std::string &path) {
if (!EndsWithAny(absolute_path, g_config->completion.include.suffixWhitelist)) if (!EndsWithAny(path, g_config->completion.include.suffixWhitelist))
return; return;
if (match_ && !match_->IsMatch(absolute_path)) if (match_ && !match_->IsMatch(path))
return; return;
std::string trimmed_path = absolute_path; std::string trimmed_path = path;
bool use_angle_brackets = bool use_angle_brackets = TrimPath(project_, trimmed_path);
TrimPath(project_, g_config->projectRoot, &trimmed_path);
lsCompletionItem item = lsCompletionItem item =
BuildCompletionItem(trimmed_path, use_angle_brackets, false /*is_stl*/); BuildCompletionItem(trimmed_path, use_angle_brackets, false /*is_stl*/);
std::unique_lock<std::mutex> lock(completion_items_mutex, std::defer_lock); std::unique_lock<std::mutex> lock(completion_items_mutex, std::defer_lock);
if (is_scanning) if (is_scanning)
lock.lock(); lock.lock();
InsertCompletionItem(absolute_path, std::move(item)); InsertCompletionItem(path, std::move(item));
} }
void IncludeComplete::InsertIncludesFromDirectory(std::string directory, void IncludeComplete::InsertIncludesFromDirectory(std::string directory,

View File

@ -295,6 +295,12 @@ struct lsTextDocumentDidChangeParams {
MAKE_REFLECT_STRUCT(lsTextDocumentDidChangeParams, textDocument, MAKE_REFLECT_STRUCT(lsTextDocumentDidChangeParams, textDocument,
contentChanges); contentChanges);
struct lsWorkspaceFolder {
lsDocumentUri uri;
std::string name;
};
MAKE_REFLECT_STRUCT(lsWorkspaceFolder, uri, name);
// Show a message to the user. // Show a message to the user.
enum class lsMessageType : int { Error = 1, Warning = 2, Info = 3, Log = 4 }; enum class lsMessageType : int { Error = 1, Warning = 2, Info = 3, Log = 4 };
MAKE_REFLECT_TYPE_PROXY(lsMessageType) MAKE_REFLECT_TYPE_PROXY(lsMessageType)

View File

@ -78,27 +78,24 @@ bool FindFileOrFail(DB *db, Project *project, std::optional<lsRequestId> id,
if (out_file_id) if (out_file_id)
*out_file_id = -1; *out_file_id = -1;
bool indexing; bool has_entry = false;
{ {
std::lock_guard<std::mutex> lock(project->mutex_); std::lock_guard<std::mutex> lock(project->mutex_);
indexing = project->path_to_entry_index.find(absolute_path) != for (auto &[root, folder] : project->root2folder)
project->path_to_entry_index.end(); 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) { if (id) {
Out_Error out; Out_Error out;
out.id = *id; out.id = *id;
if (indexing) { if (has_entry) {
out.error.code = lsErrorCodes::ServerNotInitialized; out.error.code = lsErrorCodes::ServerNotInitialized;
out.error.message = absolute_path + " is being indexed."; out.error.message = absolute_path + " is being indexed";
} else { } else {
out.error.code = lsErrorCodes::InternalError; out.error.code = lsErrorCodes::InternalError;
out.error.message = "Unable to find file " + absolute_path; out.error.message = "Unable to find file " + absolute_path;
} }
LOG_S(INFO) << out.error.message;
pipeline::WriteStdout(kMethodType_Unknown, out); pipeline::WriteStdout(kMethodType_Unknown, out);
} }

View File

@ -49,7 +49,9 @@ struct Handler_cclsInfo : BaseMessageHandler<In_cclsInfo> {
out.result.db.types = db->types.size(); out.result.db.types = db->types.size();
out.result.db.vars = db->vars.size(); out.result.db.vars = db->vars.size();
out.result.pipeline.pendingIndexRequests = pipeline::pending_index_requests; 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); pipeline::WriteStdout(cclsInfo, out);
} }
}; };

View File

@ -164,8 +164,17 @@ struct lsServerCapabilities {
struct ExecuteCommandOptions { struct ExecuteCommandOptions {
std::vector<std::string> commands{std::string(ccls_xref)}; std::vector<std::string> commands{std::string(ccls_xref)};
} executeCommandProvider; } executeCommandProvider;
struct Workspace {
struct WorkspaceFolders {
bool supported = true;
bool changeNotifications = true;
} workspaceFolders;
} workspace;
}; };
MAKE_REFLECT_STRUCT(lsServerCapabilities::ExecuteCommandOptions, commands); 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, MAKE_REFLECT_STRUCT(lsServerCapabilities, textDocumentSync, hoverProvider,
completionProvider, signatureHelpProvider, completionProvider, signatureHelpProvider,
definitionProvider, implementationProvider, definitionProvider, implementationProvider,
@ -175,7 +184,7 @@ MAKE_REFLECT_STRUCT(lsServerCapabilities, textDocumentSync, hoverProvider,
codeLensProvider, documentFormattingProvider, codeLensProvider, documentFormattingProvider,
documentRangeFormattingProvider, documentRangeFormattingProvider,
documentOnTypeFormattingProvider, renameProvider, documentOnTypeFormattingProvider, renameProvider,
documentLinkProvider, executeCommandProvider); documentLinkProvider, executeCommandProvider, workspace);
// Workspace specific client capabilities. // Workspace specific client capabilities.
struct lsWorkspaceClientCapabilites { struct lsWorkspaceClientCapabilites {
@ -333,6 +342,8 @@ struct lsInitializeParams {
// The initial trace setting. If omitted trace is disabled ('off'). // The initial trace setting. If omitted trace is disabled ('off').
lsTrace trace = lsTrace::Off; lsTrace trace = lsTrace::Off;
std::vector<lsWorkspaceFolder> workspaceFolders;
}; };
void Reflect(Reader &reader, lsInitializeParams::lsTrace &value) { void Reflect(Reader &reader, lsInitializeParams::lsTrace &value) {
@ -366,7 +377,8 @@ void Reflect(Writer& writer, lsInitializeParams::lsTrace& value) {
#endif #endif
MAKE_REFLECT_STRUCT(lsInitializeParams, processId, rootPath, rootUri, MAKE_REFLECT_STRUCT(lsInitializeParams, processId, rootPath, rootUri,
initializationOptions, capabilities, trace); initializationOptions, capabilities, trace,
workspaceFolders);
struct lsInitializeError { struct lsInitializeError {
// Indicates whether the client should retry to send the // Indicates whether the client should retry to send the
@ -472,19 +484,28 @@ struct Handler_Initialize : BaseMessageHandler<In_InitializeRequest> {
// Set project root. // Set project root.
EnsureEndsInSlash(project_path); EnsureEndsInSlash(project_path);
g_config->projectRoot = project_path; g_config->fallbackFolder = project_path;
if (g_config->cacheDirectory.size()) { 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 // Create two cache directories for files inside and outside of the
// project. // project.
auto len = g_config->projectRoot.size(); std::string escaped =
std::string escaped = EscapeFileName(g_config->projectRoot.substr(0, len - 1)); 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->cacheDirectory + '@' + escaped); sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped);
} }
idx::Init(); idx::Init();
for (const std::string &folder : g_config->workspaceFolders)
project->Load(project_path); project->Load(folder);
// Start indexer threads. Start this after loading the project, as that // Start indexer threads. Start this after loading the project, as that
// may take a long time. Indexer threads will emit status/progress // 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,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<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

@ -116,18 +116,17 @@ std::string AppendSerializationFormat(const std::string &base) {
} }
std::string GetCachePath(const std::string &source_file) { std::string GetCachePath(const std::string &source_file) {
std::string cache_file; for (auto &root : g_config->workspaceFolders)
auto len = g_config->projectRoot.size(); if (StartsWith(source_file, root)) {
if (StartsWith(source_file, g_config->projectRoot)) { auto len = root.size();
cache_file = EscapeFileName(g_config->projectRoot.substr(0, len - 1)) + '/' + return g_config->cacheDirectory +
EscapeFileName(root.substr(0, len - 1)) + '/' +
EscapeFileName(source_file.substr(len)); EscapeFileName(source_file.substr(len));
} else {
cache_file = '@' +
EscapeFileName(g_config->projectRoot.substr(0, len - 1)) + '/' +
EscapeFileName(source_file);
} }
return g_config->cacheDirectory + '@' +
return g_config->cacheDirectory + cache_file; EscapeFileName(g_config->fallbackFolder.substr(
0, g_config->fallbackFolder.size() - 1)) +
'/' + EscapeFileName(source_file);
} }
std::unique_ptr<IndexFile> RawCacheLoad(const std::string &path) { std::unique_ptr<IndexFile> RawCacheLoad(const std::string &path) {
@ -268,7 +267,7 @@ bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles,
request.mode != IndexMode::NonInteractive); request.mode != IndexMode::NonInteractive);
if (entry.id >= 0) { if (entry.id >= 0) {
std::lock_guard lock2(project->mutex_); 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; return true;
@ -331,8 +330,9 @@ bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles,
} }
if (entry.id >= 0) { if (entry.id >= 0) {
std::lock_guard<std::mutex> lock(project->mutex_); std::lock_guard<std::mutex> lock(project->mutex_);
auto &folder = project->root2folder[entry.root];
for (auto &dep : curr->dependencies) 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

@ -42,7 +42,7 @@ enum class ProjectMode { CompileCommandsJson, DotCcls, ExternalCommand };
struct ProjectConfig { struct ProjectConfig {
std::unordered_set<std::string> quote_dirs; std::unordered_set<std::string> quote_dirs;
std::unordered_set<std::string> angle_dirs; std::unordered_set<std::string> angle_dirs;
std::string project_dir; std::string root;
ProjectMode mode = ProjectMode::CompileCommandsJson; ProjectMode mode = ProjectMode::CompileCommandsJson;
}; };
@ -171,7 +171,7 @@ std::vector<Project::Entry> LoadFromDirectoryListing(ProjectConfig *config) {
std::vector<Project::Entry> result; std::vector<Project::Entry> result;
config->mode = ProjectMode::DotCcls; config->mode = ProjectMode::DotCcls;
SmallString<256> Path; 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()) LOG_IF_S(WARNING, !sys::fs::exists(Path) && g_config->clang.extraArgs.empty())
<< "ccls has no clang arguments. Use either " << "ccls has no clang arguments. Use either "
"compile_commands.json or .ccls, See ccls README for " "compile_commands.json or .ccls, See ccls README for "
@ -179,9 +179,9 @@ std::vector<Project::Entry> LoadFromDirectoryListing(ProjectConfig *config) {
std::unordered_map<std::string, std::vector<const char *>> folder_args; std::unordered_map<std::string, std::vector<const char *>> folder_args;
std::vector<std::string> files; 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*/, true /*add_folder_to_path*/,
[&folder_args, &files](const std::string &path) { [&folder_args, &files](const std::string &path) {
if (SourceFileLanguage(path) != LanguageId::Unknown) { if (SourceFileLanguage(path) != LanguageId::Unknown) {
@ -199,7 +199,7 @@ std::vector<Project::Entry> LoadFromDirectoryListing(ProjectConfig *config) {
} }
}); });
auto GetCompilerArgumentForFile = [&project_dir, auto GetCompilerArgumentForFile = [&root,
&folder_args](std::string cur) { &folder_args](std::string cur) {
while (!(cur = sys::path::parent_path(cur)).empty()) { while (!(cur = sys::path::parent_path(cur)).empty()) {
auto it = folder_args.find(cur); auto it = folder_args.find(cur);
@ -207,17 +207,18 @@ std::vector<Project::Entry> LoadFromDirectoryListing(ProjectConfig *config) {
return it->second; return it->second;
std::string normalized = NormalizePath(cur); std::string normalized = NormalizePath(cur);
// Break if outside of the project root. // Break if outside of the project root.
if (normalized.size() <= project_dir.size() || if (normalized.size() <= root.size() ||
normalized.compare(0, project_dir.size(), project_dir) != 0) normalized.compare(0, root.size(), root) != 0)
break; break;
} }
return folder_args[project_dir]; return folder_args[root];
}; };
ProjectProcessor proc(config); ProjectProcessor proc(config);
for (const std::string &file : files) { for (const std::string &file : files) {
Project::Entry e; Project::Entry e;
e.directory = config->project_dir; e.root = config->root;
e.directory = config->root;
e.filename = file; e.filename = file;
e.args = GetCompilerArgumentForFile(file); e.args = GetCompilerArgumentForFile(file);
if (e.args.empty()) if (e.args.empty())
@ -235,7 +236,7 @@ LoadEntriesFromDirectory(ProjectConfig *project,
const std::string &opt_compdb_dir) { const std::string &opt_compdb_dir) {
// If there is a .ccls file always load using directory listing. // If there is a .ccls file always load using directory listing.
SmallString<256> Path; SmallString<256> Path;
sys::path::append(Path, project->project_dir, ".ccls"); sys::path::append(Path, project->root, ".ccls");
if (sys::fs::exists(Path)) if (sys::fs::exists(Path))
return LoadFromDirectoryListing(project); return LoadFromDirectoryListing(project);
@ -245,8 +246,7 @@ LoadEntriesFromDirectory(ProjectConfig *project,
if (g_config->compilationDatabaseCommand.empty()) { if (g_config->compilationDatabaseCommand.empty()) {
project->mode = ProjectMode::CompileCommandsJson; project->mode = ProjectMode::CompileCommandsJson;
// Try to load compile_commands.json, but fallback to a project listing. // Try to load compile_commands.json, but fallback to a project listing.
comp_db_dir = comp_db_dir = opt_compdb_dir.empty() ? project->root : opt_compdb_dir;
opt_compdb_dir.empty() ? project->project_dir : opt_compdb_dir;
sys::path::append(Path, comp_db_dir, "compile_commands.json"); sys::path::append(Path, comp_db_dir, "compile_commands.json");
} else { } else {
project->mode = ProjectMode::ExternalCommand; project->mode = ProjectMode::ExternalCommand;
@ -264,7 +264,7 @@ LoadEntriesFromDirectory(ProjectConfig *project,
Reflect(json_writer, *g_config); Reflect(json_writer, *g_config);
std::string contents = GetExternalCommandOutput( std::string contents = GetExternalCommandOutput(
std::vector<std::string>{g_config->compilationDatabaseCommand, std::vector<std::string>{g_config->compilationDatabaseCommand,
project->project_dir}, project->root},
input.GetString()); input.GetString());
FILE *fout = fopen(Path.c_str(), "wb"); FILE *fout = fopen(Path.c_str(), "wb");
fwrite(contents.c_str(), contents.size(), 1, fout); fwrite(contents.c_str(), contents.size(), 1, fout);
@ -295,6 +295,8 @@ LoadEntriesFromDirectory(ProjectConfig *project,
ProjectProcessor proc(project); ProjectProcessor proc(project);
for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) { for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) {
Project::Entry entry; Project::Entry entry;
entry.root = project->root;
DoPathMapping(entry.root);
entry.directory = NormalizePath(Cmd.Directory); entry.directory = NormalizePath(Cmd.Directory);
DoPathMapping(entry.directory); DoPathMapping(entry.directory);
entry.filename = entry.filename =
@ -330,77 +332,78 @@ int ComputeGuessScore(std::string_view a, std::string_view b) {
} // namespace } // namespace
void Project::Load(const std::string &root_directory) { void Project::Load(const std::string &root) {
assert(root.back() == '/');
ProjectConfig project; ProjectConfig project;
project.project_dir = root_directory; project.root = root;
entries = LoadEntriesFromDirectory(&project, Folder &folder = root2folder[root];
g_config->compilationDatabaseDirectory);
// Cleanup / postprocess include directories. folder.entries = LoadEntriesFromDirectory(
quote_include_directories.assign(project.quote_dirs.begin(), &project, g_config->compilationDatabaseDirectory);
folder.quote_search_list.assign(project.quote_dirs.begin(),
project.quote_dirs.end()); project.quote_dirs.end());
angle_include_directories.assign(project.angle_dirs.begin(), folder.angle_search_list.assign(project.angle_dirs.begin(),
project.angle_dirs.end()); project.angle_dirs.end());
for (std::string &path : quote_include_directories) { for (std::string &path : folder.angle_search_list) {
EnsureEndsInSlash(path); 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); EnsureEndsInSlash(path);
LOG_S(INFO) << "angle_include_dir: " << path; LOG_S(INFO) << "quote search: " << path;
} }
// Setup project entries. // Setup project entries.
std::lock_guard lock(mutex_); std::lock_guard lock(mutex_);
path_to_entry_index.reserve(entries.size()); folder.path2entry_index.reserve(folder.entries.size());
for (size_t i = 0; i < entries.size(); ++i) { for (size_t i = 0; i < folder.entries.size(); ++i) {
entries[i].id = i; folder.entries[i].id = i;
path_to_entry_index[entries[i].filename] = i; folder.path2entry_index[folder.entries[i].filename] = i;
} }
} }
void Project::SetArgsForFile(const std::vector<const char *> &args, void Project::SetArgsForFile(const std::vector<const char *> &args,
const std::string &path) { const std::string &path) {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto it = path_to_entry_index.find(path); for (auto &[root, folder] : root2folder) {
if (it != path_to_entry_index.end()) { auto it = folder.path2entry_index.find(path);
if (it != folder.path2entry_index.end()) {
// The entry already exists in the project, just set the flags. // The entry already exists in the project, just set the flags.
this->entries[it->second].args = args; folder.entries[it->second].args = args;
} else { return;
// 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);
} }
} }
Project::Entry Project::FindEntry(const std::string &path, Project::Entry Project::FindEntry(const std::string &path,
bool can_be_inferred) { bool can_be_inferred) {
{
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto it = path_to_entry_index.find(path); for (auto &[root, folder] : root2folder) {
if (it != path_to_entry_index.end()) { auto it = folder.path2entry_index.find(path);
Project::Entry &entry = entries[it->second]; if (it != folder.path2entry_index.end()) {
Project::Entry &entry = folder.entries[it->second];
if (can_be_inferred || entry.filename == path) if (can_be_inferred || entry.filename == path)
return entry; return entry;
} }
} }
// We couldn't find the file. Try to infer it. Project::Entry result;
// TODO: Cache inferred file in a separate array (using a lock or similar) const Entry *best_entry = nullptr;
Entry *best_entry = nullptr;
int best_score = INT_MIN; int best_score = INT_MIN;
for (Entry &entry : entries) { for (auto &[root, folder] : root2folder) {
for (const Entry &entry : folder.entries) {
int score = ComputeGuessScore(path, entry.filename); int score = ComputeGuessScore(path, entry.filename);
if (score > best_score) { if (score > best_score) {
best_score = score; best_score = score;
best_entry = &entry; best_entry = &entry;
} }
} }
if (StartsWith(path, root))
result.root = root;
}
if (result.root.empty())
result.root = g_config->fallbackFolder;
Project::Entry result;
result.is_inferred = true; result.is_inferred = true;
result.filename = path; result.filename = path;
if (!best_entry) { if (!best_entry) {
@ -430,19 +433,26 @@ void Project::Index(WorkingFiles *wfiles, lsRequestId id) {
auto &gi = g_config->index; auto &gi = g_config->index;
GroupMatch match(gi.whitelist, gi.blacklist), GroupMatch match(gi.whitelist, gi.blacklist),
match_i(gi.initialWhitelist, gi.initialBlacklist); match_i(gi.initialWhitelist, gi.initialBlacklist);
for (int i = 0; i < entries.size(); ++i) { {
const Project::Entry &entry = entries[i]; std::lock_guard lock(mutex_);
for (auto &[root, folder] : root2folder) {
int i = 0;
for (const Project::Entry &entry : folder.entries) {
std::string reason; std::string reason;
if (match.IsMatch(entry.filename, &reason) && if (match.IsMatch(entry.filename, &reason) &&
match_i.IsMatch(entry.filename, &reason)) { match_i.IsMatch(entry.filename, &reason)) {
bool interactive = wfiles->GetFileByFilename(entry.filename) != nullptr; bool interactive =
wfiles->GetFileByFilename(entry.filename) != nullptr;
pipeline::Index( pipeline::Index(
entry.filename, entry.args, entry.filename, entry.args,
interactive ? IndexMode::Normal : IndexMode::NonInteractive, id); interactive ? IndexMode::Normal : IndexMode::NonInteractive, id);
} else { } else {
LOG_V(1) << "[" << i << "/" << entries.size() << "]: " << reason LOG_V(1) << "[" << i << "/" << folder.entries.size() << "]: " << reason
<< "; skip " << entry.filename; << "; skip " << entry.filename;
} }
i++;
}
}
} }
pipeline::loaded_ts = pipeline::tick; pipeline::loaded_ts = pipeline::tick;

View File

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