2020-01-07 18:28:05 +08:00
|
|
|
//===--- PathMapping.cpp - apply path mappings to LSP messages -===//
|
|
|
|
//
|
|
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "PathMapping.h"
|
|
|
|
#include "Transport.h"
|
|
|
|
#include "URI.h"
|
|
|
|
#include "llvm/ADT/None.h"
|
|
|
|
#include "llvm/ADT/STLExtras.h"
|
|
|
|
#include "llvm/Support/Errno.h"
|
|
|
|
#include "llvm/Support/Error.h"
|
|
|
|
#include "llvm/Support/Path.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <tuple>
|
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace clangd {
|
|
|
|
llvm::Optional<std::string> doPathMapping(llvm::StringRef S,
|
|
|
|
PathMapping::Direction Dir,
|
|
|
|
const PathMappings &Mappings) {
|
2020-04-05 14:28:11 +08:00
|
|
|
// Return early to optimize for the common case, wherein S is not a file URI
|
2020-01-07 18:28:05 +08:00
|
|
|
if (!S.startswith("file://"))
|
|
|
|
return llvm::None;
|
|
|
|
auto Uri = URI::parse(S);
|
|
|
|
if (!Uri) {
|
|
|
|
llvm::consumeError(Uri.takeError());
|
|
|
|
return llvm::None;
|
|
|
|
}
|
|
|
|
for (const auto &Mapping : Mappings) {
|
|
|
|
const std::string &From = Dir == PathMapping::Direction::ClientToServer
|
|
|
|
? Mapping.ClientPath
|
|
|
|
: Mapping.ServerPath;
|
|
|
|
const std::string &To = Dir == PathMapping::Direction::ClientToServer
|
|
|
|
? Mapping.ServerPath
|
|
|
|
: Mapping.ClientPath;
|
|
|
|
llvm::StringRef Body = Uri->body();
|
|
|
|
if (Body.consume_front(From) && (Body.empty() || Body.front() == '/')) {
|
|
|
|
std::string MappedBody = (To + Body).str();
|
|
|
|
return URI(Uri->scheme(), Uri->authority(), MappedBody.c_str())
|
|
|
|
.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return llvm::None;
|
|
|
|
}
|
|
|
|
|
|
|
|
void applyPathMappings(llvm::json::Value &V, PathMapping::Direction Dir,
|
|
|
|
const PathMappings &Mappings) {
|
|
|
|
using Kind = llvm::json::Value::Kind;
|
|
|
|
Kind K = V.kind();
|
|
|
|
if (K == Kind::Object) {
|
|
|
|
llvm::json::Object *Obj = V.getAsObject();
|
|
|
|
llvm::json::Object MappedObj;
|
|
|
|
// 1. Map all the Keys
|
|
|
|
for (auto &KV : *Obj) {
|
|
|
|
if (llvm::Optional<std::string> MappedKey =
|
|
|
|
doPathMapping(KV.first.str(), Dir, Mappings)) {
|
|
|
|
MappedObj.try_emplace(std::move(*MappedKey), std::move(KV.second));
|
|
|
|
} else {
|
|
|
|
MappedObj.try_emplace(std::move(KV.first), std::move(KV.second));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*Obj = std::move(MappedObj);
|
|
|
|
// 2. Map all the values
|
|
|
|
for (auto &KV : *Obj)
|
|
|
|
applyPathMappings(KV.second, Dir, Mappings);
|
|
|
|
} else if (K == Kind::Array) {
|
|
|
|
for (llvm::json::Value &Val : *V.getAsArray())
|
|
|
|
applyPathMappings(Val, Dir, Mappings);
|
|
|
|
} else if (K == Kind::String) {
|
|
|
|
if (llvm::Optional<std::string> Mapped =
|
|
|
|
doPathMapping(*V.getAsString(), Dir, Mappings))
|
|
|
|
V = std::move(*Mapped);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
class PathMappingMessageHandler : public Transport::MessageHandler {
|
|
|
|
public:
|
|
|
|
PathMappingMessageHandler(MessageHandler &Handler,
|
|
|
|
const PathMappings &Mappings)
|
|
|
|
: WrappedHandler(Handler), Mappings(Mappings) {}
|
|
|
|
|
|
|
|
bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override {
|
|
|
|
applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);
|
|
|
|
return WrappedHandler.onNotify(Method, std::move(Params));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool onCall(llvm::StringRef Method, llvm::json::Value Params,
|
|
|
|
llvm::json::Value ID) override {
|
|
|
|
applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);
|
|
|
|
return WrappedHandler.onCall(Method, std::move(Params), std::move(ID));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool onReply(llvm::json::Value ID,
|
|
|
|
llvm::Expected<llvm::json::Value> Result) override {
|
|
|
|
if (Result)
|
|
|
|
applyPathMappings(*Result, PathMapping::Direction::ClientToServer,
|
|
|
|
Mappings);
|
|
|
|
return WrappedHandler.onReply(std::move(ID), std::move(Result));
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Transport::MessageHandler &WrappedHandler;
|
|
|
|
const PathMappings &Mappings;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Apply path mappings to all LSP messages by intercepting all params/results
|
|
|
|
// and then delegating to the normal transport
|
|
|
|
class PathMappingTransport : public Transport {
|
|
|
|
public:
|
|
|
|
PathMappingTransport(std::unique_ptr<Transport> Transp, PathMappings Mappings)
|
|
|
|
: WrappedTransport(std::move(Transp)), Mappings(std::move(Mappings)) {}
|
|
|
|
|
|
|
|
void notify(llvm::StringRef Method, llvm::json::Value Params) override {
|
|
|
|
applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);
|
|
|
|
WrappedTransport->notify(Method, std::move(Params));
|
|
|
|
}
|
|
|
|
|
|
|
|
void call(llvm::StringRef Method, llvm::json::Value Params,
|
|
|
|
llvm::json::Value ID) override {
|
|
|
|
applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);
|
|
|
|
WrappedTransport->call(Method, std::move(Params), std::move(ID));
|
|
|
|
}
|
|
|
|
|
|
|
|
void reply(llvm::json::Value ID,
|
|
|
|
llvm::Expected<llvm::json::Value> Result) override {
|
|
|
|
if (Result)
|
|
|
|
applyPathMappings(*Result, PathMapping::Direction::ServerToClient,
|
|
|
|
Mappings);
|
|
|
|
WrappedTransport->reply(std::move(ID), std::move(Result));
|
|
|
|
}
|
|
|
|
|
|
|
|
llvm::Error loop(MessageHandler &Handler) override {
|
|
|
|
PathMappingMessageHandler WrappedHandler(Handler, Mappings);
|
|
|
|
return WrappedTransport->loop(WrappedHandler);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::unique_ptr<Transport> WrappedTransport;
|
|
|
|
PathMappings Mappings;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Converts a unix/windows path to the path portion of a file URI
|
|
|
|
// e.g. "C:\foo" -> "/C:/foo"
|
|
|
|
llvm::Expected<std::string> parsePath(llvm::StringRef Path) {
|
|
|
|
namespace path = llvm::sys::path;
|
|
|
|
if (path::is_absolute(Path, path::Style::posix)) {
|
2020-01-29 03:23:46 +08:00
|
|
|
return std::string(Path);
|
2020-01-07 18:28:05 +08:00
|
|
|
} else if (path::is_absolute(Path, path::Style::windows)) {
|
|
|
|
std::string Converted = path::convert_to_slash(Path, path::Style::windows);
|
|
|
|
if (Converted.front() != '/')
|
|
|
|
Converted = "/" + Converted;
|
|
|
|
return Converted;
|
|
|
|
}
|
|
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
|
|
"Path not absolute: " + Path);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const PathMapping &M) {
|
|
|
|
return OS << M.ClientPath << "=" << M.ServerPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
llvm::Expected<PathMappings>
|
|
|
|
parsePathMappings(llvm::StringRef RawPathMappings) {
|
|
|
|
llvm::StringRef ClientPath, ServerPath, PathPair, Rest = RawPathMappings;
|
|
|
|
PathMappings ParsedMappings;
|
|
|
|
while (!Rest.empty()) {
|
|
|
|
std::tie(PathPair, Rest) = Rest.split(",");
|
|
|
|
std::tie(ClientPath, ServerPath) = PathPair.split("=");
|
|
|
|
if (ClientPath.empty() || ServerPath.empty())
|
|
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
|
|
"Not a valid path mapping pair: " +
|
|
|
|
PathPair);
|
|
|
|
llvm::Expected<std::string> ParsedClientPath = parsePath(ClientPath);
|
|
|
|
if (!ParsedClientPath)
|
|
|
|
return ParsedClientPath.takeError();
|
|
|
|
llvm::Expected<std::string> ParsedServerPath = parsePath(ServerPath);
|
|
|
|
if (!ParsedServerPath)
|
|
|
|
return ParsedServerPath.takeError();
|
|
|
|
ParsedMappings.push_back(
|
|
|
|
{std::move(*ParsedClientPath), std::move(*ParsedServerPath)});
|
|
|
|
}
|
|
|
|
return ParsedMappings;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<Transport>
|
|
|
|
createPathMappingTransport(std::unique_ptr<Transport> Transp,
|
|
|
|
PathMappings Mappings) {
|
|
|
|
return std::make_unique<PathMappingTransport>(std::move(Transp), Mappings);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace clangd
|
|
|
|
} // namespace clang
|