1 //===--- PathMapping.cpp - apply path mappings to LSP messages -===//
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-exception
7 //===----------------------------------------------------------------------===//
8 #include "PathMapping.h"
11 #include "support/Logger.h"
12 #include "llvm/Support/Error.h"
13 #include "llvm/Support/Path.h"
20 std::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 URI
24 if (!S
.starts_with("file://"))
26 auto Uri
= URI::parse(S
);
28 llvm::consumeError(Uri
.takeError());
31 for (const auto &Mapping
: Mappings
) {
32 const std::string
&From
= Dir
== PathMapping::Direction::ClientToServer
35 const std::string
&To
= Dir
== PathMapping::Direction::ClientToServer
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
)
48 void applyPathMappings(llvm::json::Value
&V
, PathMapping::Direction Dir
,
49 const PathMappings
&Mappings
) {
50 using Kind
= llvm::json::Value::Kind
;
52 if (K
== Kind::Object
) {
53 llvm::json::Object
*Obj
= V
.getAsObject();
54 llvm::json::Object MappedObj
;
55 // 1. Map all the Keys
56 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
));
61 MappedObj
.try_emplace(std::move(KV
.first
), std::move(KV
.second
));
64 *Obj
= std::move(MappedObj
);
65 // 2. Map all the values
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
);
80 class PathMappingMessageHandler
: public Transport::MessageHandler
{
82 PathMappingMessageHandler(MessageHandler
&Handler
,
83 const PathMappings
&Mappings
)
84 : WrappedHandler(Handler
), Mappings(Mappings
) {}
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
));
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
));
97 bool onReply(llvm::json::Value ID
,
98 llvm::Expected
<llvm::json::Value
> Result
) override
{
100 applyPathMappings(*Result
, PathMapping::Direction::ClientToServer
,
102 return WrappedHandler
.onReply(std::move(ID
), std::move(Result
));
106 Transport::MessageHandler
&WrappedHandler
;
107 const PathMappings
&Mappings
;
110 // Apply path mappings to all LSP messages by intercepting all params/results
111 // and then delegating to the normal transport
112 class PathMappingTransport
: public Transport
{
114 PathMappingTransport(std::unique_ptr
<Transport
> Transp
, PathMappings Mappings
)
115 : WrappedTransport(std::move(Transp
)), Mappings(std::move(Mappings
)) {}
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
));
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
));
128 void reply(llvm::json::Value ID
,
129 llvm::Expected
<llvm::json::Value
> Result
) override
{
131 applyPathMappings(*Result
, PathMapping::Direction::ServerToClient
,
133 WrappedTransport
->reply(std::move(ID
), std::move(Result
));
136 llvm::Error
loop(MessageHandler
&Handler
) override
{
137 PathMappingMessageHandler
WrappedHandler(Handler
, Mappings
);
138 return WrappedTransport
->loop(WrappedHandler
);
142 std::unique_ptr
<Transport
> WrappedTransport
;
143 PathMappings Mappings
;
146 // Converts a unix/windows path to the path portion of a file URI
147 // e.g. "C:\foo" -> "/C:/foo"
148 llvm::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
);
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
;
159 return error("Path not absolute: {0}", Path
);
164 llvm::raw_ostream
&operator<<(llvm::raw_ostream
&OS
, const PathMapping
&M
) {
165 return OS
<< M
.ClientPath
<< "=" << M
.ServerPath
;
168 llvm::Expected
<PathMappings
>
169 parsePathMappings(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
)});
186 return ParsedMappings
;
189 std::unique_ptr
<Transport
>
190 createPathMappingTransport(std::unique_ptr
<Transport
> Transp
,
191 PathMappings Mappings
) {
192 return std::make_unique
<PathMappingTransport
>(std::move(Transp
), Mappings
);
195 } // namespace clangd