mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-21 23:25:07 +00:00
Support workspace folders
This commit is contained in:
parent
e352604ee4
commit
49dd0ed558
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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());
|
||||||
|
@ -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.
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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) {
|
||||||
// Create two cache directories for files inside and outside of the
|
std::string path = wf.uri.GetPath();
|
||||||
// project.
|
EnsureEndsInSlash(path);
|
||||||
auto len = g_config->projectRoot.size();
|
g_config->workspaceFolders.push_back(path);
|
||||||
std::string escaped = EscapeFileName(g_config->projectRoot.substr(0, len - 1));
|
LOG_S(INFO) << "add workspace folder " << wf.name << ": " << path;
|
||||||
sys::fs::create_directories(g_config->cacheDirectory + escaped);
|
|
||||||
sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped);
|
|
||||||
}
|
}
|
||||||
|
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();
|
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
|
||||||
|
108
src/messages/workspace_did.cc
Normal file
108
src/messages/workspace_did.cc
Normal 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
|
@ -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
|
|
@ -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(source_file.substr(len));
|
EscapeFileName(root.substr(0, len - 1)) + '/' +
|
||||||
} else {
|
EscapeFileName(source_file.substr(len));
|
||||||
cache_file = '@' +
|
}
|
||||||
EscapeFileName(g_config->projectRoot.substr(0, len - 1)) + '/' +
|
return g_config->cacheDirectory + '@' +
|
||||||
EscapeFileName(source_file);
|
EscapeFileName(g_config->fallbackFolder.substr(
|
||||||
}
|
0, g_config->fallbackFolder.size() - 1)) +
|
||||||
|
'/' + EscapeFileName(source_file);
|
||||||
return g_config->cacheDirectory + cache_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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
150
src/project.cc
150
src/project.cc
@ -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);
|
||||||
project.quote_dirs.end());
|
folder.quote_search_list.assign(project.quote_dirs.begin(),
|
||||||
angle_include_directories.assign(project.angle_dirs.begin(),
|
project.quote_dirs.end());
|
||||||
project.angle_dirs.end());
|
folder.angle_search_list.assign(project.angle_dirs.begin(),
|
||||||
for (std::string &path : quote_include_directories) {
|
project.angle_dirs.end());
|
||||||
|
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);
|
||||||
// The entry already exists in the project, just set the flags.
|
if (it != folder.path2entry_index.end()) {
|
||||||
this->entries[it->second].args = args;
|
// The entry already exists in the project, just set the flags.
|
||||||
} else {
|
folder.entries[it->second].args = args;
|
||||||
// Entry wasn't found, so we create a new one.
|
return;
|
||||||
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_);
|
for (auto &[root, folder] : root2folder) {
|
||||||
auto it = path_to_entry_index.find(path);
|
auto it = folder.path2entry_index.find(path);
|
||||||
if (it != path_to_entry_index.end()) {
|
if (it != folder.path2entry_index.end()) {
|
||||||
Project::Entry &entry = entries[it->second];
|
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.
|
|
||||||
// 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;
|
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.is_inferred = true;
|
||||||
result.filename = path;
|
result.filename = path;
|
||||||
if (!best_entry) {
|
if (!best_entry) {
|
||||||
@ -430,18 +433,25 @@ 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_);
|
||||||
std::string reason;
|
for (auto &[root, folder] : root2folder) {
|
||||||
if (match.IsMatch(entry.filename, &reason) &&
|
int i = 0;
|
||||||
match_i.IsMatch(entry.filename, &reason)) {
|
for (const Project::Entry &entry : folder.entries) {
|
||||||
bool interactive = wfiles->GetFileByFilename(entry.filename) != nullptr;
|
std::string reason;
|
||||||
pipeline::Index(
|
if (match.IsMatch(entry.filename, &reason) &&
|
||||||
entry.filename, entry.args,
|
match_i.IsMatch(entry.filename, &reason)) {
|
||||||
interactive ? IndexMode::Normal : IndexMode::NonInteractive, id);
|
bool interactive =
|
||||||
} else {
|
wfiles->GetFileByFilename(entry.filename) != nullptr;
|
||||||
LOG_V(1) << "[" << i << "/" << entries.size() << "]: " << reason
|
pipeline::Index(
|
||||||
<< "; skip " << entry.filename;
|
entry.filename, entry.args,
|
||||||
|
interactive ? IndexMode::Normal : IndexMode::NonInteractive, id);
|
||||||
|
} else {
|
||||||
|
LOG_V(1) << "[" << i << "/" << folder.entries.size() << "]: " << reason
|
||||||
|
<< "; skip " << entry.filename;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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::unordered_map<std::string, int> path2entry_index;
|
||||||
|
};
|
||||||
|
|
||||||
std::vector<Entry> entries;
|
|
||||||
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|.
|
||||||
//
|
//
|
||||||
|
Loading…
Reference in New Issue
Block a user