From 6387d6878688f482dc2da9639353b61887c0203f Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Mon, 2 Nov 2026 02:53:10 +0100 Subject: [PATCH] Show the connection state in the neoirc-cli status bar (closes #229) The status bar had colour tags turned on, so tview read "[connected]" and "[disconnected]" as tags and dropped them. It dropped all or part of a nick, a target or a window name holding square brackets the same way. The status bar now has colour tags turned off. It wrote none of its own, so every bracket shows as it is. TestStartUp checks that the status bar shows [disconnected] at start-up. TestStatusBar checks [connected], and checks nicks and a /query target that hold brackets. Model: opus-5-5 --- internal/cli/app_internal_test.go | 87 ++++++++++++++++++++++++++++++- internal/cli/ui.go | 6 ++- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/internal/cli/app_internal_test.go b/internal/cli/app_internal_test.go index 6c5853b..11ddd9a 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,77 @@ 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)]", + }, + { + name: "nicks with brackets and symbols", + nick: "bob[|[]", + target: "x[^[]", + unread: testOtherNick, + want: " [connected] bob[|[] [1:x[^[]] x[^[] [Act: 2:bob(1)]", + }, + { + // /query takes any text as the target. + name: "query target with brackets", + nick: testNick, + target: "[:::y|z]", + unread: testOtherNick, + want: " [connected] erin [1:[:::y|z]] [:::y|z] [Act: 2:bob(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 +585,19 @@ func bufferState(ui *UI, name string) (int, int) { return lines, unread } +// statusBarText returns the text the status bar shows. +// GetText(true) leaves out whatever tview reads as a tag, as +// drawing does. +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..f702a8d 100644 --- a/internal/cli/ui.go +++ b/internal/cli/ui.go @@ -226,8 +226,10 @@ func (ui *UI) initMessages() { } func (ui *UI) initStatusBar() { - ui.statusBar = tview.NewTextView(). - SetDynamicColors(true) + // Colour tags stay off. With them on, tview reads text in + // square brackets, such as "[connected]" or part of a nick, + // as a tag and drops it. + ui.statusBar = tview.NewTextView() ui.statusBar.SetBackgroundColor(tcell.ColorNavy) ui.statusBar.SetTextColor(tcell.ColorWhite) }