If the workspace folder is a symlink, convert paths relative to it (#314)

If the workspace folder is a symlink and the client doesn't follow it.
Treat /tmp/symlink/ as canonical and convert every /tmp/real/ path to
/tmp/symlink/.
This commit is contained in:
Fangrui Song 2019-03-10 00:27:50 -08:00
parent 925ef8213d
commit 1e3c683ae2
6 changed files with 61 additions and 21 deletions

View File

@ -30,10 +30,15 @@ std::string PathFromFileEntry(const FileEntry &file) {
if (Name.empty()) if (Name.empty())
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 symlinks outside of workspace folders, e.g. /usr/include/c++/7.3.0
if (!llvm::any_of(g_config->workspaceFolders, [&](const std::string &root) { bool in_folder = false;
return StringRef(ret).startswith(root); for (auto &[root, real] : g_config->workspaceFolders)
})) { if (real.size() && StringRef(ret).startswith(real)) {
ret = root + ret.substr(real.size());
in_folder = true;
break;
}
if (!in_folder) {
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

@ -32,7 +32,7 @@ initialization options specified by the client. For example, in shell syntax:
struct Config { struct Config {
// **Not available for configuration** // **Not available for configuration**
std::string fallbackFolder; std::string fallbackFolder;
std::vector<std::string> workspaceFolders; std::vector<std::pair<std::string, 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

@ -17,6 +17,8 @@ limitations under the License.
#include "log.hh" #include "log.hh"
#include <llvm/ADT/StringRef.h>
#include <rapidjson/document.h> #include <rapidjson/document.h>
#include <algorithm> #include <algorithm>
@ -131,6 +133,10 @@ std::string DocumentUri::GetPath() const {
ret[0] = toupper(ret[0]); ret[0] = toupper(ret[0]);
} }
#endif #endif
if (g_config)
for (auto &[root, real] : g_config->workspaceFolders)
if (real.size() && llvm::StringRef(ret).startswith(real))
return root + ret.substr(real.size());
return ret; return ret;
} }

View File

@ -337,19 +337,35 @@ void Initialize(MessageHandler *m, InitializeParam &param, ReplyOnce &reply) {
// Set project root. // Set project root.
EnsureEndsInSlash(project_path); EnsureEndsInSlash(project_path);
g_config->fallbackFolder = project_path; g_config->fallbackFolder = project_path;
auto &workspaceFolders = g_config->workspaceFolders;
auto Real = [](const std::string &path) {
SmallString<256> real;
sys::fs::real_path(path, real);
return sys::path::convert_to_slash(real) + '/';
};
for (const WorkspaceFolder &wf : param.workspaceFolders) { for (const WorkspaceFolder &wf : param.workspaceFolders) {
std::string path = wf.uri.GetPath(); std::string path = wf.uri.GetPath();
EnsureEndsInSlash(path); EnsureEndsInSlash(path);
g_config->workspaceFolders.push_back(path); std::string real = Real(path);
LOG_S(INFO) << "add workspace folder " << wf.name << ": " << path; workspaceFolders.emplace_back(path, path == real ? "" : real);
} }
if (param.workspaceFolders.empty()) if (workspaceFolders.empty()) {
g_config->workspaceFolders.push_back(project_path); std::string real = Real(project_path);
workspaceFolders.emplace_back(project_path,
project_path == real ? "" : real);
}
llvm::sort(workspaceFolders,
[](auto &l, auto &r) { return l.first.size() > r.first.size(); });
for (auto &[folder, real] : workspaceFolders)
if (real.empty())
LOG_S(INFO) << "workspace folder: " << folder;
else
LOG_S(INFO) << "workspace folder: " << folder << " -> " << real;
if (g_config->cache.directory.empty()) if (g_config->cache.directory.empty())
g_config->cache.retainInMemory = 1; g_config->cache.retainInMemory = 1;
else if (!g_config->cache.hierarchicalPath) else if (!g_config->cache.hierarchicalPath)
for (const std::string &folder : g_config->workspaceFolders) { for (auto &[folder, _] : workspaceFolders) {
// Create two cache directories for files inside and outside of the // Create two cache directories for files inside and outside of the
// project. // project.
std::string escaped = EscapeFileName(folder.substr(0, folder.size() - 1)); std::string escaped = EscapeFileName(folder.substr(0, folder.size() - 1));
@ -358,7 +374,7 @@ void Initialize(MessageHandler *m, InitializeParam &param, ReplyOnce &reply) {
} }
idx::Init(); idx::Init();
for (const std::string &folder : g_config->workspaceFolders) for (auto &[folder, _] : workspaceFolders)
m->project->Load(folder); m->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

View File

@ -23,6 +23,7 @@ limitations under the License.
#include <llvm/ADT/STLExtras.h> #include <llvm/ADT/STLExtras.h>
#include <llvm/ADT/StringRef.h> #include <llvm/ADT/StringRef.h>
#include <llvm/Support/FileSystem.h>
#include <llvm/Support/Path.h> #include <llvm/Support/Path.h>
#include <algorithm> #include <algorithm>
@ -35,7 +36,7 @@ namespace ccls {
REFLECT_STRUCT(SymbolInformation, name, kind, location, containerName); REFLECT_STRUCT(SymbolInformation, name, kind, location, containerName);
void MessageHandler::workspace_didChangeConfiguration(EmptyParam &) { void MessageHandler::workspace_didChangeConfiguration(EmptyParam &) {
for (const std::string &folder : g_config->workspaceFolders) for (auto &[folder, _] : g_config->workspaceFolders)
project->Load(folder); project->Load(folder);
project->Index(wfiles, RequestId()); project->Index(wfiles, RequestId());
@ -82,7 +83,8 @@ void MessageHandler::workspace_didChangeWorkspaceFolders(
std::string root = wf.uri.GetPath(); std::string root = wf.uri.GetPath();
EnsureEndsInSlash(root); EnsureEndsInSlash(root);
LOG_S(INFO) << "delete workspace folder " << wf.name << ": " << root; LOG_S(INFO) << "delete workspace folder " << wf.name << ": " << root;
auto it = llvm::find(g_config->workspaceFolders, root); auto it = llvm::find_if(g_config->workspaceFolders,
[&](auto &folder) { return folder.first == root; });
if (it != g_config->workspaceFolders.end()) { if (it != g_config->workspaceFolders.end()) {
g_config->workspaceFolders.erase(it); g_config->workspaceFolders.erase(it);
{ {
@ -92,13 +94,24 @@ void MessageHandler::workspace_didChangeWorkspaceFolders(
project->root2folder.erase(root); project->root2folder.erase(root);
} }
} }
auto &workspaceFolders = g_config->workspaceFolders;
for (const WorkspaceFolder &wf : param.event.added) { for (const WorkspaceFolder &wf : param.event.added) {
std::string root = wf.uri.GetPath(); std::string folder = wf.uri.GetPath();
EnsureEndsInSlash(root); EnsureEndsInSlash(folder);
LOG_S(INFO) << "add workspace folder " << wf.name << ": " << root; SmallString<256> buf;
g_config->workspaceFolders.push_back(root); sys::fs::real_path(folder, buf);
project->Load(root); auto real = llvm::sys::path::convert_to_slash(buf) + '/';
} if (folder == real)
real.clear();
LOG_S(INFO) << "add workspace folder " << wf.name << ": "
<< (real.empty() ? folder : folder + " -> " + real);
workspaceFolders.emplace_back();
auto it = workspaceFolders.end() - 1;
for (; it != workspaceFolders.begin() && folder < it[-1].first; --it)
*it = it[-1];
*it = {folder, real};
project->Load(folder);
}
project->Index(wfiles, RequestId()); project->Index(wfiles, RequestId());

View File

@ -145,7 +145,7 @@ std::string AppendSerializationFormat(const std::string &base) {
} }
} }
std::string GetCachePath(const std::string &src) { std::string GetCachePath(std::string src) {
if (g_config->cache.hierarchicalPath) { if (g_config->cache.hierarchicalPath) {
std::string ret = std::string ret =
g_config->cache.directory + (src[0] == '/' ? src.substr(1) : src); g_config->cache.directory + (src[0] == '/' ? src.substr(1) : src);
@ -154,7 +154,7 @@ std::string GetCachePath(const std::string &src) {
#endif #endif
return ret; return ret;
} }
for (auto &root : g_config->workspaceFolders) for (auto &[root, _] : g_config->workspaceFolders)
if (StringRef(src).startswith(root)) { if (StringRef(src).startswith(root)) {
auto len = root.size(); auto len = root.size();
return g_config->cache.directory + return g_config->cache.directory +