Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -21,6 +21,7 @@ ProtocolHandlers.cpp SourceCode.cpp Trace.cpp + URI.cpp XRefs.cpp index/FileIndex.cpp index/Index.cpp Index: clangd/URI.h =================================================================== --- /dev/null +++ clangd/URI.h @@ -0,0 +1,95 @@ +//===--- URI.h - File URIs with schemas --------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PATHURI_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PATHURI_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Registry.h" + +namespace clang { +namespace clangd { + +/// \brief An URI for a file path in a certain schema. This is invalid if either +/// schema or path is empty. +struct FileURI { + std::string Schema; + // This is interpreted according to the schema. + std::string Path; + + FileURI() = default; + + FileURI(std::string Schema, std::string Path) + : Schema(std::move(Schema)), Path(std::move(Path)) {} + + bool IsValid() const { return !Schema.empty() && !Path.empty(); } + + /// \brief Returns a URI string "://". + std::string toString() const; + + /// \brief Parse a URI string "://". If this is an invalid URI, + /// this returns an empty URI with empty schema and path. + static FileURI fromURI(llvm::StringRef Uri); + + friend bool operator==(const FileURI &LHS, const FileURI &RHS) { + return std::tie(LHS.Schema, LHS.Path) == std::tie(RHS.Schema, RHS.Path); + } +}; + +/// \brief This manages file paths in different schemas. Different +/// codebases/projects can have different file schemas, and clangd interprets a +/// file path according to the schema. For example, a file path provided by a +/// remote symbol index can follow a certain schema (e.g. relative to a project +/// root directory), and clangd needs to combine the schema path with execution +/// environment (e.g. working/build directory) in order to get a file path in +/// the file system. +/// +/// By default, a "file" schema is supported where URI paths are always absolute +/// in the file system. +class URISchema { +public: + virtual ~URISchema() = default; + + /// \brief Returns the absolute path of the file corresponding to \p Uri in + /// the file system. \p CurrentFile is the file from which the request is + /// issued. This is needed because the same URI in different workspace may + /// correspond to different files. + /// + /// Returns "" if \p Uri is not in the schema. + virtual std::string getAbsolutePath(llvm::StringRef CurrentFile, + const FileURI &Uri) const = 0; + + /// \brief Creates an URI for the file \p AbsolutePath in this schema. + virtual FileURI uriFromAbsolutePath(llvm::StringRef AbsolutePath) const = 0; +}; + +/// \brief This manages file paths in the file system. All paths in the schema +/// are absolute (with leading '/'). +class FileSystemSchema : public URISchema { +public: + static const char *Schema; + + std::string getAbsolutePath(llvm::StringRef /*CurrentFile*/, + const FileURI &Uri) const override; + + FileURI uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override; +}; + +typedef llvm::Registry URISchemaRegistry; + +/// \brief Resolves the absolute path of \p Uri with the first matching schema +/// registered. +/// +/// Returns "" if there is no matching schema. +std::string resolve(llvm::StringRef CurrentFile, const FileURI &Uri); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_PATHURI_H Index: clangd/URI.cpp =================================================================== --- /dev/null +++ clangd/URI.cpp @@ -0,0 +1,54 @@ +//===---- URI.h - File URIs with schemas -------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "URI.h" + +LLVM_INSTANTIATE_REGISTRY(clang::clangd::URISchemaRegistry) + +namespace clang { +namespace clangd { + +std::string FileURI::toString() const { return Schema + "://" + Path; } + +FileURI FileURI::fromURI(llvm::StringRef Uri) { + auto Pos = Uri.find("://"); + if (Pos == llvm::StringRef::npos) + return {"", ""}; + return {Uri.substr(0, Pos), Uri.substr(Pos + 3)}; +} + +const char *FileSystemSchema::Schema = "file"; + +std::string FileSystemSchema::getAbsolutePath(llvm::StringRef /*CurrentFile*/, + const FileURI &Uri) const { + return Schema == Uri.Schema ? Uri.Path : ""; +} + +FileURI +FileSystemSchema::uriFromAbsolutePath(llvm::StringRef AbsolutePath) const { + assert(AbsolutePath.startswith("/") && "Expect an absolute path."); + return {Schema, AbsolutePath}; +} + +static URISchemaRegistry::Add + X(FileSystemSchema::Schema, + "URI schema for absolute paths in the file system."); + +std::string resolve(llvm::StringRef CurrentFile, const FileURI &Uri) { + for (auto I = URISchemaRegistry::begin(), E = URISchemaRegistry::end(); + I != E; ++I) { + if (I->getName() != Uri.Schema) + continue; + return I->instantiate()->getAbsolutePath(CurrentFile, Uri); + } + return ""; +} + +} // namespace clangd +} // namespace clang Index: unittests/clangd/CMakeLists.txt =================================================================== --- unittests/clangd/CMakeLists.txt +++ unittests/clangd/CMakeLists.txt @@ -18,6 +18,7 @@ FuzzyMatchTests.cpp IndexTests.cpp JSONExprTests.cpp + URITests.cpp TestFS.cpp TraceTests.cpp SourceCodeTests.cpp Index: unittests/clangd/URITests.cpp =================================================================== --- /dev/null +++ unittests/clangd/URITests.cpp @@ -0,0 +1,75 @@ +//===-- URITests.cpp ---------------------------------*- C++ -*-----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "URI.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +TEST(URITest, URIStrings) { + EXPECT_EQ(FileURI::fromURI("abc:///x/y/z").toString(), "abc:///x/y/z"); + EXPECT_EQ(FileURI::fromURI("abc:///x/y/z"), FileURI("abc", "/x/y/z")); + EXPECT_EQ(FileURI::fromURI("abc://x/y/z"), FileURI("abc", "x/y/z")); + + EXPECT_TRUE(FileURI::fromURI("abc:///x/y/z").IsValid()); + EXPECT_FALSE(FileURI::fromURI("://x/y/z").IsValid()); + EXPECT_FALSE(FileURI::fromURI("/x/y/z").IsValid()); +} + +TEST(URITest, FileSystemSchema) { + FileURI Uri{"file", "/a/b/c"}; + FileSystemSchema S; + EXPECT_EQ(S.getAbsolutePath(/*CurrentFile*/ "", Uri), "/a/b/c"); + EXPECT_EQ(S.uriFromAbsolutePath("/a/b/c"), Uri); +} + +// Assume all files in the schema have a "test-root/" root directory, and the +// schema path is the relative path to the root directory. +// So the schema of "/some-dir/test-root/x/y/z" is "test://x/y/z". +class TestSchema : public URISchema { +public: + static const char *Schema; + + static const char *TestRoot; + + std::string getAbsolutePath(llvm::StringRef CurrentFile, + const FileURI &Uri) const override { + auto Pos = CurrentFile.find(TestRoot); + assert(Pos != llvm::StringRef::npos); + return CurrentFile.substr(0, Pos + llvm::StringRef(TestRoot).size()).str() + + Uri.Path; + } + + FileURI uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { + auto Pos = AbsolutePath.find(TestRoot); + assert(Pos != llvm::StringRef::npos); + return {Schema, + AbsolutePath.substr(Pos + Pos + llvm::StringRef(TestRoot).size())}; + } +}; + +const char *TestSchema::Schema = "test"; +const char *TestSchema::TestRoot = "/test-root/"; + +static URISchemaRegistry::Add X(TestSchema::Schema, "Test schema"); + +TEST(URITest, ResolveURIs) { + EXPECT_EQ(resolve("", FileURI("file", "/a/b/c")), "/a/b/c"); + EXPECT_EQ(resolve("/dir/test-root/x/y/z", FileURI("test", "a/b/c")), + "/dir/test-root/a/b/c"); + // Unsupported schema. + EXPECT_EQ(resolve("/x/y/z", FileURI("invalid", "a/b/c")), ""); +} + +} // namespace +} // namespace clangd +} // namespace clang