[clangd] Use new URI with scheme support in place of the existing LSP URI

Summary:
o Replace the existing clangd::URI with a wrapper of FileURI which also
carries a resolved file path.
o s/FileURI/URI/
o Get rid of the URI hack in vscode extension.

Reviewers: sammccall

Reviewed By: sammccall

Subscribers: klimek, ilya-biryukov, jkorous-apple, cfe-commits

Differential Revision: https://reviews.llvm.org/D42419

llvm-svn: 323660
This commit is contained in:
Eric Liu 2018-01-29 15:37:46 +00:00
parent ba8daf0964
commit 78ed91a76c
9 changed files with 108 additions and 134 deletions

View File

@ -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 @@ void ClangdLSPServer::onRename(Ctx C, RenameParams &Params) {
std::vector<TextEdit> 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 @@ void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) {
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::onGoToDefinition(Ctx C,
void ClangdLSPServer::onSwitchSourceHeader(Ctx C,
TextDocumentIdentifier &Params) {
llvm::Optional<Path> 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 @@ void ClangdLSPServer::onDiagnosticsReady(
{"method", "textDocument/publishDiagnostics"},
{"params",
json::obj{
{"uri", URI::fromFile(File)},
{"uri", URIForFile{File}},
{"diagnostics", std::move(DiagnosticsJSON)},
}},
});

View File

@ -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) {

View File

@ -25,6 +25,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H
#include "JSONExpr.h"
#include "URI.h"
#include "llvm/ADT/Optional.h"
#include <string>
#include <vector>
@ -47,32 +48,31 @@ enum class ErrorCode {
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 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Range &);
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 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TextEdit &);
struct TextDocumentItem {
/// The text document's URI.
URI uri;
URIForFile uri;
/// The text document's language identifier.
std::string languageId;
@ -195,7 +195,7 @@ struct InitializeParams {
/// The rootUri of the workspace. Is null if no
/// folder is open. If both `rootPath` and `rootUri` are set
/// `rootUri` wins.
llvm::Optional<URI> rootUri;
llvm::Optional<URIForFile> rootUri;
// User provided initialization options.
// initializationOptions?: any;
@ -253,7 +253,7 @@ bool fromJSON(const json::Expr &E, FileChangeType &Out);
struct FileEvent {
/// The file's URI.
URI uri;
URIForFile uri;
/// The change type.
FileChangeType type;
};

View File

@ -47,7 +47,7 @@ public:
return std::string(Path.begin(), Path.end());
}
llvm::Expected<FileURI>
llvm::Expected<URI>
uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
using namespace llvm::sys;
@ -56,7 +56,7 @@ public:
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 @@ std::string percentDecode(llvm::StringRef Content) {
} // namespace
llvm::Expected<FileURI> 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 @@ std::string FileURI::toString() const {
return Result;
}
llvm::Expected<FileURI> FileURI::parse(llvm::StringRef OrigUri) {
FileURI U;
llvm::Expected<URI> URI::parse(llvm::StringRef OrigUri) {
URI U;
llvm::StringRef Uri = OrigUri;
auto Pos = Uri.find(':');
@ -177,8 +170,8 @@ llvm::Expected<FileURI> FileURI::parse(llvm::StringRef OrigUri) {
return U;
}
llvm::Expected<FileURI> FileURI::create(llvm::StringRef AbsolutePath,
llvm::StringRef Scheme) {
llvm::Expected<URI> 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 @@ llvm::Expected<FileURI> FileURI::create(llvm::StringRef AbsolutePath,
return S->get()->uriFromAbsolutePath(AbsolutePath);
}
llvm::Expected<std::string> 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<std::string> URI::resolve(const URI &Uri,
llvm::StringRef HintPath) {
auto S = findSchemeByName(Uri.Scheme);
if (!S)
return S.takeError();

View File

@ -26,8 +26,10 @@ namespace clangd {
/// Clangd handles URIs of the form <scheme>:[//<authority>]<body>. 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 @@ public:
/// Returns a string URI with all components percent-encoded.
std::string toString() const;
/// Create a FileURI from unescaped scheme+authority+body.
static llvm::Expected<FileURI> 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<FileURI> create(llvm::StringRef AbsolutePath,
llvm::StringRef Scheme = "file");
static llvm::Expected<URI> 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 "<scheme>:[//<authority>/]<path>". Percent-encoded
/// characters in the URI will be decoded.
static llvm::Expected<FileURI> parse(llvm::StringRef Uri);
static llvm::Expected<URI> 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<std::string> resolve(const FileURI &U,
static llvm::Expected<std::string> 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 @@ public:
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<std::string>
getAbsolutePath(llvm::StringRef Authority, llvm::StringRef Body,
llvm::StringRef HintPath) const = 0;
virtual llvm::Expected<FileURI>
virtual llvm::Expected<URI>
uriFromAbsolutePath(llvm::StringRef AbsolutePath) const = 0;
};

View File

@ -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 @@ getDeclarationLocation(ParsedAST &AST, const SourceRange &ValSourceRange) {
StringRef FilePath = F->tryGetRealPathName();
if (FilePath.empty())
FilePath = F->getName();
L.uri = URI::fromFile(FilePath);
L.uri.file = FilePath;
L.range = R;
return L;
}

View File

@ -27,13 +27,6 @@ export function activate(context: vscode.ExtensionContext) {
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)
}

View File

@ -40,13 +40,12 @@ public:
.str();
}
llvm::Expected<FileURI>
llvm::Expected<URI>
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 @@ static URISchemeRegistry::Add<TestScheme> X(TestScheme::Scheme, "Test schema");
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 @@ TEST(PercentEncodingTest, Decode) {
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, Create) {
}
TEST(URITest, FailedCreate) {
auto Fail = [](llvm::Expected<FileURI> U) {
auto Fail = [](llvm::Expected<URI> 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, Parse) {
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, Resolve) {
TEST(URITest, Platform) {
auto Path = getVirtualTestFilePath("x");
auto U = FileURI::create(Path, "file");
auto U = URI::create(Path, "file");
EXPECT_TRUE(static_cast<bool>(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;

View File

@ -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 @@ using testing::UnorderedElementsAreArray;
// 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),