Skip to content

Commit 5011737

Browse files
committedApr 7, 2017
[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
1 parent 6e79529 commit 5011737

File tree

7 files changed

+105
-60
lines changed

7 files changed

+105
-60
lines changed
 

‎clang-tools-extra/clangd/ASTManager.cpp

+15-21
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ getRemappedFiles(const DocumentStore &Docs) {
2828
std::vector<ASTUnit::RemappedFile> RemappedFiles;
2929
for (const auto &P : Docs.getAllDocuments()) {
3030
StringRef FileName = P.first;
31-
FileName.consume_front("file://");
3231
RemappedFiles.push_back(ASTUnit::RemappedFile(
3332
FileName,
3433
llvm::MemoryBuffer::getMemBufferCopy(P.second, FileName).release()));
@@ -142,7 +141,7 @@ void ASTManager::parseFileAndPublishDiagnostics(StringRef File) {
142141
Diagnostics.pop_back(); // Drop trailing comma.
143142
Output.writeMessage(
144143
R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
145-
File + R"(","diagnostics":[)" + Diagnostics + R"(]}})");
144+
URI::fromFile(File).uri + R"(","diagnostics":[)" + Diagnostics + R"(]}})");
146145
}
147146

148147
ASTManager::~ASTManager() {
@@ -155,51 +154,48 @@ ASTManager::~ASTManager() {
155154
ClangWorker.join();
156155
}
157156

158-
void ASTManager::onDocumentAdd(StringRef Uri) {
157+
void ASTManager::onDocumentAdd(StringRef File) {
159158
if (RunSynchronously) {
160-
parseFileAndPublishDiagnostics(Uri);
159+
parseFileAndPublishDiagnostics(File);
161160
return;
162161
}
163162
std::lock_guard<std::mutex> Guard(RequestLock);
164163
// Currently we discard all pending requests and just enqueue the latest one.
165164
RequestQueue.clear();
166-
RequestQueue.push_back(Uri);
165+
RequestQueue.push_back(File);
167166
ClangRequestCV.notify_one();
168167
}
169168

170169
tooling::CompilationDatabase *
171-
ASTManager::getOrCreateCompilationDatabaseForFile(StringRef Uri) {
172-
auto &I = CompilationDatabases[Uri];
170+
ASTManager::getOrCreateCompilationDatabaseForFile(StringRef File) {
171+
auto &I = CompilationDatabases[File];
173172
if (I)
174173
return I.get();
175174

176-
Uri.consume_front("file://");
177-
178175
std::string Error;
179-
I = tooling::CompilationDatabase::autoDetectFromSource(Uri, Error);
176+
I = tooling::CompilationDatabase::autoDetectFromSource(File, Error);
180177
Output.log("Failed to load compilation database: " + Twine(Error) + "\n");
181178
return I.get();
182179
}
183180

184181
std::unique_ptr<clang::ASTUnit>
185-
ASTManager::createASTUnitForFile(StringRef Uri, const DocumentStore &Docs) {
182+
ASTManager::createASTUnitForFile(StringRef File, const DocumentStore &Docs) {
186183
tooling::CompilationDatabase *CDB =
187-
getOrCreateCompilationDatabaseForFile(Uri);
184+
getOrCreateCompilationDatabaseForFile(File);
188185

189-
Uri.consume_front("file://");
190186
std::vector<tooling::CompileCommand> Commands;
191187

192188
if (CDB) {
193-
Commands = CDB->getCompileCommands(Uri);
189+
Commands = CDB->getCompileCommands(File);
194190
// chdir. This is thread hostile.
195191
if (!Commands.empty())
196192
llvm::sys::fs::set_current_path(Commands.front().Directory);
197193
}
198194
if (Commands.empty()) {
199195
// Add a fake command line if we know nothing.
200196
Commands.push_back(tooling::CompileCommand(
201-
llvm::sys::path::parent_path(Uri), llvm::sys::path::filename(Uri),
202-
{"clang", "-fsyntax-only", Uri.str()}, ""));
197+
llvm::sys::path::parent_path(File), llvm::sys::path::filename(File),
198+
{"clang", "-fsyntax-only", File.str()}, ""));
203199
}
204200

205201
// Inject the resource dir.
@@ -278,7 +274,7 @@ class CompletionItemsCollector : public CodeCompleteConsumer {
278274
} // namespace
279275

280276
std::vector<CompletionItem>
281-
ASTManager::codeComplete(StringRef Uri, unsigned Line, unsigned Column) {
277+
ASTManager::codeComplete(StringRef File, unsigned Line, unsigned Column) {
282278
CodeCompleteOptions CCO;
283279
CCO.IncludeBriefComments = 1;
284280
// This is where code completion stores dirty buffers. Need to free after
@@ -290,15 +286,13 @@ ASTManager::codeComplete(StringRef Uri, unsigned Line, unsigned Column) {
290286
std::vector<CompletionItem> Items;
291287
CompletionItemsCollector Collector(&Items, CCO);
292288
std::lock_guard<std::mutex> Guard(ASTLock);
293-
auto &Unit = ASTs[Uri];
289+
auto &Unit = ASTs[File];
294290
if (!Unit)
295-
Unit = createASTUnitForFile(Uri, this->Store);
291+
Unit = createASTUnitForFile(File, this->Store);
296292
if (!Unit)
297293
return {};
298294
IntrusiveRefCntPtr<SourceManager> SourceMgr(
299295
new SourceManager(*DiagEngine, Unit->getFileManager()));
300-
StringRef File(Uri);
301-
File.consume_front("file://");
302296
// CodeComplete seems to require fresh LangOptions.
303297
LangOptions LangOpts = Unit->getLangOpts();
304298
// The language server protocol uses zero-based line and column numbers.

‎clang-tools-extra/clangd/ASTManager.h

+6-6
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class ASTManager : public DocumentStoreListener {
3434
ASTManager(JSONOutput &Output, DocumentStore &Store, bool RunSynchronously);
3535
~ASTManager() override;
3636

37-
void onDocumentAdd(StringRef Uri) override;
37+
void onDocumentAdd(StringRef File) override;
3838
// FIXME: Implement onDocumentRemove
3939

4040
/// Get code completions at a specified \p Line and \p Column in \p File.
@@ -61,21 +61,21 @@ class ASTManager : public DocumentStoreListener {
6161
// asynchronously.
6262
bool RunSynchronously;
6363

64-
/// Loads a compilation database for URI. May return nullptr if it fails. The
64+
/// Loads a compilation database for File. May return nullptr if it fails. The
6565
/// database is cached for subsequent accesses.
6666
clang::tooling::CompilationDatabase *
67-
getOrCreateCompilationDatabaseForFile(StringRef Uri);
68-
// Creates a new ASTUnit for the document at Uri.
67+
getOrCreateCompilationDatabaseForFile(StringRef File);
68+
// Creates a new ASTUnit for the document at File.
6969
// FIXME: This calls chdir internally, which is thread unsafe.
7070
std::unique_ptr<clang::ASTUnit>
71-
createASTUnitForFile(StringRef Uri, const DocumentStore &Docs);
71+
createASTUnitForFile(StringRef File, const DocumentStore &Docs);
7272

7373
void runWorker();
7474
void parseFileAndPublishDiagnostics(StringRef File);
7575

7676
/// Clang objects.
7777

78-
/// A map from Uri-s to ASTUnit-s. Guarded by \c ASTLock. ASTUnit-s are used
78+
/// A map from File-s to ASTUnit-s. Guarded by \c ASTLock. ASTUnit-s are used
7979
/// for generating diagnostics and fix-it-s asynchronously by the worker
8080
/// thread and synchronously for code completion.
8181
///

‎clang-tools-extra/clangd/DocumentStore.h

+11-11
Original file line numberDiff line numberDiff line change
@@ -22,40 +22,40 @@ class DocumentStore;
2222

2323
struct DocumentStoreListener {
2424
virtual ~DocumentStoreListener() = default;
25-
virtual void onDocumentAdd(StringRef Uri) {}
26-
virtual void onDocumentRemove(StringRef Uri) {}
25+
virtual void onDocumentAdd(StringRef File) {}
26+
virtual void onDocumentRemove(StringRef File) {}
2727
};
2828

29-
/// A container for files opened in a workspace, addressed by URI. The contents
29+
/// A container for files opened in a workspace, addressed by File. The contents
3030
/// are owned by the DocumentStore.
3131
class DocumentStore {
3232
public:
3333
/// Add a document to the store. Overwrites existing contents.
34-
void addDocument(StringRef Uri, StringRef Text) {
34+
void addDocument(StringRef File, StringRef Text) {
3535
{
3636
std::lock_guard<std::mutex> Guard(DocsMutex);
37-
Docs[Uri] = Text;
37+
Docs[File] = Text;
3838
}
3939
for (const auto &Listener : Listeners)
40-
Listener->onDocumentAdd(Uri);
40+
Listener->onDocumentAdd(File);
4141
}
4242
/// Delete a document from the store.
43-
void removeDocument(StringRef Uri) {
43+
void removeDocument(StringRef File) {
4444
{
4545
std::lock_guard<std::mutex> Guard(DocsMutex);
46-
Docs.erase(Uri);
46+
Docs.erase(File);
4747
}
4848
for (const auto &Listener : Listeners)
49-
Listener->onDocumentRemove(Uri);
49+
Listener->onDocumentRemove(File);
5050
}
5151
/// Retrieve a document from the store. Empty string if it's unknown.
5252
///
5353
/// This function is thread-safe. It returns a copy to avoid handing out
5454
/// references to unguarded data.
55-
std::string getDocument(StringRef Uri) const {
55+
std::string getDocument(StringRef File) const {
5656
// FIXME: This could be a reader lock.
5757
std::lock_guard<std::mutex> Guard(DocsMutex);
58-
return Docs.lookup(Uri);
58+
return Docs.lookup(File);
5959
}
6060

6161
/// Add a listener. Does not take ownership.

‎clang-tools-extra/clangd/Protocol.cpp

+38-3
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,44 @@
1717
#include "llvm/ADT/SmallString.h"
1818
#include "llvm/Support/Format.h"
1919
#include "llvm/Support/raw_ostream.h"
20+
#include "llvm/Support/Path.h"
2021
using namespace clang::clangd;
2122

23+
24+
URI URI::fromUri(llvm::StringRef uri) {
25+
URI Result;
26+
Result.uri = uri;
27+
uri.consume_front("file://");
28+
// For Windows paths e.g. /X:
29+
if (uri.size() > 2 && uri[0] == '/' && uri[2] == ':')
30+
uri.consume_front("/");
31+
// Make sure that file paths are in native separators
32+
Result.file = llvm::sys::path::convert_to_slash(uri);
33+
return Result;
34+
}
35+
36+
URI URI::fromFile(llvm::StringRef file) {
37+
using namespace llvm::sys;
38+
URI Result;
39+
Result.file = file;
40+
Result.uri = "file://";
41+
// For Windows paths e.g. X:
42+
if (file.size() > 1 && file[1] == ':')
43+
Result.uri += "/";
44+
// Make sure that uri paths are with posix separators
45+
Result.uri += path::convert_to_slash(file, path::Style::posix);
46+
return Result;
47+
}
48+
49+
URI URI::parse(llvm::yaml::ScalarNode *Param) {
50+
llvm::SmallString<10> Storage;
51+
return URI::fromUri(Param->getValue(Storage));
52+
}
53+
54+
std::string URI::unparse(const URI &U) {
55+
return U.uri;
56+
}
57+
2258
llvm::Optional<TextDocumentIdentifier>
2359
TextDocumentIdentifier::parse(llvm::yaml::MappingNode *Params) {
2460
TextDocumentIdentifier Result;
@@ -34,9 +70,8 @@ TextDocumentIdentifier::parse(llvm::yaml::MappingNode *Params) {
3470
if (!Value)
3571
return llvm::None;
3672

37-
llvm::SmallString<10> Storage;
3873
if (KeyValue == "uri") {
39-
Result.uri = Value->getValue(Storage);
74+
Result.uri = URI::parse(Value);
4075
} else if (KeyValue == "version") {
4176
// FIXME: parse version, but only for VersionedTextDocumentIdentifiers.
4277
} else {
@@ -142,7 +177,7 @@ TextDocumentItem::parse(llvm::yaml::MappingNode *Params) {
142177

143178
llvm::SmallString<10> Storage;
144179
if (KeyValue == "uri") {
145-
Result.uri = Value->getValue(Storage);
180+
Result.uri = URI::parse(Value);
146181
} else if (KeyValue == "languageId") {
147182
Result.languageId = Value->getValue(Storage);
148183
} else if (KeyValue == "version") {

‎clang-tools-extra/clangd/Protocol.h

+14-3
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,20 @@
2929
namespace clang {
3030
namespace clangd {
3131

32+
struct URI {
33+
std::string uri;
34+
std::string file;
35+
36+
static URI fromUri(llvm::StringRef uri);
37+
static URI fromFile(llvm::StringRef file);
38+
39+
static URI parse(llvm::yaml::ScalarNode *Param);
40+
static std::string unparse(const URI &U);
41+
};
42+
3243
struct TextDocumentIdentifier {
3344
/// The text document's URI.
34-
std::string uri;
45+
URI uri;
3546

3647
static llvm::Optional<TextDocumentIdentifier>
3748
parse(llvm::yaml::MappingNode *Params);
@@ -90,7 +101,7 @@ struct TextEdit {
90101

91102
struct TextDocumentItem {
92103
/// The text document's URI.
93-
std::string uri;
104+
URI uri;
94105

95106
/// The text document's language identifier.
96107
std::string languageId;
@@ -328,7 +339,7 @@ struct CompletionItem {
328339
/// this completion. Edits must not overlap with the main edit nor with
329340
/// themselves.
330341
std::vector<TextEdit> additionalTextEdits;
331-
342+
332343
// TODO(krasimir): The following optional fields defined by the language
333344
// server protocol are unsupported:
334345
//

‎clang-tools-extra/clangd/ProtocolHandlers.cpp

+11-14
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ void TextDocumentDidOpenHandler::handleNotification(
2121
Output.log("Failed to decode DidOpenTextDocumentParams!\n");
2222
return;
2323
}
24-
Store.addDocument(DOTDP->textDocument.uri, DOTDP->textDocument.text);
24+
Store.addDocument(DOTDP->textDocument.uri.file, DOTDP->textDocument.text);
2525
}
2626

2727
void TextDocumentDidChangeHandler::handleNotification(
@@ -32,7 +32,7 @@ void TextDocumentDidChangeHandler::handleNotification(
3232
return;
3333
}
3434
// We only support full syncing right now.
35-
Store.addDocument(DCTDP->textDocument.uri, DCTDP->contentChanges[0].text);
35+
Store.addDocument(DCTDP->textDocument.uri.file, DCTDP->contentChanges[0].text);
3636
}
3737

3838
/// Turn a [line, column] pair into an offset in Code.
@@ -83,9 +83,6 @@ static std::string formatCode(StringRef Code, StringRef Filename,
8383
// Call clang-format.
8484
// FIXME: Don't ignore style.
8585
format::FormatStyle Style = format::getLLVMStyle();
86-
// On windows FileManager doesn't like file://. Just strip it, clang-format
87-
// doesn't need it.
88-
Filename.consume_front("file://");
8986
tooling::Replacements Replacements =
9087
format::reformat(Style, Code, Ranges, Filename);
9188

@@ -102,12 +99,12 @@ void TextDocumentRangeFormattingHandler::handleMethod(
10299
return;
103100
}
104101

105-
std::string Code = Store.getDocument(DRFP->textDocument.uri);
102+
std::string Code = Store.getDocument(DRFP->textDocument.uri.file);
106103

107104
size_t Begin = positionToOffset(Code, DRFP->range.start);
108105
size_t Len = positionToOffset(Code, DRFP->range.end) - Begin;
109106

110-
writeMessage(formatCode(Code, DRFP->textDocument.uri,
107+
writeMessage(formatCode(Code, DRFP->textDocument.uri.file,
111108
{clang::tooling::Range(Begin, Len)}, ID));
112109
}
113110

@@ -121,14 +118,14 @@ void TextDocumentOnTypeFormattingHandler::handleMethod(
121118

122119
// Look for the previous opening brace from the character position and format
123120
// starting from there.
124-
std::string Code = Store.getDocument(DOTFP->textDocument.uri);
121+
std::string Code = Store.getDocument(DOTFP->textDocument.uri.file);
125122
size_t CursorPos = positionToOffset(Code, DOTFP->position);
126123
size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos);
127124
if (PreviousLBracePos == StringRef::npos)
128125
PreviousLBracePos = CursorPos;
129126
size_t Len = 1 + CursorPos - PreviousLBracePos;
130127

131-
writeMessage(formatCode(Code, DOTFP->textDocument.uri,
128+
writeMessage(formatCode(Code, DOTFP->textDocument.uri.file,
132129
{clang::tooling::Range(PreviousLBracePos, Len)}, ID));
133130
}
134131

@@ -141,8 +138,8 @@ void TextDocumentFormattingHandler::handleMethod(
141138
}
142139

143140
// Format everything.
144-
std::string Code = Store.getDocument(DFP->textDocument.uri);
145-
writeMessage(formatCode(Code, DFP->textDocument.uri,
141+
std::string Code = Store.getDocument(DFP->textDocument.uri.file);
142+
writeMessage(formatCode(Code, DFP->textDocument.uri.file,
146143
{clang::tooling::Range(0, Code.size())}, ID));
147144
}
148145

@@ -156,7 +153,7 @@ void CodeActionHandler::handleMethod(llvm::yaml::MappingNode *Params,
156153

157154
// We provide a code action for each diagnostic at the requested location
158155
// which has FixIts available.
159-
std::string Code = AST.getStore().getDocument(CAP->textDocument.uri);
156+
std::string Code = AST.getStore().getDocument(CAP->textDocument.uri.file);
160157
std::string Commands;
161158
for (Diagnostic &D : CAP->context.diagnostics) {
162159
std::vector<clang::tooling::Replacement> Fixes = AST.getFixIts(D);
@@ -166,7 +163,7 @@ void CodeActionHandler::handleMethod(llvm::yaml::MappingNode *Params,
166163
Commands +=
167164
R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
168165
R"('", "command": "clangd.applyFix", "arguments": [")" +
169-
llvm::yaml::escape(CAP->textDocument.uri) +
166+
llvm::yaml::escape(CAP->textDocument.uri.uri) +
170167
R"(", [)" + Edits +
171168
R"(]]},)";
172169
}
@@ -187,7 +184,7 @@ void CompletionHandler::handleMethod(llvm::yaml::MappingNode *Params,
187184
return;
188185
}
189186

190-
auto Items = AST.codeComplete(TDPP->textDocument.uri, TDPP->position.line,
187+
auto Items = AST.codeComplete(TDPP->textDocument.uri.file, TDPP->position.line,
191188
TDPP->position.character);
192189
std::string Completions;
193190
for (const auto &Item : Items) {

‎clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,23 @@ export function activate(context: vscode.ExtensionContext) {
2323

2424
const clientOptions: vscodelc.LanguageClientOptions = {
2525
// Register the server for C/C++ files
26-
documentSelector: ['c', 'cc', 'cpp', 'h', 'hh', 'hpp']
26+
documentSelector: ['c', 'cc', 'cpp', 'h', 'hh', 'hpp'],
27+
uriConverters: {
28+
// FIXME: by default the URI sent over the protocol will be percent encoded (see rfc3986#section-2.1)
29+
// the "workaround" below disables temporarily the encoding until decoding
30+
// is implemented properly in clangd
31+
code2Protocol: (uri: vscode.Uri) : string => uri.toString(true),
32+
protocol2Code: (uri: string) : vscode.Uri => undefined
33+
}
2734
};
2835

2936
const clangdClient = new vscodelc.LanguageClient('Clang Language Server', serverOptions, clientOptions);
3037

3138
function applyTextEdits(uri: string, edits: vscodelc.TextEdit[]) {
3239
let textEditor = vscode.window.activeTextEditor;
3340

34-
if (textEditor && textEditor.document.uri.toString() === uri) {
41+
// FIXME: vscode expects that uri will be percent encoded
42+
if (textEditor && textEditor.document.uri.toString(true) === uri) {
3543
textEditor.edit(mutator => {
3644
for (const edit of edits) {
3745
mutator.replace(vscodelc.Protocol2Code.asRange(edit.range), edit.newText);

0 commit comments

Comments
 (0)
Please sign in to comment.