[clangd] Extract FsPath from file:// uri

Patch contributed by stanionascu!

rfc8089#appendix-E.2 specifies that paths can begin with a drive letter e.g. as file:///c:/.
In this case just consuming front file:// is not enough and the 3rd slash must be consumed to produce a valid path on windows.

The patch introduce a generic way of converting an uri to a filesystem path and back.

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

llvm-svn: 299758
This commit is contained in:
Krasimir Georgiev 2017-04-07 11:03:26 +00:00
parent 6e79529db4
commit 50117372db
7 changed files with 105 additions and 60 deletions

View File

@ -28,7 +28,6 @@ getRemappedFiles(const DocumentStore &Docs) {
std::vector<ASTUnit::RemappedFile> RemappedFiles;
for (const auto &P : Docs.getAllDocuments()) {
StringRef FileName = P.first;
FileName.consume_front("file://");
RemappedFiles.push_back(ASTUnit::RemappedFile(
FileName,
llvm::MemoryBuffer::getMemBufferCopy(P.second, FileName).release()));
@ -142,7 +141,7 @@ void ASTManager::parseFileAndPublishDiagnostics(StringRef File) {
Diagnostics.pop_back(); // Drop trailing comma.
Output.writeMessage(
R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
File + R"(","diagnostics":[)" + Diagnostics + R"(]}})");
URI::fromFile(File).uri + R"(","diagnostics":[)" + Diagnostics + R"(]}})");
}
ASTManager::~ASTManager() {
@ -155,42 +154,39 @@ ASTManager::~ASTManager() {
ClangWorker.join();
}
void ASTManager::onDocumentAdd(StringRef Uri) {
void ASTManager::onDocumentAdd(StringRef File) {
if (RunSynchronously) {
parseFileAndPublishDiagnostics(Uri);
parseFileAndPublishDiagnostics(File);
return;
}
std::lock_guard<std::mutex> Guard(RequestLock);
// Currently we discard all pending requests and just enqueue the latest one.
RequestQueue.clear();
RequestQueue.push_back(Uri);
RequestQueue.push_back(File);
ClangRequestCV.notify_one();
}
tooling::CompilationDatabase *
ASTManager::getOrCreateCompilationDatabaseForFile(StringRef Uri) {
auto &I = CompilationDatabases[Uri];
ASTManager::getOrCreateCompilationDatabaseForFile(StringRef File) {
auto &I = CompilationDatabases[File];
if (I)
return I.get();
Uri.consume_front("file://");
std::string Error;
I = tooling::CompilationDatabase::autoDetectFromSource(Uri, Error);
I = tooling::CompilationDatabase::autoDetectFromSource(File, Error);
Output.log("Failed to load compilation database: " + Twine(Error) + "\n");
return I.get();
}
std::unique_ptr<clang::ASTUnit>
ASTManager::createASTUnitForFile(StringRef Uri, const DocumentStore &Docs) {
ASTManager::createASTUnitForFile(StringRef File, const DocumentStore &Docs) {
tooling::CompilationDatabase *CDB =
getOrCreateCompilationDatabaseForFile(Uri);
getOrCreateCompilationDatabaseForFile(File);
Uri.consume_front("file://");
std::vector<tooling::CompileCommand> Commands;
if (CDB) {
Commands = CDB->getCompileCommands(Uri);
Commands = CDB->getCompileCommands(File);
// chdir. This is thread hostile.
if (!Commands.empty())
llvm::sys::fs::set_current_path(Commands.front().Directory);
@ -198,8 +194,8 @@ ASTManager::createASTUnitForFile(StringRef Uri, const DocumentStore &Docs) {
if (Commands.empty()) {
// Add a fake command line if we know nothing.
Commands.push_back(tooling::CompileCommand(
llvm::sys::path::parent_path(Uri), llvm::sys::path::filename(Uri),
{"clang", "-fsyntax-only", Uri.str()}, ""));
llvm::sys::path::parent_path(File), llvm::sys::path::filename(File),
{"clang", "-fsyntax-only", File.str()}, ""));
}
// Inject the resource dir.
@ -278,7 +274,7 @@ public:
} // namespace
std::vector<CompletionItem>
ASTManager::codeComplete(StringRef Uri, unsigned Line, unsigned Column) {
ASTManager::codeComplete(StringRef File, unsigned Line, unsigned Column) {
CodeCompleteOptions CCO;
CCO.IncludeBriefComments = 1;
// This is where code completion stores dirty buffers. Need to free after
@ -290,15 +286,13 @@ ASTManager::codeComplete(StringRef Uri, unsigned Line, unsigned Column) {
std::vector<CompletionItem> Items;
CompletionItemsCollector Collector(&Items, CCO);
std::lock_guard<std::mutex> Guard(ASTLock);
auto &Unit = ASTs[Uri];
auto &Unit = ASTs[File];
if (!Unit)
Unit = createASTUnitForFile(Uri, this->Store);
Unit = createASTUnitForFile(File, this->Store);
if (!Unit)
return {};
IntrusiveRefCntPtr<SourceManager> SourceMgr(
new SourceManager(*DiagEngine, Unit->getFileManager()));
StringRef File(Uri);
File.consume_front("file://");
// CodeComplete seems to require fresh LangOptions.
LangOptions LangOpts = Unit->getLangOpts();
// The language server protocol uses zero-based line and column numbers.

View File

@ -34,7 +34,7 @@ public:
ASTManager(JSONOutput &Output, DocumentStore &Store, bool RunSynchronously);
~ASTManager() override;
void onDocumentAdd(StringRef Uri) override;
void onDocumentAdd(StringRef File) override;
// FIXME: Implement onDocumentRemove
/// Get code completions at a specified \p Line and \p Column in \p File.
@ -61,21 +61,21 @@ private:
// asynchronously.
bool RunSynchronously;
/// Loads a compilation database for URI. May return nullptr if it fails. The
/// Loads a compilation database for File. May return nullptr if it fails. The
/// database is cached for subsequent accesses.
clang::tooling::CompilationDatabase *
getOrCreateCompilationDatabaseForFile(StringRef Uri);
// Creates a new ASTUnit for the document at Uri.
getOrCreateCompilationDatabaseForFile(StringRef File);
// Creates a new ASTUnit for the document at File.
// FIXME: This calls chdir internally, which is thread unsafe.
std::unique_ptr<clang::ASTUnit>
createASTUnitForFile(StringRef Uri, const DocumentStore &Docs);
createASTUnitForFile(StringRef File, const DocumentStore &Docs);
void runWorker();
void parseFileAndPublishDiagnostics(StringRef File);
/// Clang objects.
/// A map from Uri-s to ASTUnit-s. Guarded by \c ASTLock. ASTUnit-s are used
/// A map from File-s to ASTUnit-s. Guarded by \c ASTLock. ASTUnit-s are used
/// for generating diagnostics and fix-it-s asynchronously by the worker
/// thread and synchronously for code completion.
///

View File

@ -22,40 +22,40 @@ class DocumentStore;
struct DocumentStoreListener {
virtual ~DocumentStoreListener() = default;
virtual void onDocumentAdd(StringRef Uri) {}
virtual void onDocumentRemove(StringRef Uri) {}
virtual void onDocumentAdd(StringRef File) {}
virtual void onDocumentRemove(StringRef File) {}
};
/// A container for files opened in a workspace, addressed by URI. The contents
/// A container for files opened in a workspace, addressed by File. The contents
/// are owned by the DocumentStore.
class DocumentStore {
public:
/// Add a document to the store. Overwrites existing contents.
void addDocument(StringRef Uri, StringRef Text) {
void addDocument(StringRef File, StringRef Text) {
{
std::lock_guard<std::mutex> Guard(DocsMutex);
Docs[Uri] = Text;
Docs[File] = Text;
}
for (const auto &Listener : Listeners)
Listener->onDocumentAdd(Uri);
Listener->onDocumentAdd(File);
}
/// Delete a document from the store.
void removeDocument(StringRef Uri) {
void removeDocument(StringRef File) {
{
std::lock_guard<std::mutex> Guard(DocsMutex);
Docs.erase(Uri);
Docs.erase(File);
}
for (const auto &Listener : Listeners)
Listener->onDocumentRemove(Uri);
Listener->onDocumentRemove(File);
}
/// Retrieve a document from the store. Empty string if it's unknown.
///
/// This function is thread-safe. It returns a copy to avoid handing out
/// references to unguarded data.
std::string getDocument(StringRef Uri) const {
std::string getDocument(StringRef File) const {
// FIXME: This could be a reader lock.
std::lock_guard<std::mutex> Guard(DocsMutex);
return Docs.lookup(Uri);
return Docs.lookup(File);
}
/// Add a listener. Does not take ownership.

View File

@ -17,8 +17,44 @@
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Path.h"
using namespace clang::clangd;
URI URI::fromUri(llvm::StringRef uri) {
URI Result;
Result.uri = uri;
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;
}
URI URI::parse(llvm::yaml::ScalarNode *Param) {
llvm::SmallString<10> Storage;
return URI::fromUri(Param->getValue(Storage));
}
std::string URI::unparse(const URI &U) {
return U.uri;
}
llvm::Optional<TextDocumentIdentifier>
TextDocumentIdentifier::parse(llvm::yaml::MappingNode *Params) {
TextDocumentIdentifier Result;
@ -34,9 +70,8 @@ TextDocumentIdentifier::parse(llvm::yaml::MappingNode *Params) {
if (!Value)
return llvm::None;
llvm::SmallString<10> Storage;
if (KeyValue == "uri") {
Result.uri = Value->getValue(Storage);
Result.uri = URI::parse(Value);
} else if (KeyValue == "version") {
// FIXME: parse version, but only for VersionedTextDocumentIdentifiers.
} else {
@ -142,7 +177,7 @@ TextDocumentItem::parse(llvm::yaml::MappingNode *Params) {
llvm::SmallString<10> Storage;
if (KeyValue == "uri") {
Result.uri = Value->getValue(Storage);
Result.uri = URI::parse(Value);
} else if (KeyValue == "languageId") {
Result.languageId = Value->getValue(Storage);
} else if (KeyValue == "version") {

View File

@ -29,9 +29,20 @@
namespace clang {
namespace clangd {
struct URI {
std::string uri;
std::string file;
static URI fromUri(llvm::StringRef uri);
static URI fromFile(llvm::StringRef file);
static URI parse(llvm::yaml::ScalarNode *Param);
static std::string unparse(const URI &U);
};
struct TextDocumentIdentifier {
/// The text document's URI.
std::string uri;
URI uri;
static llvm::Optional<TextDocumentIdentifier>
parse(llvm::yaml::MappingNode *Params);
@ -90,7 +101,7 @@ struct TextEdit {
struct TextDocumentItem {
/// The text document's URI.
std::string uri;
URI uri;
/// The text document's language identifier.
std::string languageId;
@ -328,7 +339,7 @@ struct CompletionItem {
/// this completion. Edits must not overlap with the main edit nor with
/// themselves.
std::vector<TextEdit> additionalTextEdits;
// TODO(krasimir): The following optional fields defined by the language
// server protocol are unsupported:
//

View File

@ -21,7 +21,7 @@ void TextDocumentDidOpenHandler::handleNotification(
Output.log("Failed to decode DidOpenTextDocumentParams!\n");
return;
}
Store.addDocument(DOTDP->textDocument.uri, DOTDP->textDocument.text);
Store.addDocument(DOTDP->textDocument.uri.file, DOTDP->textDocument.text);
}
void TextDocumentDidChangeHandler::handleNotification(
@ -32,7 +32,7 @@ void TextDocumentDidChangeHandler::handleNotification(
return;
}
// We only support full syncing right now.
Store.addDocument(DCTDP->textDocument.uri, DCTDP->contentChanges[0].text);
Store.addDocument(DCTDP->textDocument.uri.file, DCTDP->contentChanges[0].text);
}
/// Turn a [line, column] pair into an offset in Code.
@ -83,9 +83,6 @@ static std::string formatCode(StringRef Code, StringRef Filename,
// Call clang-format.
// FIXME: Don't ignore style.
format::FormatStyle Style = format::getLLVMStyle();
// On windows FileManager doesn't like file://. Just strip it, clang-format
// doesn't need it.
Filename.consume_front("file://");
tooling::Replacements Replacements =
format::reformat(Style, Code, Ranges, Filename);
@ -102,12 +99,12 @@ void TextDocumentRangeFormattingHandler::handleMethod(
return;
}
std::string Code = Store.getDocument(DRFP->textDocument.uri);
std::string Code = Store.getDocument(DRFP->textDocument.uri.file);
size_t Begin = positionToOffset(Code, DRFP->range.start);
size_t Len = positionToOffset(Code, DRFP->range.end) - Begin;
writeMessage(formatCode(Code, DRFP->textDocument.uri,
writeMessage(formatCode(Code, DRFP->textDocument.uri.file,
{clang::tooling::Range(Begin, Len)}, ID));
}
@ -121,14 +118,14 @@ void TextDocumentOnTypeFormattingHandler::handleMethod(
// Look for the previous opening brace from the character position and format
// starting from there.
std::string Code = Store.getDocument(DOTFP->textDocument.uri);
std::string Code = Store.getDocument(DOTFP->textDocument.uri.file);
size_t CursorPos = positionToOffset(Code, DOTFP->position);
size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
if (PreviousLBracePos == StringRef::npos)
PreviousLBracePos = CursorPos;
size_t Len = 1 + CursorPos - PreviousLBracePos;
writeMessage(formatCode(Code, DOTFP->textDocument.uri,
writeMessage(formatCode(Code, DOTFP->textDocument.uri.file,
{clang::tooling::Range(PreviousLBracePos, Len)}, ID));
}
@ -141,8 +138,8 @@ void TextDocumentFormattingHandler::handleMethod(
}
// Format everything.
std::string Code = Store.getDocument(DFP->textDocument.uri);
writeMessage(formatCode(Code, DFP->textDocument.uri,
std::string Code = Store.getDocument(DFP->textDocument.uri.file);
writeMessage(formatCode(Code, DFP->textDocument.uri.file,
{clang::tooling::Range(0, Code.size())}, ID));
}
@ -156,7 +153,7 @@ void CodeActionHandler::handleMethod(llvm::yaml::MappingNode *Params,
// We provide a code action for each diagnostic at the requested location
// which has FixIts available.
std::string Code = AST.getStore().getDocument(CAP->textDocument.uri);
std::string Code = AST.getStore().getDocument(CAP->textDocument.uri.file);
std::string Commands;
for (Diagnostic &D : CAP->context.diagnostics) {
std::vector<clang::tooling::Replacement> Fixes = AST.getFixIts(D);
@ -166,7 +163,7 @@ void CodeActionHandler::handleMethod(llvm::yaml::MappingNode *Params,
Commands +=
R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
R"('", "command": "clangd.applyFix", "arguments": [")" +
llvm::yaml::escape(CAP->textDocument.uri) +
llvm::yaml::escape(CAP->textDocument.uri.uri) +
R"(", [)" + Edits +
R"(]]},)";
}
@ -187,7 +184,7 @@ void CompletionHandler::handleMethod(llvm::yaml::MappingNode *Params,
return;
}
auto Items = AST.codeComplete(TDPP->textDocument.uri, TDPP->position.line,
auto Items = AST.codeComplete(TDPP->textDocument.uri.file, TDPP->position.line,
TDPP->position.character);
std::string Completions;
for (const auto &Item : Items) {

View File

@ -23,7 +23,14 @@ export function activate(context: vscode.ExtensionContext) {
const clientOptions: vscodelc.LanguageClientOptions = {
// Register the server for C/C++ files
documentSelector: ['c', 'cc', 'cpp', 'h', 'hh', 'hpp']
documentSelector: ['c', 'cc', 'cpp', 'h', 'hh', 'hpp'],
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 => undefined
}
};
const clangdClient = new vscodelc.LanguageClient('Clang Language Server', serverOptions, clientOptions);
@ -31,7 +38,8 @@ export function activate(context: vscode.ExtensionContext) {
function applyTextEdits(uri: string, edits: vscodelc.TextEdit[]) {
let textEditor = vscode.window.activeTextEditor;
if (textEditor && textEditor.document.uri.toString() === uri) {
// FIXME: vscode expects that uri will be percent encoded
if (textEditor && textEditor.document.uri.toString(true) === uri) {
textEditor.edit(mutator => {
for (const edit of edits) {
mutator.replace(vscodelc.Protocol2Code.asRange(edit.range), edit.newText);