diff --git a/nixd/tools/nixd/meson.build b/nixd/tools/nixd/meson.build index 99c384737..1d35c1a98 100644 --- a/nixd/tools/nixd/meson.build +++ b/nixd/tools/nixd/meson.build @@ -1,6 +1,12 @@ nixd_next = executable('nixd', - 'src/Controller.cpp', + 'src/CodeAction.cpp', + 'src/Convert.cpp', + 'src/Diagnostics.cpp', + 'src/Hover.cpp', + 'src/LifeTime.cpp', 'src/Main.cpp', + 'src/Support.cpp', + 'src/TextDocumentSync.cpp', install: true, dependencies: [ nixd_lsp_server, nixf, llvm ] ) diff --git a/nixd/tools/nixd/src/CodeAction.cpp b/nixd/tools/nixd/src/CodeAction.cpp new file mode 100644 index 000000000..e39c57f4f --- /dev/null +++ b/nixd/tools/nixd/src/CodeAction.cpp @@ -0,0 +1,50 @@ +/// \file +/// \brief Implementation of [Code Action]. +/// [Code Action]: +/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction + +#include "Controller.h" + +namespace nixd { + +using namespace llvm::json; +using namespace lspserver; + +void Controller::onCodeAction(const lspserver::CodeActionParams &Params, + Callback> Reply) { + PathRef File = Params.textDocument.uri.file(); + Range Range = Params.range; + const std::vector &Diagnostics = TUs[File].diagnostics(); + std::vector Actions; + Actions.reserve(Diagnostics.size()); + for (const nixf::Diagnostic &D : Diagnostics) { + auto DRange = toLSPRange(D.range()); + if (!Range.overlap(DRange)) + continue; + + // Add fixes. + for (const nixf::Fix &F : D.fixes()) { + std::vector Edits; + Edits.reserve(F.edits().size()); + for (const nixf::TextEdit &TE : F.edits()) { + Edits.emplace_back(TextEdit{ + .range = toLSPRange(TE.oldRange()), + .newText = std::string(TE.newText()), + }); + } + using Changes = std::map>; + std::string FileURI = URIForFile::canonicalize(File, File).uri(); + WorkspaceEdit WE{.changes = Changes{ + {std::move(FileURI), std::move(Edits)}, + }}; + Actions.emplace_back(CodeAction{ + .title = F.message(), + .kind = std::string(CodeAction::QUICKFIX_KIND), + .edit = std::move(WE), + }); + } + } + Reply(std::move(Actions)); +} + +} // namespace nixd diff --git a/nixd/tools/nixd/src/Controller.cpp b/nixd/tools/nixd/src/Controller.cpp deleted file mode 100644 index f5ee9d837..000000000 --- a/nixd/tools/nixd/src/Controller.cpp +++ /dev/null @@ -1,330 +0,0 @@ -/// \file -/// \brief Controller. The process interacting with users. -#include "nixd-config.h" - -#include "nixf/Basic/Diagnostic.h" -#include "nixf/Basic/Nodes.h" -#include "nixf/Basic/Range.h" -#include "nixf/Parse/Parser.h" -#include "nixf/Sema/Lowering.h" - -#include "lspserver/DraftStore.h" -#include "lspserver/LSPServer.h" -#include "lspserver/Path.h" -#include "lspserver/Protocol.h" -#include "lspserver/SourceCode.h" - -#include - -#include - -namespace { - -using namespace lspserver; -using namespace llvm::json; - -Position toLSPPosition(const nixf::LexerCursor &P) { - return Position{static_cast(P.line()), static_cast(P.column())}; -} - -Range toLSPRange(const nixf::LexerCursorRange &R) { - return Range{toLSPPosition(R.lCur()), toLSPPosition(R.rCur())}; -} - -int getLSPSeverity(nixf::Diagnostic::DiagnosticKind Kind) { - switch (nixf::Diagnostic::severity(Kind)) { - case nixf::Diagnostic::DS_Fatal: - case nixf::Diagnostic::DS_Error: - return 1; - case nixf::Diagnostic::DS_Warning: - return 2; - } - assert(false && "Invalid severity"); - __builtin_unreachable(); -} - -llvm::SmallVector -toLSPTags(const std::vector &Tags) { - llvm::SmallVector Result; - Result.reserve(Tags.size()); - for (const nixf::DiagnosticTag &Tag : Tags) { - switch (Tag) { - case nixf::DiagnosticTag::Faded: - Result.emplace_back(DiagnosticTag::Unnecessary); - break; - case nixf::DiagnosticTag::Striked: - Result.emplace_back(DiagnosticTag::Deprecated); - break; - } - } - return Result; -} - -/// Holds analyzed information about a document. -/// -/// TU stands for "Translation Unit". -class NixTU { - std::vector Diagnostics; - std::unique_ptr AST; - -public: - NixTU() = default; - NixTU(std::vector Diagnostics, - std::unique_ptr AST) - : Diagnostics(std::move(Diagnostics)), AST(std::move(AST)) {} - - [[nodiscard]] const std::vector &diagnostics() const { - return Diagnostics; - } - - [[nodiscard]] const std::unique_ptr &ast() const { return AST; } -}; - -class Controller : public LSPServer { - DraftStore Store; - - llvm::unique_function - PublishDiagnostic; - - llvm::StringMap TUs; - - /// Action right after a document is added (including updates). - void actOnDocumentAdd(PathRef File, std::optional Version) { - auto Draft = Store.getDraft(File); - assert(Draft && "Added document is not in the store?"); - std::vector Diagnostics; - std::unique_ptr AST = - nixf::parse(*Draft->Contents, Diagnostics); - nixf::lower(AST.get(), *Draft->Contents, Diagnostics); - std::vector LSPDiags; - LSPDiags.reserve(Diagnostics.size()); - for (const nixf::Diagnostic &D : Diagnostics) { - // Format the message. - std::string Message = D.format(); - - // Add fix information. - if (!D.fixes().empty()) { - Message += " ("; - if (D.fixes().size() == 1) { - Message += "fix available"; - } else { - Message += std::to_string(D.fixes().size()); - Message += " fixes available"; - } - Message += ")"; - } - - Diagnostic &Diag = LSPDiags.emplace_back(Diagnostic{ - .range = toLSPRange(D.range()), - .severity = getLSPSeverity(D.kind()), - .code = D.sname(), - .source = "nixf", - .message = Message, - .tags = toLSPTags(D.tags()), - .relatedInformation = std::vector{}, - }); - - assert(Diag.relatedInformation && "Must be initialized"); - Diag.relatedInformation->reserve(D.notes().size()); - for (const nixf::Note &N : D.notes()) { - Diag.relatedInformation->emplace_back(DiagnosticRelatedInformation{ - .location = - Location{ - .uri = URIForFile::canonicalize(File, File), - .range = toLSPRange(N.range()), - }, - .message = N.format(), - }); - LSPDiags.emplace_back(Diagnostic{ - .range = toLSPRange(N.range()), - .severity = 4, - .code = N.sname(), - .source = "nixf", - .message = N.format(), - .tags = toLSPTags(N.tags()), - .relatedInformation = - std::vector{ - DiagnosticRelatedInformation{ - .location = - Location{ - .uri = URIForFile::canonicalize(File, File), - .range = Diag.range, - }, - .message = "original diagnostic", - }}, - }); - } - } - PublishDiagnostic({ - .uri = URIForFile::canonicalize(File, File), - .diagnostics = std::move(LSPDiags), - .version = Version, - }); - TUs[File] = NixTU{std::move(Diagnostics), std::move(AST)}; - } - - void removeDocument(PathRef File) { Store.removeDraft(File); } - - void onInitialize( // NOLINT(readability-convert-member-functions-to-static) - [[maybe_unused]] const InitializeParams &Params, Callback Reply) { - - Object ServerCaps{ - {{"textDocumentSync", - llvm::json::Object{ - {"openClose", true}, - {"change", (int)TextDocumentSyncKind::Incremental}, - {"save", true}, - }}, - { - "codeActionProvider", - Object{ - {"codeActionKinds", Array{CodeAction::QUICKFIX_KIND}}, - {"resolveProvider", false}, - }, - }, - {"hoverProvider", true}}, - }; - - Object Result{{ - {"serverInfo", - Object{ - {"name", "nixd"}, - {"version", NIXD_VERSION}, - }}, - {"capabilities", std::move(ServerCaps)}, - }}; - - Reply(std::move(Result)); - - PublishDiagnostic = mkOutNotifiction( - "textDocument/publishDiagnostics"); - } - void onInitialized([[maybe_unused]] const InitializedParams &Params) {} - - void onDocumentDidOpen(const DidOpenTextDocumentParams &Params) { - PathRef File = Params.textDocument.uri.file(); - const std::string &Contents = Params.textDocument.text; - std::optional Version = Params.textDocument.version; - Store.addDraft(File, DraftStore::encodeVersion(Version), Contents); - actOnDocumentAdd(File, Version); - } - - void onDocumentDidChange(const DidChangeTextDocumentParams &Params) { - PathRef File = Params.textDocument.uri.file(); - auto Code = Store.getDraft(File); - if (!Code) { - log("Trying to incrementally change non-added document: {0}", File); - return; - } - std::string NewCode(*Code->Contents); - for (const auto &Change : Params.contentChanges) { - if (auto Err = applyChange(NewCode, Change)) { - // If this fails, we are most likely going to be not in sync anymore - // with the client. It is better to remove the draft and let further - // operations fail rather than giving wrong results. - removeDocument(File); - elog("Failed to update {0}: {1}", File, std::move(Err)); - return; - } - } - std::optional Version = Params.textDocument.version; - Store.addDraft(File, DraftStore::encodeVersion(Version), NewCode); - actOnDocumentAdd(File, Version); - } - - void onDocumentDidClose(const DidCloseTextDocumentParams &Params) { - PathRef File = Params.textDocument.uri.file(); - removeDocument(File); - } - - void onCodeAction(const CodeActionParams &Params, - Callback> Reply) { - PathRef File = Params.textDocument.uri.file(); - Range Range = Params.range; - const std::vector &Diagnostics = TUs[File].diagnostics(); - std::vector Actions; - Actions.reserve(Diagnostics.size()); - for (const nixf::Diagnostic &D : Diagnostics) { - auto DRange = toLSPRange(D.range()); - if (!Range.overlap(DRange)) - continue; - - // Add fixes. - for (const nixf::Fix &F : D.fixes()) { - std::vector Edits; - Edits.reserve(F.edits().size()); - for (const nixf::TextEdit &TE : F.edits()) { - Edits.emplace_back(TextEdit{ - .range = toLSPRange(TE.oldRange()), - .newText = std::string(TE.newText()), - }); - } - using Changes = std::map>; - std::string FileURI = URIForFile::canonicalize(File, File).uri(); - WorkspaceEdit WE{.changes = Changes{ - {std::move(FileURI), std::move(Edits)}, - }}; - Actions.emplace_back(CodeAction{ - .title = F.message(), - .kind = std::string(CodeAction::QUICKFIX_KIND), - .edit = std::move(WE), - }); - } - } - Reply(std::move(Actions)); - } - - void onHover(const TextDocumentPositionParams &Params, - Callback> Reply) { - PathRef File = Params.textDocument.uri.file(); - const std::unique_ptr &AST = TUs[File].ast(); - nixf::Position Pos{Params.position.line, Params.position.character}; - const nixf::Node *N = AST->descend({Pos, Pos}); - if (!N) { - Reply(std::nullopt); - return; - } - std::string Name = N->name(); - Reply(Hover{ - .contents = - MarkupContent{ - .kind = MarkupKind::Markdown, - .value = "`" + Name + "`", - }, - .range = toLSPRange(N->range()), - }); - } - -public: - Controller(std::unique_ptr In, std::unique_ptr Out) - : LSPServer(std::move(In), std::move(Out)) { - - // Life Cycle - Registry.addMethod("initialize", this, &Controller::onInitialize); - Registry.addNotification("initialized", this, &Controller::onInitialized); - - // Text Document Synchronization - Registry.addNotification("textDocument/didOpen", this, - &Controller::onDocumentDidOpen); - Registry.addNotification("textDocument/didChange", this, - &Controller::onDocumentDidChange); - - Registry.addNotification("textDocument/didClose", this, - &Controller::onDocumentDidClose); - - // Language Features - Registry.addMethod("textDocument/codeAction", this, - &Controller::onCodeAction); - Registry.addMethod("textDocument/hover", this, &Controller::onHover); - } -}; - -} // namespace - -namespace nixd { -void runController(std::unique_ptr In, - std::unique_ptr Out) { - Controller C(std::move(In), std::move(Out)); - C.run(); -} -} // namespace nixd diff --git a/nixd/tools/nixd/src/Controller.h b/nixd/tools/nixd/src/Controller.h index b34108964..b30d1b407 100644 --- a/nixd/tools/nixd/src/Controller.h +++ b/nixd/tools/nixd/src/Controller.h @@ -1,15 +1,55 @@ #pragma once -#include "lspserver/Connection.h" +#include "Convert.h" +#include "NixTU.h" -#include +#include "lspserver/DraftStore.h" +#include "lspserver/LSPServer.h" namespace nixd { -/// \brief Run controller with input & output ports. -/// -/// The function will return after received "exit" -void runController(std::unique_ptr In, - std::unique_ptr Out); +class Controller : public lspserver::LSPServer { + lspserver::DraftStore Store; + + llvm::unique_function + PublishDiagnostic; + + llvm::StringMap TUs; + + /// Action right after a document is added (including updates). + void actOnDocumentAdd(lspserver::PathRef File, + std::optional Version); + + void removeDocument(lspserver::PathRef File) { Store.removeDraft(File); } + + void onInitialize( // NOLINT(readability-convert-member-functions-to-static) + [[maybe_unused]] const lspserver::InitializeParams &Params, + lspserver::Callback Reply); + + void + onInitialized([[maybe_unused]] const lspserver::InitializedParams &Params) {} + + void onDocumentDidOpen(const lspserver::DidOpenTextDocumentParams &Params); + + void + onDocumentDidChange(const lspserver::DidChangeTextDocumentParams &Params); + + void onDocumentDidClose(const lspserver::DidCloseTextDocumentParams &Params); + + void + onCodeAction(const lspserver::CodeActionParams &Params, + lspserver::Callback> Reply); + + void onHover(const lspserver::TextDocumentPositionParams &Params, + lspserver::Callback> Reply); + + void publishDiagnostics(lspserver::PathRef File, + std::optional Version, + const std::vector &Diagnostics); + +public: + Controller(std::unique_ptr In, + std::unique_ptr Out); +}; } // namespace nixd diff --git a/nixd/tools/nixd/src/Convert.cpp b/nixd/tools/nixd/src/Convert.cpp new file mode 100644 index 000000000..ad258e12d --- /dev/null +++ b/nixd/tools/nixd/src/Convert.cpp @@ -0,0 +1,45 @@ +#include "Convert.h" + +using namespace lspserver; + +namespace nixd { + +int getLSPSeverity(nixf::Diagnostic::DiagnosticKind Kind) { + switch (nixf::Diagnostic::severity(Kind)) { + case nixf::Diagnostic::DS_Fatal: + case nixf::Diagnostic::DS_Error: + return 1; + case nixf::Diagnostic::DS_Warning: + return 2; + } + assert(false && "Invalid severity"); + __builtin_unreachable(); +} + +lspserver::Position toLSPPosition(const nixf::LexerCursor &P) { + return lspserver::Position{static_cast(P.line()), + static_cast(P.column())}; +} + +lspserver::Range toLSPRange(const nixf::LexerCursorRange &R) { + return lspserver::Range{toLSPPosition(R.lCur()), toLSPPosition(R.rCur())}; +} + +llvm::SmallVector +toLSPTags(const std::vector &Tags) { + llvm::SmallVector Result; + Result.reserve(Tags.size()); + for (const nixf::DiagnosticTag &Tag : Tags) { + switch (Tag) { + case nixf::DiagnosticTag::Faded: + Result.emplace_back(DiagnosticTag::Unnecessary); + break; + case nixf::DiagnosticTag::Striked: + Result.emplace_back(DiagnosticTag::Deprecated); + break; + } + } + return Result; +} + +} // namespace nixd diff --git a/nixd/tools/nixd/src/Convert.h b/nixd/tools/nixd/src/Convert.h new file mode 100644 index 000000000..455659d1b --- /dev/null +++ b/nixd/tools/nixd/src/Convert.h @@ -0,0 +1,22 @@ +/// \file +/// \brief Convert between LSP and nixf types. + +#pragma once + +#include "nixf/Basic/Diagnostic.h" +#include "nixf/Basic/Range.h" + +#include "lspserver/Protocol.h" + +namespace nixd { + +lspserver::Position toLSPPosition(const nixf::LexerCursor &P); + +lspserver::Range toLSPRange(const nixf::LexerCursorRange &R); + +int getLSPSeverity(nixf::Diagnostic::DiagnosticKind Kind); + +llvm::SmallVector +toLSPTags(const std::vector &Tags); + +} // namespace nixd diff --git a/nixd/tools/nixd/src/Diagnostics.cpp b/nixd/tools/nixd/src/Diagnostics.cpp new file mode 100644 index 000000000..66683eabd --- /dev/null +++ b/nixd/tools/nixd/src/Diagnostics.cpp @@ -0,0 +1,82 @@ +/// \file +/// \brief Implementation of [PublishDiagnostics Notification]. +/// [PublishDiagnostics Notification]: +/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics + +#include "Controller.h" + +namespace nixd { + +using namespace llvm::json; +using namespace lspserver; + +void Controller::publishDiagnostics( + PathRef File, std::optional Version, + const std::vector &Diagnostics) { + std::vector LSPDiags; + LSPDiags.reserve(Diagnostics.size()); + for (const nixf::Diagnostic &D : Diagnostics) { + // Format the message. + std::string Message = D.format(); + + // Add fix information. + if (!D.fixes().empty()) { + Message += " ("; + if (D.fixes().size() == 1) { + Message += "fix available"; + } else { + Message += std::to_string(D.fixes().size()); + Message += " fixes available"; + } + Message += ")"; + } + + Diagnostic &Diag = LSPDiags.emplace_back(Diagnostic{ + .range = toLSPRange(D.range()), + .severity = getLSPSeverity(D.kind()), + .code = D.sname(), + .source = "nixf", + .message = Message, + .tags = toLSPTags(D.tags()), + .relatedInformation = std::vector{}, + }); + + assert(Diag.relatedInformation && "Must be initialized"); + Diag.relatedInformation->reserve(D.notes().size()); + for (const nixf::Note &N : D.notes()) { + Diag.relatedInformation->emplace_back(DiagnosticRelatedInformation{ + .location = + Location{ + .uri = URIForFile::canonicalize(File, File), + .range = toLSPRange(N.range()), + }, + .message = N.format(), + }); + LSPDiags.emplace_back(Diagnostic{ + .range = toLSPRange(N.range()), + .severity = 4, + .code = N.sname(), + .source = "nixf", + .message = N.format(), + .tags = toLSPTags(N.tags()), + .relatedInformation = + std::vector{ + DiagnosticRelatedInformation{ + .location = + Location{ + .uri = URIForFile::canonicalize(File, File), + .range = Diag.range, + }, + .message = "original diagnostic", + }}, + }); + } + } + PublishDiagnostic({ + .uri = URIForFile::canonicalize(File, File), + .diagnostics = std::move(LSPDiags), + .version = Version, + }); +} + +} // namespace nixd diff --git a/nixd/tools/nixd/src/Hover.cpp b/nixd/tools/nixd/src/Hover.cpp new file mode 100644 index 000000000..47047a230 --- /dev/null +++ b/nixd/tools/nixd/src/Hover.cpp @@ -0,0 +1,34 @@ +/// \file +/// \brief Implementation of [Hover Request]. +/// [Hover Request]: +/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover + +#include "Controller.h" + +namespace nixd { + +using namespace llvm::json; +using namespace lspserver; + +void Controller::onHover(const TextDocumentPositionParams &Params, + Callback> Reply) { + PathRef File = Params.textDocument.uri.file(); + const std::unique_ptr &AST = TUs[File].ast(); + nixf::Position Pos{Params.position.line, Params.position.character}; + const nixf::Node *N = AST->descend({Pos, Pos}); + if (!N) { + Reply(std::nullopt); + return; + } + std::string Name = N->name(); + Reply(Hover{ + .contents = + MarkupContent{ + .kind = MarkupKind::Markdown, + .value = "`" + Name + "`", + }, + .range = toLSPRange(N->range()), + }); +} + +} // namespace nixd diff --git a/nixd/tools/nixd/src/LifeTime.cpp b/nixd/tools/nixd/src/LifeTime.cpp new file mode 100644 index 000000000..607f05ce6 --- /dev/null +++ b/nixd/tools/nixd/src/LifeTime.cpp @@ -0,0 +1,52 @@ +/// \file +/// \brief Implementation of [Server Lifecycle]. +/// [Server Lifecycle]: +/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#lifeCycleMessages + +#include "nixd-config.h" + +#include "Controller.h" + +namespace nixd { + +using namespace llvm::json; +using namespace lspserver; + +void Controller:: + onInitialize( // NOLINT(readability-convert-member-functions-to-static) + [[maybe_unused]] const InitializeParams &Params, + Callback Reply) { + + Object ServerCaps{ + {{"textDocumentSync", + llvm::json::Object{ + {"openClose", true}, + {"change", (int)TextDocumentSyncKind::Incremental}, + {"save", true}, + }}, + { + "codeActionProvider", + Object{ + {"codeActionKinds", Array{CodeAction::QUICKFIX_KIND}}, + {"resolveProvider", false}, + }, + }, + {"hoverProvider", true}}, + }; + + Object Result{{ + {"serverInfo", + Object{ + {"name", "nixd"}, + {"version", NIXD_VERSION}, + }}, + {"capabilities", std::move(ServerCaps)}, + }}; + + Reply(std::move(Result)); + + PublishDiagnostic = mkOutNotifiction( + "textDocument/publishDiagnostics"); +} + +} // namespace nixd diff --git a/nixd/tools/nixd/src/Main.cpp b/nixd/tools/nixd/src/Main.cpp index f6bf99a39..9d008d8dc 100644 --- a/nixd/tools/nixd/src/Main.cpp +++ b/nixd/tools/nixd/src/Main.cpp @@ -77,7 +77,10 @@ int main(int argc, char *argv[]) { auto Out = std::make_unique(PrettyPrint); - nixd::runController(std::move(In), std::move(Out)); + auto Controller = + std::make_unique(std::move(In), std::move(Out)); + + Controller->run(); return 0; } diff --git a/nixd/tools/nixd/src/NixTU.h b/nixd/tools/nixd/src/NixTU.h new file mode 100644 index 000000000..7d1e040db --- /dev/null +++ b/nixd/tools/nixd/src/NixTU.h @@ -0,0 +1,28 @@ +#pragma once + +#include "nixf/Basic/Diagnostic.h" +#include "nixf/Basic/Nodes.h" + +namespace nixd { + +/// \brief Holds analyzed information about a document. +/// +/// TU stands for "Translation Unit". +class NixTU { + std::vector Diagnostics; + std::unique_ptr AST; + +public: + NixTU() = default; + NixTU(std::vector Diagnostics, + std::unique_ptr AST) + : Diagnostics(std::move(Diagnostics)), AST(std::move(AST)) {} + + [[nodiscard]] const std::vector &diagnostics() const { + return Diagnostics; + } + + [[nodiscard]] const std::unique_ptr &ast() const { return AST; } +}; + +} // namespace nixd diff --git a/nixd/tools/nixd/src/Support.cpp b/nixd/tools/nixd/src/Support.cpp new file mode 100644 index 000000000..5f4ee6763 --- /dev/null +++ b/nixd/tools/nixd/src/Support.cpp @@ -0,0 +1,48 @@ +#include "Controller.h" +#include "Convert.h" + +#include "nixf/Basic/Diagnostic.h" +#include "nixf/Basic/Nodes.h" +#include "nixf/Parse/Parser.h" +#include "nixf/Sema/Lowering.h" + +using namespace lspserver; +using namespace nixd; + +namespace nixd { + +void Controller::actOnDocumentAdd(PathRef File, + std::optional Version) { + auto Draft = Store.getDraft(File); + assert(Draft && "Added document is not in the store?"); + std::vector Diagnostics; + std::unique_ptr AST = nixf::parse(*Draft->Contents, Diagnostics); + nixf::lower(AST.get(), *Draft->Contents, Diagnostics); + publishDiagnostics(File, Version, Diagnostics); + TUs[File] = NixTU{std::move(Diagnostics), std::move(AST)}; +} + +Controller::Controller(std::unique_ptr In, + std::unique_ptr Out) + : LSPServer(std::move(In), std::move(Out)) { + + // Life Cycle + Registry.addMethod("initialize", this, &Controller::onInitialize); + Registry.addNotification("initialized", this, &Controller::onInitialized); + + // Text Document Synchronization + Registry.addNotification("textDocument/didOpen", this, + &Controller::onDocumentDidOpen); + Registry.addNotification("textDocument/didChange", this, + &Controller::onDocumentDidChange); + + Registry.addNotification("textDocument/didClose", this, + &Controller::onDocumentDidClose); + + // Language Features + Registry.addMethod("textDocument/codeAction", this, + &Controller::onCodeAction); + Registry.addMethod("textDocument/hover", this, &Controller::onHover); +} + +} // namespace nixd diff --git a/nixd/tools/nixd/src/TextDocumentSync.cpp b/nixd/tools/nixd/src/TextDocumentSync.cpp new file mode 100644 index 000000000..c5770c2d0 --- /dev/null +++ b/nixd/tools/nixd/src/TextDocumentSync.cpp @@ -0,0 +1,51 @@ +/// \file +/// \brief Implementation of the [text document +/// sync](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_synchronization). +#include "Controller.h" + +#include "lspserver/SourceCode.h" + +namespace nixd { + +using namespace llvm::json; +using namespace lspserver; + +void Controller::onDocumentDidOpen( + const lspserver::DidOpenTextDocumentParams &Params) { + PathRef File = Params.textDocument.uri.file(); + const std::string &Contents = Params.textDocument.text; + std::optional Version = Params.textDocument.version; + Store.addDraft(File, DraftStore::encodeVersion(Version), Contents); + actOnDocumentAdd(File, Version); +} + +void Controller::onDocumentDidChange( + const DidChangeTextDocumentParams &Params) { + PathRef File = Params.textDocument.uri.file(); + auto Code = Store.getDraft(File); + if (!Code) { + log("Trying to incrementally change non-added document: {0}", File); + return; + } + std::string NewCode(*Code->Contents); + for (const auto &Change : Params.contentChanges) { + if (auto Err = applyChange(NewCode, Change)) { + // If this fails, we are most likely going to be not in sync anymore + // with the client. It is better to remove the draft and let further + // operations fail rather than giving wrong results. + removeDocument(File); + elog("Failed to update {0}: {1}", File, std::move(Err)); + return; + } + } + std::optional Version = Params.textDocument.version; + Store.addDraft(File, DraftStore::encodeVersion(Version), NewCode); + actOnDocumentAdd(File, Version); +} + +void Controller::onDocumentDidClose(const DidCloseTextDocumentParams &Params) { + PathRef File = Params.textDocument.uri.file(); + removeDocument(File); +} + +} // namespace nixd