197 lines · cpp
1//===--- PathMapping.cpp - apply path mappings to LSP messages -===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8#include "PathMapping.h"9#include "Transport.h"10#include "URI.h"11#include "support/Logger.h"12#include "llvm/Support/Error.h"13#include "llvm/Support/Path.h"14#include <algorithm>15#include <optional>16#include <tuple>17 18namespace clang {19namespace clangd {20std::optional<std::string> doPathMapping(llvm::StringRef S,21 PathMapping::Direction Dir,22 const PathMappings &Mappings) {23 // Return early to optimize for the common case, wherein S is not a file URI24 if (!S.starts_with("file://"))25 return std::nullopt;26 auto Uri = URI::parse(S);27 if (!Uri) {28 llvm::consumeError(Uri.takeError());29 return std::nullopt;30 }31 for (const auto &Mapping : Mappings) {32 const std::string &From = Dir == PathMapping::Direction::ClientToServer33 ? Mapping.ClientPath34 : Mapping.ServerPath;35 const std::string &To = Dir == PathMapping::Direction::ClientToServer36 ? Mapping.ServerPath37 : Mapping.ClientPath;38 llvm::StringRef Body = Uri->body();39 if (Body.consume_front(From) && (Body.empty() || Body.front() == '/')) {40 std::string MappedBody = (To + Body).str();41 return URI(Uri->scheme(), Uri->authority(), MappedBody)42 .toString();43 }44 }45 return std::nullopt;46}47 48void applyPathMappings(llvm::json::Value &V, PathMapping::Direction Dir,49 const PathMappings &Mappings) {50 using Kind = llvm::json::Value::Kind;51 Kind K = V.kind();52 if (K == Kind::Object) {53 llvm::json::Object *Obj = V.getAsObject();54 llvm::json::Object MappedObj;55 // 1. Map all the Keys56 for (auto &KV : *Obj) {57 if (std::optional<std::string> MappedKey =58 doPathMapping(KV.first.str(), Dir, Mappings)) {59 MappedObj.try_emplace(std::move(*MappedKey), std::move(KV.second));60 } else {61 MappedObj.try_emplace(std::move(KV.first), std::move(KV.second));62 }63 }64 *Obj = std::move(MappedObj);65 // 2. Map all the values66 for (auto &KV : *Obj)67 applyPathMappings(KV.second, Dir, Mappings);68 } else if (K == Kind::Array) {69 for (llvm::json::Value &Val : *V.getAsArray())70 applyPathMappings(Val, Dir, Mappings);71 } else if (K == Kind::String) {72 if (std::optional<std::string> Mapped =73 doPathMapping(*V.getAsString(), Dir, Mappings))74 V = std::move(*Mapped);75 }76}77 78namespace {79 80class PathMappingMessageHandler : public Transport::MessageHandler {81public:82 PathMappingMessageHandler(MessageHandler &Handler,83 const PathMappings &Mappings)84 : WrappedHandler(Handler), Mappings(Mappings) {}85 86 bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override {87 applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);88 return WrappedHandler.onNotify(Method, std::move(Params));89 }90 91 bool onCall(llvm::StringRef Method, llvm::json::Value Params,92 llvm::json::Value ID) override {93 applyPathMappings(Params, PathMapping::Direction::ClientToServer, Mappings);94 return WrappedHandler.onCall(Method, std::move(Params), std::move(ID));95 }96 97 bool onReply(llvm::json::Value ID,98 llvm::Expected<llvm::json::Value> Result) override {99 if (Result)100 applyPathMappings(*Result, PathMapping::Direction::ClientToServer,101 Mappings);102 return WrappedHandler.onReply(std::move(ID), std::move(Result));103 }104 105private:106 Transport::MessageHandler &WrappedHandler;107 const PathMappings &Mappings;108};109 110// Apply path mappings to all LSP messages by intercepting all params/results111// and then delegating to the normal transport112class PathMappingTransport : public Transport {113public:114 PathMappingTransport(std::unique_ptr<Transport> Transp, PathMappings Mappings)115 : WrappedTransport(std::move(Transp)), Mappings(std::move(Mappings)) {}116 117 void notify(llvm::StringRef Method, llvm::json::Value Params) override {118 applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);119 WrappedTransport->notify(Method, std::move(Params));120 }121 122 void call(llvm::StringRef Method, llvm::json::Value Params,123 llvm::json::Value ID) override {124 applyPathMappings(Params, PathMapping::Direction::ServerToClient, Mappings);125 WrappedTransport->call(Method, std::move(Params), std::move(ID));126 }127 128 void reply(llvm::json::Value ID,129 llvm::Expected<llvm::json::Value> Result) override {130 if (Result)131 applyPathMappings(*Result, PathMapping::Direction::ServerToClient,132 Mappings);133 WrappedTransport->reply(std::move(ID), std::move(Result));134 }135 136 llvm::Error loop(MessageHandler &Handler) override {137 PathMappingMessageHandler WrappedHandler(Handler, Mappings);138 return WrappedTransport->loop(WrappedHandler);139 }140 141private:142 std::unique_ptr<Transport> WrappedTransport;143 PathMappings Mappings;144};145 146// Converts a unix/windows path to the path portion of a file URI147// e.g. "C:\foo" -> "/C:/foo"148llvm::Expected<std::string> parsePath(llvm::StringRef Path) {149 namespace path = llvm::sys::path;150 if (path::is_absolute(Path, path::Style::posix)) {151 return std::string(Path);152 }153 if (path::is_absolute(Path, path::Style::windows)) {154 std::string Converted = path::convert_to_slash(Path, path::Style::windows);155 if (Converted.front() != '/')156 Converted = "/" + Converted;157 return Converted;158 }159 return error("Path not absolute: {0}", Path);160}161 162} // namespace163 164llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const PathMapping &M) {165 return OS << M.ClientPath << "=" << M.ServerPath;166}167 168llvm::Expected<PathMappings>169parsePathMappings(llvm::StringRef RawPathMappings) {170 llvm::StringRef ClientPath, ServerPath, PathPair, Rest = RawPathMappings;171 PathMappings ParsedMappings;172 while (!Rest.empty()) {173 std::tie(PathPair, Rest) = Rest.split(",");174 std::tie(ClientPath, ServerPath) = PathPair.split("=");175 if (ClientPath.empty() || ServerPath.empty())176 return error("Not a valid path mapping pair: {0}", PathPair);177 llvm::Expected<std::string> ParsedClientPath = parsePath(ClientPath);178 if (!ParsedClientPath)179 return ParsedClientPath.takeError();180 llvm::Expected<std::string> ParsedServerPath = parsePath(ServerPath);181 if (!ParsedServerPath)182 return ParsedServerPath.takeError();183 ParsedMappings.push_back(184 {std::move(*ParsedClientPath), std::move(*ParsedServerPath)});185 }186 return ParsedMappings;187}188 189std::unique_ptr<Transport>190createPathMappingTransport(std::unique_ptr<Transport> Transp,191 PathMappings Mappings) {192 return std::make_unique<PathMappingTransport>(std::move(Transp), Mappings);193}194 195} // namespace clangd196} // namespace clang197