Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -10,6 +10,7 @@ #include "ClangdLSPServer.h" #include "JSONRPCDispatcher.h" #include "SourceCode.h" +#include "URI.h" #include "llvm/Support/FormatVariadic.h" using namespace clang::clangd; @@ -157,7 +158,7 @@ std::vector Edits = replacementsToEdits(*Code, *Replacements); WorkspaceEdit WE; - WE.changes = {{Params.textDocument.uri.uri, Edits}}; + WE.changes = {{Params.textDocument.uri.uri(), Edits}}; reply(C, WE); } @@ -227,7 +228,7 @@ auto Edits = getFixIts(Params.textDocument.uri.file, D); if (!Edits.empty()) { WorkspaceEdit WE; - WE.changes = {{Params.textDocument.uri.uri, std::move(Edits)}}; + WE.changes = {{Params.textDocument.uri.uri(), std::move(Edits)}}; Commands.push_back(json::obj{ {"title", llvm::formatv("Apply FixIt {0}", D.message)}, {"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}, @@ -279,8 +280,7 @@ void ClangdLSPServer::onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) { llvm::Optional Result = Server.switchSourceHeader(Params.uri.file); - std::string ResultUri; - reply(C, Result ? URI::fromFile(*Result).uri : ""); + reply(C, Result ? URI::createFile(*Result).toString() : ""); } void ClangdLSPServer::onDocumentHighlight(Ctx C, @@ -377,7 +377,7 @@ {"method", "textDocument/publishDiagnostics"}, {"params", json::obj{ - {"uri", URI::fromFile(File)}, + {"uri", URIForFile{File}}, {"diagnostics", std::move(DiagnosticsJSON)}, }}, }); Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -25,6 +25,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H #include "JSONExpr.h" +#include "URI.h" #include "llvm/ADT/Optional.h" #include #include @@ -47,32 +48,31 @@ RequestCancelled = -32800, }; -struct URI { - std::string uri; +struct URIForFile { std::string file; - static URI fromUri(llvm::StringRef uri); - static URI fromFile(llvm::StringRef file); + std::string uri() const { return URI::createFile(file).toString(); } - friend bool operator==(const URI &LHS, const URI &RHS) { - return LHS.uri == RHS.uri; + friend bool operator==(const URIForFile &LHS, const URIForFile &RHS) { + return LHS.file == RHS.file; } - friend bool operator!=(const URI &LHS, const URI &RHS) { + friend bool operator!=(const URIForFile &LHS, const URIForFile &RHS) { return !(LHS == RHS); } - friend bool operator<(const URI &LHS, const URI &RHS) { - return LHS.uri < RHS.uri; + friend bool operator<(const URIForFile &LHS, const URIForFile &RHS) { + return LHS.file < RHS.file; } }; -json::Expr toJSON(const URI &U); -bool fromJSON(const json::Expr &, URI &); -llvm::raw_ostream &operator<<(llvm::raw_ostream &, const URI &); + +/// Serialize/deserialize \p URIForFile to/from a string URI. +json::Expr toJSON(const URIForFile &U); +bool fromJSON(const json::Expr &, URIForFile &); struct TextDocumentIdentifier { /// The text document's URI. - URI uri; + URIForFile uri; }; bool fromJSON(const json::Expr &, TextDocumentIdentifier &); @@ -116,7 +116,7 @@ struct Location { /// The text document's URI. - URI uri; + URIForFile uri; Range range; friend bool operator==(const Location &LHS, const Location &RHS) { @@ -154,7 +154,7 @@ struct TextDocumentItem { /// The text document's URI. - URI uri; + URIForFile uri; /// The text document's language identifier. std::string languageId; @@ -195,7 +195,7 @@ /// The rootUri of the workspace. Is null if no /// folder is open. If both `rootPath` and `rootUri` are set /// `rootUri` wins. - llvm::Optional rootUri; + llvm::Optional rootUri; // User provided initialization options. // initializationOptions?: any; @@ -253,7 +253,7 @@ struct FileEvent { /// The file's URI. - URI uri; + URIForFile uri; /// The change type. FileChangeType type; }; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -12,6 +12,8 @@ //===----------------------------------------------------------------------===// #include "Protocol.h" +#include "URI.h" +#include "Logger.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/Format.h" @@ -22,45 +24,32 @@ namespace clang { namespace clangd { -URI URI::fromUri(llvm::StringRef uri) { - URI Result; - Result.uri = uri; - uri.consume_front("file://"); - // Also trim authority-less URIs - uri.consume_front("file:"); - // For Windows paths e.g. /X: - if (uri.size() > 2 && uri[0] == '/' && uri[2] == ':') - uri.consume_front("/"); - // Make sure that file paths are in native separators - Result.file = llvm::sys::path::convert_to_slash(uri); - return Result; -} - -URI URI::fromFile(llvm::StringRef file) { - using namespace llvm::sys; - URI Result; - Result.file = file; - Result.uri = "file://"; - // For Windows paths e.g. X: - if (file.size() > 1 && file[1] == ':') - Result.uri += "/"; - // Make sure that uri paths are with posix separators - Result.uri += path::convert_to_slash(file, path::Style::posix); - return Result; -} - -bool fromJSON(const json::Expr &E, URI &R) { +bool fromJSON(const json::Expr &E, URIForFile &R) { if (auto S = E.asString()) { - R = URI::fromUri(*S); + auto U = URI::parse(*S); + if (!U) { + log(Context::empty(), + "Failed to parse URI " + *S + ": " + llvm::toString(U.takeError())); + return false; + } + if (U->scheme() != "file") { + log(Context::empty(), + "Clangd only supports 'file' URI scheme for workspace files: " + *S); + return false; + } + // We know that body of a file URI is absolute path. + R.file = U->body(); return true; } return false; } -json::Expr toJSON(const URI &U) { return U.uri; } +json::Expr toJSON(const URIForFile &U) { + return U.uri(); +} -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const URI &U) { - return OS << U.uri; +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const URIForFile &U) { + return OS << U.uri(); } bool fromJSON(const json::Expr &Params, TextDocumentIdentifier &R) { Index: clangd/URI.h =================================================================== --- clangd/URI.h +++ clangd/URI.h @@ -26,8 +26,10 @@ /// Clangd handles URIs of the form :[//]. It doesn't /// further split the authority or body into constituent parts (e.g. query /// strings is included in the body). -class FileURI { +class URI { public: + URI(llvm::StringRef Scheme, llvm::StringRef Authority, llvm::StringRef Body); + /// Returns decoded scheme e.g. "https" llvm::StringRef scheme() const { return Scheme; } /// Returns decoded authority e.g. "reviews.lvm.org" @@ -38,35 +40,38 @@ /// Returns a string URI with all components percent-encoded. std::string toString() const; - /// Create a FileURI from unescaped scheme+authority+body. - static llvm::Expected create(llvm::StringRef Scheme, - llvm::StringRef Authority, - llvm::StringRef Body); - - /// Creates a FileURI for a file in the given scheme. \p Scheme must be + /// Creates a URI for a file in the given scheme. \p Scheme must be /// registered. The URI is percent-encoded. - static llvm::Expected create(llvm::StringRef AbsolutePath, - llvm::StringRef Scheme = "file"); + static llvm::Expected create(llvm::StringRef AbsolutePath, + llvm::StringRef Scheme); + + /// This creates a file:// URI for \p AbsolutePath. The path must be absolute. + static URI createFile(llvm::StringRef AbsolutePath); /// Parse a URI string ":[///]". Percent-encoded /// characters in the URI will be decoded. - static llvm::Expected parse(llvm::StringRef Uri); + static llvm::Expected parse(llvm::StringRef Uri); /// Resolves the absolute path of \p U. If there is no matching scheme, or the /// URI is invalid in the scheme, this returns an error. /// /// \p HintPath A related path, such as the current file or working directory, /// which can help disambiguate when the same file exists in many workspaces. - static llvm::Expected resolve(const FileURI &U, + static llvm::Expected resolve(const URI &U, llvm::StringRef HintPath = ""); - friend bool operator==(const FileURI &LHS, const FileURI &RHS) { + 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); } + 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); + } + private: - FileURI() = default; + URI() = default; std::string Scheme; std::string Authority; @@ -81,13 +86,13 @@ virtual ~URIScheme() = default; /// Returns the absolute path of the file corresponding to the URI - /// authority+body in the file system. See FileURI::resolve for semantics of + /// authority+body in the file system. See URI::resolve for semantics of /// \p HintPath. virtual llvm::Expected getAbsolutePath(llvm::StringRef Authority, llvm::StringRef Body, llvm::StringRef HintPath) const = 0; - virtual llvm::Expected + virtual llvm::Expected uriFromAbsolutePath(llvm::StringRef AbsolutePath) const = 0; }; Index: clangd/URI.cpp =================================================================== --- clangd/URI.cpp +++ clangd/URI.cpp @@ -47,7 +47,7 @@ return std::string(Path.begin(), Path.end()); } - llvm::Expected + llvm::Expected uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { using namespace llvm::sys; @@ -56,7 +56,7 @@ if (AbsolutePath.size() > 1 && AbsolutePath[1] == ':') Body = "/"; Body += path::convert_to_slash(AbsolutePath); - return FileURI::create(Scheme, /*Authority=*/"", Body); + return URI(Scheme, /*Authority=*/"", Body); } }; @@ -129,22 +129,15 @@ } // namespace -llvm::Expected FileURI::create(llvm::StringRef Scheme, - llvm::StringRef Authority, - llvm::StringRef Body) { - if (Scheme.empty()) - return make_string_error("Scheme must be specified in a URI."); - if (!Authority.empty() && !Body.startswith("/")) - return make_string_error( - "URI body must start with '/' when authority is present."); - FileURI U; - U.Scheme = Scheme; - U.Authority = Authority; - U.Body = Body; - return U; +URI::URI(llvm::StringRef Scheme, llvm::StringRef Authority, + llvm::StringRef Body) + : Scheme(Scheme), Authority(Authority), Body(Body) { + assert(!Scheme.empty()); + assert((Authority.empty() || Body.startswith("/")) && + "URI body must start with '/' when authority is present."); } -std::string FileURI::toString() const { +std::string URI::toString() const { std::string Result; llvm::raw_string_ostream OS(Result); OS << percentEncode(Scheme) << ":"; @@ -159,8 +152,8 @@ return Result; } -llvm::Expected FileURI::parse(llvm::StringRef OrigUri) { - FileURI U; +llvm::Expected URI::parse(llvm::StringRef OrigUri) { + URI U; llvm::StringRef Uri = OrigUri; auto Pos = Uri.find(':'); @@ -177,8 +170,8 @@ return U; } -llvm::Expected FileURI::create(llvm::StringRef AbsolutePath, - llvm::StringRef Scheme) { +llvm::Expected URI::create(llvm::StringRef AbsolutePath, + llvm::StringRef Scheme) { if (!llvm::sys::path::is_absolute(AbsolutePath)) return make_string_error("Not a valid absolute path: " + AbsolutePath); auto S = findSchemeByName(Scheme); @@ -187,8 +180,15 @@ return S->get()->uriFromAbsolutePath(AbsolutePath); } -llvm::Expected FileURI::resolve(const FileURI &Uri, - llvm::StringRef HintPath) { +URI URI::createFile(llvm::StringRef AbsolutePath) { + auto U = create(AbsolutePath, "file"); + if (!U) + llvm_unreachable(llvm::toString(U.takeError()).c_str()); + return std::move(*U); +} + +llvm::Expected URI::resolve(const URI &Uri, + llvm::StringRef HintPath) { auto S = findSchemeByName(Uri.Scheme); if (!S) return S.takeError(); Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -7,6 +7,8 @@ // //===---------------------------------------------------------------------===// #include "XRefs.h" +#include "Logger.h" +#include "URI.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexingAction.h" namespace clang { @@ -139,7 +141,7 @@ StringRef FilePath = F->tryGetRealPathName(); if (FilePath.empty()) FilePath = F->getName(); - L.uri = URI::fromFile(FilePath); + L.uri.file = FilePath; L.range = R; return L; } Index: clangd/clients/clangd-vscode/src/extension.ts =================================================================== --- clangd/clients/clangd-vscode/src/extension.ts +++ clangd/clients/clangd-vscode/src/extension.ts @@ -27,13 +27,6 @@ const clientOptions: vscodelc.LanguageClientOptions = { // Register the server for C/C++ files documentSelector: [{scheme: 'file', pattern: filePattern}], - uriConverters: { - // FIXME: by default the URI sent over the protocol will be percent encoded (see rfc3986#section-2.1) - // the "workaround" below disables temporarily the encoding until decoding - // is implemented properly in clangd - code2Protocol: (uri: vscode.Uri) : string => uri.toString(true), - protocol2Code: (uri: string) : vscode.Uri => vscode.Uri.parse(uri) - }, synchronize: !syncFileEvents ? undefined : { fileEvents: vscode.workspace.createFileSystemWatcher(filePattern) } Index: unittests/clangd/URITests.cpp =================================================================== --- unittests/clangd/URITests.cpp +++ unittests/clangd/URITests.cpp @@ -40,13 +40,12 @@ .str(); } - llvm::Expected + llvm::Expected uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { auto Pos = AbsolutePath.find(TestRoot); assert(Pos != llvm::StringRef::npos); - return FileURI::create( - Scheme, /*Authority=*/"", - AbsolutePath.substr(Pos + llvm::StringRef(TestRoot).size())); + return URI(Scheme, /*Authority=*/"", + AbsolutePath.substr(Pos + llvm::StringRef(TestRoot).size())); } }; @@ -57,30 +56,22 @@ std::string createOrDie(llvm::StringRef AbsolutePath, llvm::StringRef Scheme = "file") { - auto Uri = FileURI::create(AbsolutePath, Scheme); + auto Uri = URI::create(AbsolutePath, Scheme); if (!Uri) llvm_unreachable(llvm::toString(Uri.takeError()).c_str()); return Uri->toString(); } -std::string createOrDie(llvm::StringRef Scheme, llvm::StringRef Authority, - llvm::StringRef Body) { - auto Uri = FileURI::create(Scheme, Authority, Body); - if (!Uri) - llvm_unreachable(llvm::toString(Uri.takeError()).c_str()); - return Uri->toString(); -} - -FileURI parseOrDie(llvm::StringRef Uri) { - auto U = FileURI::parse(Uri); +URI parseOrDie(llvm::StringRef Uri) { + auto U = URI::parse(Uri); if (!U) llvm_unreachable(llvm::toString(U.takeError()).c_str()); return *U; } TEST(PercentEncodingTest, Encode) { - EXPECT_EQ(createOrDie("x", /*Authority=*/"", "a/b/c"), "x:a/b/c"); - EXPECT_EQ(createOrDie("x", /*Authority=*/"", "a!b;c~"), "x:a%21b%3bc~"); + EXPECT_EQ(URI("x", /*Authority=*/"", "a/b/c").toString(), "x:a/b/c"); + EXPECT_EQ(URI("x", /*Authority=*/"", "a!b;c~").toString(), "x:a%21b%3bc~"); } TEST(PercentEncodingTest, Decode) { @@ -93,8 +84,8 @@ EXPECT_EQ(parseOrDie("x:a%21b%3ac~").body(), "a!b:c~"); } -std::string resolveOrDie(const FileURI &U, llvm::StringRef HintPath = "") { - auto Path = FileURI::resolve(U, HintPath); +std::string resolveOrDie(const URI &U, llvm::StringRef HintPath = "") { + auto Path = URI::resolve(U, HintPath); if (!Path) llvm_unreachable(llvm::toString(Path.takeError()).c_str()); return *Path; @@ -110,25 +101,16 @@ } TEST(URITest, FailedCreate) { - auto Fail = [](llvm::Expected U) { + auto Fail = [](llvm::Expected U) { if (!U) { llvm::consumeError(U.takeError()); return true; } return false; }; - // Create from scheme+authority+body: - // - // Scheme must be provided. - EXPECT_TRUE(Fail(FileURI::create("", "auth", "/a"))); - // Body must start with '/' if authority is present. - EXPECT_TRUE(Fail(FileURI::create("scheme", "auth", "x/y/z"))); - - // Create from scheme registry: - // - EXPECT_TRUE(Fail(FileURI::create("/x/y/z", "no"))); + EXPECT_TRUE(Fail(URI::create("/x/y/z", "no"))); // Path has to be absolute. - EXPECT_TRUE(Fail(FileURI::create("x/y/z"))); + EXPECT_TRUE(Fail(URI::create("x/y/z", "file"))); } TEST(URITest, Parse) { @@ -163,7 +145,7 @@ TEST(URITest, ParseFailed) { auto FailedParse = [](llvm::StringRef U) { - auto URI = FileURI::parse(U); + auto URI = URI::parse(U); if (!URI) { llvm::consumeError(URI.takeError()); return true; @@ -194,14 +176,14 @@ TEST(URITest, Platform) { auto Path = getVirtualTestFilePath("x"); - auto U = FileURI::create(Path, "file"); + auto U = URI::create(Path, "file"); EXPECT_TRUE(static_cast(U)); EXPECT_THAT(resolveOrDie(*U), Path.str()); } TEST(URITest, ResolveFailed) { auto FailedResolve = [](llvm::StringRef Uri) { - auto Path = FileURI::resolve(parseOrDie(Uri)); + auto Path = URI::resolve(parseOrDie(Uri)); if (!Path) { llvm::consumeError(Path.takeError()); return true; Index: unittests/clangd/XRefsTests.cpp =================================================================== --- unittests/clangd/XRefsTests.cpp +++ unittests/clangd/XRefsTests.cpp @@ -9,6 +9,7 @@ #include "Annotations.h" #include "ClangdUnit.h" #include "Matchers.h" +#include "TestFS.h" #include "XRefs.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/PCHContainerOperations.h" @@ -38,7 +39,9 @@ // FIXME: this is duplicated with FileIndexTests. Share it. ParsedAST build(StringRef Code) { - auto CI = createInvocationFromCommandLine({"clang", "-xc++", "Foo.cpp"}); + auto TestFile = getVirtualTestFilePath("Foo.cpp"); + auto CI = + createInvocationFromCommandLine({"clang", "-xc++", TestFile.c_str()}); auto Buf = MemoryBuffer::getMemBuffer(Code); auto AST = ParsedAST::Build( Context::empty(), std::move(CI), nullptr, std::move(Buf),