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()));
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() {
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);
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.
} // 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
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.
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.
// 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.
///
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.
#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;
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 {
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") {
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);
struct TextDocumentItem {
/// The text document's URI.
- std::string uri;
+ URI uri;
/// The text document's language identifier.
std::string languageId;
/// 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:
//
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(
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.
// 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);
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));
}
// 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));
}
}
// 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));
}
// 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);
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"(]]},)";
}
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) {
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);
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);