#pragma once // Step 90: LSP client core // // Minimal JSON-RPC LSP client with injectable transport. #include #include #include #include #include class LSPTransport { public: virtual ~LSPTransport() = default; virtual void send(const std::string& msg) = 0; virtual bool receive(std::string& out) = 0; virtual bool isOpen() const = 0; virtual void close() = 0; }; class LSPClient { public: explicit LSPClient(std::shared_ptr transport) : transport_(std::move(transport)) {} struct Position { int line = 0; int character = 0; }; struct Range { Position start; Position end; }; struct Diagnostic { std::string uri; Range range; int severity = 0; std::string message; std::string source; }; void initialize(const std::string& rootUri, const std::string& clientName, int processId) { nlohmann::json params; params["processId"] = processId; params["rootUri"] = rootUri; params["clientInfo"] = { {"name", clientName}, {"version", "0.1"} }; params["capabilities"] = nlohmann::json::object(); sendRequest("initialize", params); sendNotification("initialized", nlohmann::json::object()); } void shutdown() { sendRequest("shutdown", nlohmann::json::object()); sendNotification("exit", nlohmann::json::object()); if (transport_) transport_->close(); } void didOpen(const std::string& uri, const std::string& languageId, const std::string& text, int version) { nlohmann::json params; params["textDocument"] = { {"uri", uri}, {"languageId", languageId}, {"version", version}, {"text", text} }; sendNotification("textDocument/didOpen", params); } void didChange(const std::string& uri, const std::string& text, int version) { nlohmann::json params; params["textDocument"] = { {"uri", uri}, {"version", version} }; params["contentChanges"] = nlohmann::json::array({{ {"text", text} }}); sendNotification("textDocument/didChange", params); } void didSave(const std::string& uri, const std::string& text = std::string()) { nlohmann::json params; params["textDocument"] = {{"uri", uri}}; if (!text.empty()) params["text"] = text; sendNotification("textDocument/didSave", params); } void handleMessage(const std::string& msg) { nlohmann::json j; try { j = nlohmann::json::parse(msg); } catch (...) { return; } if (!j.contains("method")) return; if (j["method"].get() != "textDocument/publishDiagnostics") return; if (!j.contains("params")) return; const auto& params = j["params"]; if (!params.contains("uri") || !params.contains("diagnostics")) return; std::string uri = params["uri"].get(); std::vector parsed; for (const auto& diag : params["diagnostics"]) { Diagnostic d; d.uri = uri; if (diag.contains("range")) { const auto& range = diag["range"]; if (range.contains("start")) { d.range.start.line = range["start"].value("line", 0); d.range.start.character = range["start"].value("character", 0); } if (range.contains("end")) { d.range.end.line = range["end"].value("line", 0); d.range.end.character = range["end"].value("character", 0); } } d.severity = diag.value("severity", 0); d.message = diag.value("message", ""); d.source = diag.value("source", ""); parsed.push_back(std::move(d)); } diagnosticsByUri_[uri] = std::move(parsed); } std::vector getDiagnostics() const { std::vector out; for (const auto& kv : diagnosticsByUri_) { out.insert(out.end(), kv.second.begin(), kv.second.end()); } return out; } std::vector getDiagnosticsForUri(const std::string& uri) const { auto it = diagnosticsByUri_.find(uri); if (it == diagnosticsByUri_.end()) return {}; return it->second; } void sendNotification(const std::string& method, const nlohmann::json& params) { nlohmann::json msg; msg["jsonrpc"] = "2.0"; msg["method"] = method; msg["params"] = params; sendMessage(msg); } int sendRequest(const std::string& method, const nlohmann::json& params) { nlohmann::json msg; msg["jsonrpc"] = "2.0"; msg["id"] = nextId_++; msg["method"] = method; msg["params"] = params; sendMessage(msg); return msg["id"].get(); } private: void sendMessage(const nlohmann::json& msg) { if (!transport_) return; std::string body = msg.dump(); std::string header = "Content-Length: " + std::to_string(body.size()) + "\r\n\r\n"; transport_->send(header + body); } std::shared_ptr transport_; int nextId_ = 1; std::unordered_map> diagnosticsByUri_; };