198 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
//===--- 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 "support/Logger.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) {
 | 
						|
  // Return early to optimize for the common case, wherein S is not a file URI
 | 
						|
  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)) {
 | 
						|
    return std::string(Path);
 | 
						|
  } 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 error("Path not absolute: {0}", 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 error("Not a valid path mapping pair: {0}", 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
 |