| //===- LSPServer.cpp - MLIR Language Server -------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "LSPServer.h" |
| #include "MLIRServer.h" |
| #include "Protocol.h" |
| #include "mlir/Tools/lsp-server-support/Logging.h" |
| #include "mlir/Tools/lsp-server-support/Transport.h" |
| #include "llvm/ADT/FunctionExtras.h" |
| #include "llvm/ADT/StringMap.h" |
| #include <optional> |
| |
| #define DEBUG_TYPE "mlir-lsp-server" |
| |
| using namespace mlir; |
| using namespace mlir::lsp; |
| |
| //===----------------------------------------------------------------------===// |
| // LSPServer |
| //===----------------------------------------------------------------------===// |
| |
| namespace { |
| struct LSPServer { |
| LSPServer(MLIRServer &server) : server(server) {} |
| |
| //===--------------------------------------------------------------------===// |
| // Initialization |
| |
| void onInitialize(const InitializeParams ¶ms, |
| Callback<llvm::json::Value> reply); |
| void onInitialized(const InitializedParams ¶ms); |
| void onShutdown(const NoParams ¶ms, Callback<std::nullptr_t> reply); |
| |
| //===--------------------------------------------------------------------===// |
| // Document Change |
| |
| void onDocumentDidOpen(const DidOpenTextDocumentParams ¶ms); |
| void onDocumentDidClose(const DidCloseTextDocumentParams ¶ms); |
| void onDocumentDidChange(const DidChangeTextDocumentParams ¶ms); |
| |
| //===--------------------------------------------------------------------===// |
| // Definitions and References |
| |
| void onGoToDefinition(const TextDocumentPositionParams ¶ms, |
| Callback<std::vector<Location>> reply); |
| void onReference(const ReferenceParams ¶ms, |
| Callback<std::vector<Location>> reply); |
| |
| //===--------------------------------------------------------------------===// |
| // Hover |
| |
| void onHover(const TextDocumentPositionParams ¶ms, |
| Callback<std::optional<Hover>> reply); |
| |
| //===--------------------------------------------------------------------===// |
| // Document Symbols |
| |
| void onDocumentSymbol(const DocumentSymbolParams ¶ms, |
| Callback<std::vector<DocumentSymbol>> reply); |
| |
| //===--------------------------------------------------------------------===// |
| // Code Completion |
| |
| void onCompletion(const CompletionParams ¶ms, |
| Callback<CompletionList> reply); |
| |
| //===--------------------------------------------------------------------===// |
| // Code Action |
| |
| void onCodeAction(const CodeActionParams ¶ms, |
| Callback<llvm::json::Value> reply); |
| |
| //===--------------------------------------------------------------------===// |
| // Bytecode |
| |
| void onConvertFromBytecode(const MLIRConvertBytecodeParams ¶ms, |
| Callback<MLIRConvertBytecodeResult> reply); |
| void onConvertToBytecode(const MLIRConvertBytecodeParams ¶ms, |
| Callback<MLIRConvertBytecodeResult> reply); |
| |
| //===--------------------------------------------------------------------===// |
| // Fields |
| //===--------------------------------------------------------------------===// |
| |
| MLIRServer &server; |
| |
| /// An outgoing notification used to send diagnostics to the client when they |
| /// are ready to be processed. |
| OutgoingNotification<PublishDiagnosticsParams> publishDiagnostics; |
| |
| /// Used to indicate that the 'shutdown' request was received from the |
| /// Language Server client. |
| bool shutdownRequestReceived = false; |
| }; |
| } // namespace |
| |
| //===----------------------------------------------------------------------===// |
| // Initialization |
| //===----------------------------------------------------------------------===// |
| |
| void LSPServer::onInitialize(const InitializeParams ¶ms, |
| Callback<llvm::json::Value> reply) { |
| // Send a response with the capabilities of this server. |
| llvm::json::Object serverCaps{ |
| {"textDocumentSync", |
| llvm::json::Object{ |
| {"openClose", true}, |
| {"change", (int)TextDocumentSyncKind::Full}, |
| {"save", true}, |
| }}, |
| {"completionProvider", |
| llvm::json::Object{ |
| {"allCommitCharacters", |
| { |
| "\t", |
| ";", |
| ",", |
| ".", |
| "=", |
| }}, |
| {"resolveProvider", false}, |
| {"triggerCharacters", |
| {".", "%", "^", "!", "#", "(", ",", "<", ":", "[", " ", "\"", "/"}}, |
| }}, |
| {"definitionProvider", true}, |
| {"referencesProvider", true}, |
| {"hoverProvider", true}, |
| |
| // For now we only support documenting symbols when the client supports |
| // hierarchical symbols. |
| {"documentSymbolProvider", |
| params.capabilities.hierarchicalDocumentSymbol}, |
| }; |
| |
| // Per LSP, codeActionProvider can be either boolean or CodeActionOptions. |
| // CodeActionOptions is only valid if the client supports action literal |
| // via textDocument.codeAction.codeActionLiteralSupport. |
| serverCaps["codeActionProvider"] = |
| params.capabilities.codeActionStructure |
| ? llvm::json::Object{{"codeActionKinds", |
| {CodeAction::kQuickFix, CodeAction::kRefactor, |
| CodeAction::kInfo}}} |
| : llvm::json::Value(true); |
| |
| llvm::json::Object result{ |
| {{"serverInfo", |
| llvm::json::Object{{"name", "mlir-lsp-server"}, {"version", "0.0.0"}}}, |
| {"capabilities", std::move(serverCaps)}}}; |
| reply(std::move(result)); |
| } |
| void LSPServer::onInitialized(const InitializedParams &) {} |
| void LSPServer::onShutdown(const NoParams &, Callback<std::nullptr_t> reply) { |
| shutdownRequestReceived = true; |
| reply(nullptr); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Document Change |
| //===----------------------------------------------------------------------===// |
| |
| void LSPServer::onDocumentDidOpen(const DidOpenTextDocumentParams ¶ms) { |
| PublishDiagnosticsParams diagParams(params.textDocument.uri, |
| params.textDocument.version); |
| server.addOrUpdateDocument(params.textDocument.uri, params.textDocument.text, |
| params.textDocument.version, |
| diagParams.diagnostics); |
| |
| // Publish any recorded diagnostics. |
| publishDiagnostics(diagParams); |
| } |
| void LSPServer::onDocumentDidClose(const DidCloseTextDocumentParams ¶ms) { |
| std::optional<int64_t> version = |
| server.removeDocument(params.textDocument.uri); |
| if (!version) |
| return; |
| |
| // Empty out the diagnostics shown for this document. This will clear out |
| // anything currently displayed by the client for this document (e.g. in the |
| // "Problems" pane of VSCode). |
| publishDiagnostics( |
| PublishDiagnosticsParams(params.textDocument.uri, *version)); |
| } |
| void LSPServer::onDocumentDidChange(const DidChangeTextDocumentParams ¶ms) { |
| // TODO: We currently only support full document updates, we should refactor |
| // to avoid this. |
| if (params.contentChanges.size() != 1) |
| return; |
| PublishDiagnosticsParams diagParams(params.textDocument.uri, |
| params.textDocument.version); |
| server.addOrUpdateDocument( |
| params.textDocument.uri, params.contentChanges.front().text, |
| params.textDocument.version, diagParams.diagnostics); |
| |
| // Publish any recorded diagnostics. |
| publishDiagnostics(diagParams); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Definitions and References |
| //===----------------------------------------------------------------------===// |
| |
| void LSPServer::onGoToDefinition(const TextDocumentPositionParams ¶ms, |
| Callback<std::vector<Location>> reply) { |
| std::vector<Location> locations; |
| server.getLocationsOf(params.textDocument.uri, params.position, locations); |
| reply(std::move(locations)); |
| } |
| |
| void LSPServer::onReference(const ReferenceParams ¶ms, |
| Callback<std::vector<Location>> reply) { |
| std::vector<Location> locations; |
| server.findReferencesOf(params.textDocument.uri, params.position, locations); |
| reply(std::move(locations)); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Hover |
| //===----------------------------------------------------------------------===// |
| |
| void LSPServer::onHover(const TextDocumentPositionParams ¶ms, |
| Callback<std::optional<Hover>> reply) { |
| reply(server.findHover(params.textDocument.uri, params.position)); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Document Symbols |
| //===----------------------------------------------------------------------===// |
| |
| void LSPServer::onDocumentSymbol(const DocumentSymbolParams ¶ms, |
| Callback<std::vector<DocumentSymbol>> reply) { |
| std::vector<DocumentSymbol> symbols; |
| server.findDocumentSymbols(params.textDocument.uri, symbols); |
| reply(std::move(symbols)); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Code Completion |
| //===----------------------------------------------------------------------===// |
| |
| void LSPServer::onCompletion(const CompletionParams ¶ms, |
| Callback<CompletionList> reply) { |
| reply(server.getCodeCompletion(params.textDocument.uri, params.position)); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Code Action |
| //===----------------------------------------------------------------------===// |
| |
| void LSPServer::onCodeAction(const CodeActionParams ¶ms, |
| Callback<llvm::json::Value> reply) { |
| URIForFile uri = params.textDocument.uri; |
| |
| // Check whether a particular CodeActionKind is included in the response. |
| auto isKindAllowed = [only(params.context.only)](StringRef kind) { |
| if (only.empty()) |
| return true; |
| return llvm::any_of(only, [&](StringRef base) { |
| return kind.consume_front(base) && |
| (kind.empty() || kind.starts_with(".")); |
| }); |
| }; |
| |
| // We provide a code action for fixes on the specified diagnostics. |
| std::vector<CodeAction> actions; |
| if (isKindAllowed(CodeAction::kQuickFix)) |
| server.getCodeActions(uri, params.range.start, params.context, actions); |
| reply(std::move(actions)); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Bytecode |
| //===----------------------------------------------------------------------===// |
| |
| void LSPServer::onConvertFromBytecode( |
| const MLIRConvertBytecodeParams ¶ms, |
| Callback<MLIRConvertBytecodeResult> reply) { |
| reply(server.convertFromBytecode(params.uri)); |
| } |
| |
| void LSPServer::onConvertToBytecode(const MLIRConvertBytecodeParams ¶ms, |
| Callback<MLIRConvertBytecodeResult> reply) { |
| reply(server.convertToBytecode(params.uri)); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Entry point |
| //===----------------------------------------------------------------------===// |
| |
| LogicalResult lsp::runMlirLSPServer(MLIRServer &server, |
| JSONTransport &transport) { |
| LSPServer lspServer(server); |
| MessageHandler messageHandler(transport); |
| |
| // Initialization |
| messageHandler.method("initialize", &lspServer, &LSPServer::onInitialize); |
| messageHandler.notification("initialized", &lspServer, |
| &LSPServer::onInitialized); |
| messageHandler.method("shutdown", &lspServer, &LSPServer::onShutdown); |
| |
| // Document Changes |
| messageHandler.notification("textDocument/didOpen", &lspServer, |
| &LSPServer::onDocumentDidOpen); |
| messageHandler.notification("textDocument/didClose", &lspServer, |
| &LSPServer::onDocumentDidClose); |
| messageHandler.notification("textDocument/didChange", &lspServer, |
| &LSPServer::onDocumentDidChange); |
| |
| // Definitions and References |
| messageHandler.method("textDocument/definition", &lspServer, |
| &LSPServer::onGoToDefinition); |
| messageHandler.method("textDocument/references", &lspServer, |
| &LSPServer::onReference); |
| |
| // Hover |
| messageHandler.method("textDocument/hover", &lspServer, &LSPServer::onHover); |
| |
| // Document Symbols |
| messageHandler.method("textDocument/documentSymbol", &lspServer, |
| &LSPServer::onDocumentSymbol); |
| |
| // Code Completion |
| messageHandler.method("textDocument/completion", &lspServer, |
| &LSPServer::onCompletion); |
| |
| // Code Action |
| messageHandler.method("textDocument/codeAction", &lspServer, |
| &LSPServer::onCodeAction); |
| |
| // Bytecode |
| messageHandler.method("mlir/convertFromBytecode", &lspServer, |
| &LSPServer::onConvertFromBytecode); |
| messageHandler.method("mlir/convertToBytecode", &lspServer, |
| &LSPServer::onConvertToBytecode); |
| |
| // Diagnostics |
| lspServer.publishDiagnostics = |
| messageHandler.outgoingNotification<PublishDiagnosticsParams>( |
| "textDocument/publishDiagnostics"); |
| |
| // Run the main loop of the transport. |
| LogicalResult result = success(); |
| if (llvm::Error error = transport.run(messageHandler)) { |
| Logger::error("Transport error: {0}", error); |
| llvm::consumeError(std::move(error)); |
| result = failure(); |
| } else { |
| result = success(lspServer.shutdownRequestReceived); |
| } |
| return result; |
| } |