From d1cb9f80445332f8e7aee8457c5976e233b039b1 Mon Sep 17 00:00:00 2001 From: clawbot Date: Mon, 2 Nov 2026 01:12:11 +0000 Subject: [PATCH] Show the connection state in the neoirc-cli status bar (closes #229) The status bar has colour tags turned on, so tview read "[connected]" and "[disconnected]" as colour names and dropped them. It dropped the bracketed part of a nick such as "erin[away]" the same way. renderStatusBar now passes the whole status line through tview.Escape, so every bracket shows as it is. TestStartUp checks that the status bar shows [disconnected] at start-up. TestStatusBar checks [connected] with plain nicks and with nicks that hold brackets. Model: opus-5-5 --- internal/cli/app_internal_test.go | 71 ++++++++++++++++++++++++++++++- internal/cli/ui.go | 7 ++- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/internal/cli/app_internal_test.go b/internal/cli/app_internal_test.go index 6c5853b..3759f55 100644 --- a/internal/cli/app_internal_test.go +++ b/internal/cli/app_internal_test.go @@ -362,7 +362,8 @@ func TestLinesKeepOrder(t *testing.T) { // TestStartUp runs the client's start-up path on a simulation // screen. It fails unless the event loop starts and the status // window gets two lines: first the one that holds "Welcome to -// neoirc-cli", then the /connect hint. +// neoirc-cli", then the /connect hint. The status bar must show +// [disconnected]. func TestStartUp(t *testing.T) { t.Parallel() @@ -429,6 +430,62 @@ func TestStartUp(t *testing.T) { if !strings.Contains(lines[1], "/connect ") { t.Errorf("second status line is %q, want the /connect hint", lines[1]) } + + // run sets the status bar before it adds the welcome lines. + status := statusBarText(ui) + if !strings.Contains(status, "[disconnected]") { + t.Errorf("status bar is %q, want it to show [disconnected]", status) + } +} + +// TestStatusBar checks that the status bar shows [connected], and +// shows a nick, target or window name with square brackets as it +// is. +func TestStatusBar(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + nick string + target string + unread string + want string + }{ + { + name: "plain names", + nick: testNick, + target: testChannel, + unread: testOtherNick, + want: " [connected] erin [1:#chan] #chan [Act: 2:bob(1)]", + }, + { + name: "nicks with brackets", + nick: "erin[away]", + target: "bob[m]", + unread: "dave[afk]", + want: " [connected] erin[away] [1:bob[m]] bob[m] [Act: 2:dave[afk](1)]", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + ui := NewUI() + runUI(t, ui) + + // Window 1, the one shown, is the target's. Window 2 + // gets one unread line. + ui.SwitchToBuffer(test.target) + ui.AddLine(test.unread, testText) + ui.SetStatus(test.nick, test.target, "connected") + + got := statusBarText(ui) + if got != test.want { + t.Fatalf("status bar is %q, want %q", got, test.want) + } + }) + } } // TestOwnEventFromOtherClient checks that a message, notice, @@ -513,6 +570,18 @@ func bufferState(ui *UI, name string) (int, int) { return lines, unread } +// statusBarText returns the text the status bar shows, without +// its colour tags. +func statusBarText(ui *UI) string { + var text string + + ui.app.QueueUpdate(func() { + text = ui.statusBar.GetText(true) + }) + + return text +} + // handledLines makes app.ui pass each typed line to // app.handleInput, as Run does, and returns a channel that // receives each line once it has been handled. diff --git a/internal/cli/ui.go b/internal/cli/ui.go index 563df0d..2edb78f 100644 --- a/internal/cli/ui.go +++ b/internal/cli/ui.go @@ -332,10 +332,13 @@ func (ui *UI) renderStatusBar( ui.statusBar.Clear() - _, _ = fmt.Fprintf(ui.statusBar, + // The status bar has colour tags turned on, so tview would read + // "[connected]", or a name in square brackets, as a colour tag + // and drop it. Escape makes it show every bracket as it is. + _, _ = fmt.Fprint(ui.statusBar, tview.Escape(fmt.Sprintf( " [%s] %s %s %s%s", connStatus, nick, bufInfo, target, unread, - ) + ))) } func (ui *UI) getOrCreateBuffer(name string) *Buffer {