From 2a6441cd539027baa0898a2e04925a411cddafb5 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Thu, 6 Aug 2026 14:09:33 +0200 Subject: [PATCH] kate: add connection state tracking and auto-recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a heartbeat timer (5s interval) that pings the 9P server with a cheap read. If the server goes down, the status bar turns red showing 'disconnected'. When it comes back, the plugin reconnects and re-selects the agent for the current project. Status bar now shows connection state: - Red '✗ disconnected' — server unreachable - Gray '○ no agent' — connected but no agent selected - Green '● kate/ID' — connected with active agent --- kate/ollie_kate.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++-- kate/ollie_kate.h | 4 ++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/kate/ollie_kate.cpp b/kate/ollie_kate.cpp index 61a78b0..3ff83a2 100644 --- a/kate/ollie_kate.cpp +++ b/kate/ollie_kate.cpp @@ -60,12 +60,19 @@ OllieKateView::OllieKateView(OllieKatePlugin *plugin, KTextEditor::MainWindow *m , m_plugin(plugin) , m_mainWindow(mainWindow) , m_9p(new Lib9pClient(this)) + , m_heartbeatTimer(new QTimer(this)) { // Connect to 9P server - if (!m_9p->connectDefault()) { + m_connected = m_9p->connectDefault(); + if (!m_connected) { qWarning() << "Ollie: failed to connect to 9P server:" << m_9p->lastError(); } + // Heartbeat timer — detect server death and reconnect + m_heartbeatTimer->setInterval(5000); + connect(m_heartbeatTimer, &QTimer::timeout, this, &OllieKateView::heartbeat); + m_heartbeatTimer->start(); + // Status bar indicator if (auto *win = qobject_cast(m_mainWindow->window())) { m_statusLabel = new QLabel(); @@ -120,6 +127,7 @@ OllieKateView::OllieKateView(OllieKatePlugin *plugin, KTextEditor::MainWindow *m OllieKateView::~OllieKateView() { + m_heartbeatTimer->stop(); delete m_ghost; if (m_statusLabel) { delete m_statusLabel; @@ -229,10 +237,46 @@ QString OllieKateView::agentPath() const void OllieKateView::updateStatusLabel() { if (!m_statusLabel) return; - if (m_agentId.isEmpty()) { + if (!m_connected) { + m_statusLabel->setText(QStringLiteral("✗ ollie: disconnected")); + m_statusLabel->setStyleSheet(QStringLiteral("color: red; padding: 0px 4px;")); + } else if (m_agentId.isEmpty()) { m_statusLabel->setText(QStringLiteral("○ ollie: no agent")); + m_statusLabel->setStyleSheet(QStringLiteral("color: gray; padding: 0px 4px;")); } else { m_statusLabel->setText(QStringLiteral("● ollie: kate/") + m_agentId); + m_statusLabel->setStyleSheet(QStringLiteral("color: green; padding: 0px 4px;")); + } +} + +// --- Connection Recovery --- + +void OllieKateView::heartbeat() +{ + // Try a cheap read to verify daemon is alive + if (!m_9p->isConnected()) { + if (!m_9p->connectDefault()) { + if (m_connected) { + m_connected = false; + updateStatusLabel(); + } + return; + } + } + + QByteArray result = m_9p->read(QStringLiteral("help")); + bool alive = !result.isEmpty(); + + if (alive && !m_connected) { + // Recovered — re-establish state + m_connected = true; + selectAgentForProject(); + updateStatusLabel(); + } else if (!alive && m_connected) { + // Lost connection + m_connected = false; + m_9p->disconnect(); + updateStatusLabel(); } } diff --git a/kate/ollie_kate.h b/kate/ollie_kate.h index aa31c6b..c0199fc 100644 --- a/kate/ollie_kate.h +++ b/kate/ollie_kate.h @@ -12,6 +12,7 @@ #include #include #include +#include #include "lib9pclient.h" @@ -60,6 +61,7 @@ protected: private slots: void onProjectChanged(); + void heartbeat(); private: // Actions @@ -89,7 +91,9 @@ private: QString m_sessionId; QString m_agentId; + bool m_connected = false; + QTimer *m_heartbeatTimer; QLabel *m_statusLabel = nullptr; QPointer m_ghost; bool m_ghostPrefix = false;