Params.contentChanges[0].text);
}
+void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
+ Server.onFileEvent(Params);
+}
+
void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams Params,
JSONOutput &Out) {
Server.removeDocument(Params.textDocument.uri.file);
JSONOutput &Out) override;
void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID,
JSONOutput &Out) override;
+ void onFileEvent(const DidChangeWatchedFilesParams &Params) override;
std::vector<clang::tooling::Replacement>
getFixIts(StringRef File, const clangd::Diagnostic &D);
std::move(DeferredCancel));
return DoneFuture;
}
+
+void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
+ // FIXME: Do nothing for now. This will be used for indexing and potentially
+ // invalidating other caches.
+}
/// Waits until all requests to worker thread are finished and dumps AST for
/// \p File. \p File must be in the list of added documents.
std::string dumpAST(PathRef File);
+ /// Called when an event occurs for a watched file in the workspace.
+ void onFileEvent(const DidChangeWatchedFilesParams &Params);
private:
std::future<void>
return Result;
}
+llvm::Optional<FileEvent> FileEvent::parse(llvm::yaml::MappingNode *Params,
+ clangd::Logger &Logger) {
+ llvm::Optional<FileEvent> Result = FileEvent();
+ for (auto &NextKeyValue : *Params) {
+ // We have to consume the whole MappingNode because it doesn't support
+ // skipping and we want to be able to parse further valid events.
+ if (!Result)
+ continue;
+
+ auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+ if (!KeyString) {
+ Result.reset();
+ continue;
+ }
+
+ llvm::SmallString<10> KeyStorage;
+ StringRef KeyValue = KeyString->getValue(KeyStorage);
+ auto *Value =
+ dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getValue());
+ if (!Value) {
+ Result.reset();
+ continue;
+ }
+ llvm::SmallString<10> Storage;
+ if (KeyValue == "uri") {
+ Result->uri = URI::parse(Value);
+ } else if (KeyValue == "type") {
+ long long Val;
+ if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val)) {
+ Result.reset();
+ continue;
+ }
+ Result->type = static_cast<FileChangeType>(Val);
+ if (Result->type < FileChangeType::Created ||
+ Result->type > FileChangeType::Deleted)
+ Result.reset();
+ } else {
+ logIgnoredField(KeyValue, Logger);
+ }
+ }
+ return Result;
+}
+
+llvm::Optional<DidChangeWatchedFilesParams>
+DidChangeWatchedFilesParams::parse(llvm::yaml::MappingNode *Params,
+ clangd::Logger &Logger) {
+ DidChangeWatchedFilesParams Result;
+ for (auto &NextKeyValue : *Params) {
+ auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+ if (!KeyString)
+ return llvm::None;
+
+ llvm::SmallString<10> KeyStorage;
+ StringRef KeyValue = KeyString->getValue(KeyStorage);
+ auto *Value = NextKeyValue.getValue();
+
+ llvm::SmallString<10> Storage;
+ if (KeyValue == "changes") {
+ auto *Seq = dyn_cast<llvm::yaml::SequenceNode>(Value);
+ if (!Seq)
+ return llvm::None;
+ for (auto &Item : *Seq) {
+ auto *I = dyn_cast<llvm::yaml::MappingNode>(&Item);
+ if (!I)
+ return llvm::None;
+ auto Parsed = FileEvent::parse(I, Logger);
+ if (Parsed)
+ Result.changes.push_back(std::move(*Parsed));
+ else
+ Logger.log("Failed to decode a FileEvent.\n");
+ }
+ } else {
+ logIgnoredField(KeyValue, Logger);
+ }
+ }
+ return Result;
+}
+
llvm::Optional<TextDocumentContentChangeEvent>
TextDocumentContentChangeEvent::parse(llvm::yaml::MappingNode *Params,
clangd::Logger &Logger) {
parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger);
};
+enum class FileChangeType {
+ /// The file got created.
+ Created = 1,
+ /// The file got changed.
+ Changed = 2,
+ /// The file got deleted.
+ Deleted = 3
+};
+
+struct FileEvent {
+ /// The file's URI.
+ URI uri;
+ /// The change type.
+ FileChangeType type;
+
+ static llvm::Optional<FileEvent> parse(llvm::yaml::MappingNode *Params,
+ clangd::Logger &Logger);
+};
+
+struct DidChangeWatchedFilesParams {
+ /// The actual file events.
+ std::vector<FileEvent> changes;
+
+ static llvm::Optional<DidChangeWatchedFilesParams>
+ parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger);
+};
+
struct FormattingOptions {
/// Size of a tab in spaces.
int tabSize;
ProtocolCallbacks &Callbacks;
};
+struct WorkspaceDidChangeWatchedFilesHandler : Handler {
+ WorkspaceDidChangeWatchedFilesHandler(JSONOutput &Output,
+ ProtocolCallbacks &Callbacks)
+ : Handler(Output), Callbacks(Callbacks) {}
+
+ void handleNotification(llvm::yaml::MappingNode *Params) {
+ auto DCWFP = DidChangeWatchedFilesParams::parse(Params, Output);
+ if (!DCWFP) {
+ Output.log("Failed to decode DidChangeWatchedFilesParams.\n");
+ return;
+ }
+
+ Callbacks.onFileEvent(*DCWFP);
+ }
+
+private:
+ ProtocolCallbacks &Callbacks;
+};
+
} // namespace
void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher,
llvm::make_unique<GotoDefinitionHandler>(Out, Callbacks));
Dispatcher.registerHandler(
"textDocument/switchSourceHeader",
- llvm::make_unique<SwitchSourceHeaderHandler>(Out, Callbacks));
+ llvm::make_unique<SwitchSourceHeaderHandler>(Out, Callbacks));
+ Dispatcher.registerHandler(
+ "workspace/didChangeWatchedFiles",
+ llvm::make_unique<WorkspaceDidChangeWatchedFilesHandler>(Out, Callbacks));
}
JSONOutput &Out) = 0;
virtual void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID,
JSONOutput &Out) = 0;
+ virtual void onFileEvent(const DidChangeWatchedFilesParams &Params) = 0;
};
void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,
"type": "string"
},
"description": "Arguments for clangd server"
+ },
+ "clangd.syncFileEvents": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether or not to send file events to clangd (File created, changed or deleted). This can be disabled for performance consideration."
}
}
}
export function activate(context: vscode.ExtensionContext) {
const clangdPath = getConfig<string>('path');
const clangdArgs = getConfig<string[]>('arguments');
+ const syncFileEvents = getConfig<boolean>('syncFileEvents', true);
const serverOptions: vscodelc.ServerOptions = { command: clangdPath, args: clangdArgs };
+ const cppFileExtensions: string[] = ['cpp', 'c', 'cc', 'cxx', 'c++', 'm', 'mm', 'h', 'hh', 'hpp', 'hxx', 'inc'];
+ const cppFileExtensionsPattern = cppFileExtensions.join();
const clientOptions: vscodelc.LanguageClientOptions = {
// Register the server for C/C++ files
- documentSelector: ['c', 'cc', 'cpp', 'h', 'hh', 'hpp'],
+ documentSelector: cppFileExtensions,
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('**/*.{' + cppFileExtensionsPattern + '}')
}
};
--- /dev/null
+# RUN: clangd -run-synchronously < %s 2>&1 | FileCheck -check-prefix=STDERR %s\r
+# It is absolutely vital that this file has CRLF line endings.\r
+#\r
+# Test initialize request parameters with rootUri\r
+Content-Length: 143\r
+\r
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}}\r
+# CHECK: Content-Length: 466\r
+# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{\r
+# CHECK: "textDocumentSync": 1,\r
+# CHECK: "documentFormattingProvider": true,\r
+# CHECK: "documentRangeFormattingProvider": true,\r
+# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},\r
+# CHECK: "codeActionProvider": true,\r
+# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},\r
+# CHECK: "definitionProvider": true\r
+# CHECK: }}}\r
+#\r
+#Normal case\r
+Content-Length: 217\r
+\r
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file.cpp","type":1},{"uri":"file:///path/to/file2.cpp","type":2},{"uri":"file:///path/to/file3.cpp","type":3}]}}\r
+\r
+# Wrong event type, integer\r
+Content-Length: 173\r
+\r
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file2.cpp","type":0},{"uri":"file:///path/to/file3.cpp","type":4}]}}\r
+# STDERR: Failed to decode a FileEvent.\r
+# Wrong event type, string\r
+Content-Length: 132\r
+\r
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file2.cpp","type":"foo"}]}}\r
+# STDERR: Failed to decode a FileEvent.\r
+#Custom event field\r
+Content-Length: 143\r
+\r
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file2.cpp","type":1,"custom":"foo"}]}}\r
+# STDERR: Failed to decode a FileEvent.\r
+#Event field with object\r
+Content-Length: 140\r
+\r
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file2.cpp","type":{"foo":"bar"}}]}}\r
+# STDERR: Failed to decode a FileEvent.\r
+# Changes field with sequence but no object\r
+Content-Length: 86\r
+\r
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[""]}}\r
+# STDERR: Failed to decode DidChangeWatchedFilesParams.\r
+# Changes field with no sequence\r
+Content-Length: 84\r
+\r
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":""}}\r
+# STDERR: Failed to decode DidChangeWatchedFilesParams.\r
+# Custom field\r
+Content-Length: 86\r
+\r
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"custom":"foo"}}\r
+# STDERR: Ignored unknown field "custom"\r
+Content-Length: 44\r
+\r
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}\r