Index: clang-tools-extra/trunk/clangd/ASTManager.h =================================================================== --- clang-tools-extra/trunk/clangd/ASTManager.h +++ clang-tools-extra/trunk/clangd/ASTManager.h @@ -34,7 +34,7 @@ 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 @@ // 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 - 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. /// Index: clang-tools-extra/trunk/clangd/ASTManager.cpp =================================================================== --- clang-tools-extra/trunk/clangd/ASTManager.cpp +++ clang-tools-extra/trunk/clangd/ASTManager.cpp @@ -28,7 +28,6 @@ std::vector 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 @@ 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 @@ ClangWorker.join(); } -void ASTManager::onDocumentAdd(StringRef Uri) { +void ASTManager::onDocumentAdd(StringRef File) { if (RunSynchronously) { - parseFileAndPublishDiagnostics(Uri); + parseFileAndPublishDiagnostics(File); return; } std::lock_guard 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 -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 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 @@ 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 @@ } // namespace std::vector -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 @@ std::vector Items; CompletionItemsCollector Collector(&Items, CCO); std::lock_guard 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 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. Index: clang-tools-extra/trunk/clangd/DocumentStore.h =================================================================== --- clang-tools-extra/trunk/clangd/DocumentStore.h +++ clang-tools-extra/trunk/clangd/DocumentStore.h @@ -22,40 +22,40 @@ 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 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 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 Guard(DocsMutex); - return Docs.lookup(Uri); + return Docs.lookup(File); } /// Add a listener. Does not take ownership. Index: clang-tools-extra/trunk/clangd/Protocol.h =================================================================== --- clang-tools-extra/trunk/clangd/Protocol.h +++ clang-tools-extra/trunk/clangd/Protocol.h @@ -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 parse(llvm::yaml::MappingNode *Params); @@ -90,7 +101,7 @@ 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 @@ /// this completion. Edits must not overlap with the main edit nor with /// themselves. std::vector additionalTextEdits; - + // TODO(krasimir): The following optional fields defined by the language // server protocol are unsupported: // Index: clang-tools-extra/trunk/clangd/Protocol.cpp =================================================================== --- clang-tools-extra/trunk/clangd/Protocol.cpp +++ clang-tools-extra/trunk/clangd/Protocol.cpp @@ -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::parse(llvm::yaml::MappingNode *Params) { TextDocumentIdentifier Result; @@ -34,9 +70,8 @@ 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 @@ 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") { Index: clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp =================================================================== --- clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp +++ clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp @@ -21,7 +21,7 @@ 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 @@ 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 @@ // 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 @@ 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 @@ // 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 @@ } // 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 @@ // 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 Fixes = AST.getFixIts(D); @@ -166,7 +163,7 @@ 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 @@ 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) { Index: clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts =================================================================== --- clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts +++ clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts @@ -23,7 +23,14 @@ 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 @@ 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);