Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -286,6 +286,13 @@ auto U = URI::parse(Header); if (!U) return U.takeError(); + + auto IncludePath = URI::includePath(*U); + if (!IncludePath) + return IncludePath.takeError(); + if (!IncludePath->empty()) + return HeaderFile{std::move(*IncludePath), /*Verbatim=*/true}; + auto Resolved = URI::resolve(*U, HintPath); if (!Resolved) return Resolved.takeError(); Index: clangd/URI.h =================================================================== --- clangd/URI.h +++ clangd/URI.h @@ -60,6 +60,14 @@ static llvm::Expected resolve(const URI &U, llvm::StringRef HintPath = ""); + /// Tries to get the include path of the file corresponding to the URI. + /// This allows clients to provide their customized include paths. + /// + /// If the returned string is non-empty, clangd will use it directly when + /// doing include insertion; otherwise we will fall back to the clang to + /// calculate the include path from the resolved absolute path. + static llvm::Expected includePath(const URI &U); + friend bool operator==(const URI &LHS, const URI &RHS) { return std::tie(LHS.Scheme, LHS.Authority, LHS.Body) == std::tie(RHS.Scheme, RHS.Authority, RHS.Body); @@ -94,6 +102,13 @@ virtual llvm::Expected uriFromAbsolutePath(llvm::StringRef AbsolutePath) const = 0; + + /// Returns the include path of the file corresponding to the URI, which can + /// be #include directly. See URI::resolveIncludePath for details. + virtual llvm::Expected + getIncludePath(const URI& U) const { + return ""; // no customized include path for this scheme. + } }; /// By default, a "file" scheme is supported where URI paths are always absolute Index: clangd/URI.cpp =================================================================== --- clangd/URI.cpp +++ clangd/URI.cpp @@ -196,5 +196,12 @@ return S->get()->getAbsolutePath(Uri.Authority, Uri.Body, HintPath); } +llvm::Expected URI::includePath(const URI &Uri) { + auto S = findSchemeByName(Uri.Scheme); + if (!S) + return S.takeError(); + return S->get()->getIncludePath(Uri); +} + } // namespace clangd } // namespace clang Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -961,6 +961,10 @@ /*Preferred=*/"", "")); EXPECT_TRUE(Inserted(OriginalHeader, PreferredHeader, "\"Y.h\"")); EXPECT_TRUE(Inserted("", PreferredHeader, "\"Y.h\"")); + auto TestURIHeader = URI::create("/abc/test-root/x/y/z.h", "unittest"); + EXPECT_TRUE(static_cast(TestURIHeader)); + EXPECT_TRUE(Inserted(TestURIHeader->toString(), "", "\"x/y/z.h\"")); + // Check that includes are sorted. const auto Expected = R"cpp( Index: unittests/clangd/URITests.cpp =================================================================== --- unittests/clangd/URITests.cpp +++ unittests/clangd/URITests.cpp @@ -47,6 +47,10 @@ return URI(Scheme, /*Authority=*/"", AbsolutePath.substr(Pos + llvm::StringRef(TestRoot).size())); } + + llvm::Expected getIncludePath(const URI &U) const override { + return ("\"" + U.body() + "\"").str(); + } }; const char *TestScheme::Scheme = "unittest";