Show neoirc-cli's own messages, joins and parts from the server's events (closes #186)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
neoirc-cli wrote its own line when it sent a message, joined or parted, and ignored the server's event for its own nick, except a JOIN, so a join showed twice and what the session did from another client never showed. The HTTP API queues these events to every client of the session, so the CLI now writes no line of its own when it sends and shows the event as it shows anyone else's. A direct message the user sent goes in the recipient's buffer. The user's own part does not mark the channel unread, as the user has just left it. Model: opus-5-5
This commit is contained in:
+21
-49
@@ -96,20 +96,7 @@ func (a *App) handleInput(text string) {
|
|||||||
a.ui.AddStatus(
|
a.ui.AddStatus(
|
||||||
"[red]Send error: " + err.Error(),
|
"[red]Send error: " + err.Error(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp := time.Now().Format(timeFormat)
|
|
||||||
|
|
||||||
a.mu.Lock()
|
|
||||||
nick := a.nick
|
|
||||||
a.mu.Unlock()
|
|
||||||
|
|
||||||
a.ui.AddLine(target, fmt.Sprintf(
|
|
||||||
"[gray]%s [green]<%s>[white] %s",
|
|
||||||
timestamp, nick, text,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) handleCommand(text string) {
|
func (a *App) handleCommand(text string) {
|
||||||
@@ -301,9 +288,6 @@ func (a *App) cmdJoin(channel string) {
|
|||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
|
|
||||||
a.ui.SwitchToBuffer(channel)
|
a.ui.SwitchToBuffer(channel)
|
||||||
a.ui.AddLine(channel,
|
|
||||||
"[yellow]*** Joined "+channel,
|
|
||||||
)
|
|
||||||
a.ui.SetStatus(nick, channel, "connected")
|
a.ui.SetStatus(nick, channel, "connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,10 +322,6 @@ func (a *App) cmdPart(channel string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
a.ui.AddLine(channel,
|
|
||||||
"[yellow]*** Left "+channel,
|
|
||||||
)
|
|
||||||
|
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
if a.target == channel {
|
if a.target == channel {
|
||||||
a.target = ""
|
a.target = ""
|
||||||
@@ -368,7 +348,6 @@ func (a *App) cmdMsg(args string) {
|
|||||||
|
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
connected := a.connected
|
connected := a.connected
|
||||||
nick := a.nick
|
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
|
|
||||||
if !connected {
|
if !connected {
|
||||||
@@ -386,16 +365,7 @@ func (a *App) cmdMsg(args string) {
|
|||||||
a.ui.AddStatus(fmt.Sprintf(
|
a.ui.AddStatus(fmt.Sprintf(
|
||||||
"[red]Send failed: %v", err,
|
"[red]Send failed: %v", err,
|
||||||
))
|
))
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp := time.Now().Format(timeFormat)
|
|
||||||
|
|
||||||
a.ui.AddLine(target, fmt.Sprintf(
|
|
||||||
"[gray]%s [green]<%s>[white] %s",
|
|
||||||
timestamp, nick, text,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) cmdQuery(nick string) {
|
func (a *App) cmdQuery(nick string) {
|
||||||
@@ -785,12 +755,10 @@ func (a *App) handlePrivmsgEvent(
|
|||||||
lines := msg.BodyLines()
|
lines := msg.BodyLines()
|
||||||
text := strings.Join(lines, " ")
|
text := strings.Join(lines, " ")
|
||||||
|
|
||||||
if msg.From == myNick {
|
// A direct message shows in the buffer of the other user:
|
||||||
return
|
// the recipient of one the user sent, else the sender.
|
||||||
}
|
|
||||||
|
|
||||||
target := msg.To
|
target := msg.To
|
||||||
if !strings.HasPrefix(target, "#") {
|
if !strings.HasPrefix(target, "#") && msg.From != myNick {
|
||||||
target = msg.From
|
target = msg.From
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -813,31 +781,35 @@ func (a *App) handleJoinEvent(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlePartEvent shows another user's part. The server
|
// handlePartEvent shows a part in the channel's buffer. The
|
||||||
// also sends the user's own part back to it, which cmdPart
|
// user's own part does not mark the channel unread: the user
|
||||||
// has already shown; showing it again would add a second
|
// has just left it.
|
||||||
// line and mark the channel just left as unread.
|
|
||||||
func (a *App) handlePartEvent(
|
func (a *App) handlePartEvent(
|
||||||
msg *api.Message, timestamp, myNick string,
|
msg *api.Message, timestamp, myNick string,
|
||||||
) {
|
) {
|
||||||
if msg.To == "" || msg.From == myNick {
|
if msg.To == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := msg.BodyLines()
|
lines := msg.BodyLines()
|
||||||
reason := strings.Join(lines, " ")
|
reason := strings.Join(lines, " ")
|
||||||
|
|
||||||
|
line := fmt.Sprintf(
|
||||||
|
"[gray]%s [yellow]*** %s has left %s",
|
||||||
|
timestamp, msg.From, msg.To,
|
||||||
|
)
|
||||||
|
|
||||||
if reason != "" {
|
if reason != "" {
|
||||||
a.ui.AddLine(msg.To, fmt.Sprintf(
|
line += " (" + reason + ")"
|
||||||
"[gray]%s [yellow]*** %s has left %s (%s)",
|
|
||||||
timestamp, msg.From, msg.To, reason,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
a.ui.AddLine(msg.To, fmt.Sprintf(
|
|
||||||
"[gray]%s [yellow]*** %s has left %s",
|
|
||||||
timestamp, msg.From, msg.To,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if msg.From == myNick {
|
||||||
|
a.ui.AddLineAsRead(msg.To, line)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.ui.AddLine(msg.To, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) handleQuitEvent(
|
func (a *App) handleQuitEvent(
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
@@ -8,6 +10,15 @@ import (
|
|||||||
"sneak.berlin/go/neoirc/pkg/irc"
|
"sneak.berlin/go/neoirc/pkg/irc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The user's nick, a channel it is in, another user's nick and
|
||||||
|
// a message text, for the tests below.
|
||||||
|
const (
|
||||||
|
testNick = "erin"
|
||||||
|
testChannel = "#chan"
|
||||||
|
testOtherNick = "bob"
|
||||||
|
testText = "hello"
|
||||||
|
)
|
||||||
|
|
||||||
func TestMemberPrefix(t *testing.T) {
|
func TestMemberPrefix(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -53,48 +64,190 @@ func TestMemberPrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestPartEvent checks that a PART event adds a line to the
|
// TestPartEvent checks that a PART event adds a line to the
|
||||||
// channel's buffer and marks it unread, except for the
|
// channel's buffer and marks it unread, except that the
|
||||||
// user's own part, which cmdPart has already shown.
|
// user's own part does not mark it unread.
|
||||||
func TestPartEvent(t *testing.T) {
|
func TestPartEvent(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
from string
|
from string
|
||||||
wantLines int
|
|
||||||
wantUnread int
|
wantUnread int
|
||||||
}{
|
}{
|
||||||
{name: "own part", from: "erin", wantLines: 0, wantUnread: 0},
|
{name: "own part", from: testNick, wantUnread: 0},
|
||||||
{name: "other member", from: "bob", wantLines: 1, wantUnread: 1},
|
{name: "other member", from: testOtherNick, wantUnread: 1},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
app := &App{ui: NewUI(), nick: "erin"}
|
app := &App{ui: NewUI(), nick: testNick}
|
||||||
runUI(t, app.ui)
|
runUI(t, app.ui)
|
||||||
|
|
||||||
app.handleServerMessage(&api.Message{
|
app.handleServerMessage(&api.Message{
|
||||||
Command: irc.CmdPart, From: test.from, To: "#chan",
|
Command: irc.CmdPart, From: test.from, To: testChannel,
|
||||||
})
|
})
|
||||||
|
|
||||||
var lines, unread int
|
lines, unread := bufferState(app.ui, testChannel)
|
||||||
|
if lines != 1 || unread != test.wantUnread {
|
||||||
app.ui.app.QueueUpdate(func() {
|
t.Fatalf("got %d lines and %d unread, want 1 and %d",
|
||||||
buf := app.ui.getOrCreateBuffer("#chan")
|
lines, unread, test.wantUnread)
|
||||||
lines = len(buf.Lines)
|
|
||||||
unread = buf.Unread
|
|
||||||
})
|
|
||||||
|
|
||||||
if lines != test.wantLines || unread != test.wantUnread {
|
|
||||||
t.Fatalf("got %d lines and %d unread, want %d and %d",
|
|
||||||
lines, unread, test.wantLines, test.wantUnread)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestOwnCommandShownOnce checks that a message, join or
|
||||||
|
// part made from neoirc-cli shows as one line in the
|
||||||
|
// channel's buffer once the server's event for it arrives,
|
||||||
|
// and does not mark the channel unread.
|
||||||
|
func TestOwnCommandShownOnce(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
send func(app *App)
|
||||||
|
event api.Message
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "message",
|
||||||
|
send: func(app *App) { app.handleInput(testText) },
|
||||||
|
event: api.Message{
|
||||||
|
Command: irc.CmdPrivmsg, From: testNick, To: testChannel,
|
||||||
|
Body: []string{testText},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "join",
|
||||||
|
send: func(app *App) { app.cmdJoin(testChannel) },
|
||||||
|
event: api.Message{
|
||||||
|
Command: irc.CmdJoin, From: testNick, To: testChannel,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "part",
|
||||||
|
send: func(app *App) { app.cmdPart(testChannel) },
|
||||||
|
event: api.Message{
|
||||||
|
Command: irc.CmdPart, From: testNick, To: testChannel,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// The server accepts every command and answers nothing.
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(
|
||||||
|
func(http.ResponseWriter, *http.Request) {},
|
||||||
|
))
|
||||||
|
t.Cleanup(server.Close)
|
||||||
|
|
||||||
|
app := &App{
|
||||||
|
ui: NewUI(),
|
||||||
|
client: api.NewClient(server.URL),
|
||||||
|
nick: testNick,
|
||||||
|
target: testChannel,
|
||||||
|
connected: true,
|
||||||
|
}
|
||||||
|
runUI(t, app.ui)
|
||||||
|
app.ui.SwitchToBuffer(testChannel)
|
||||||
|
|
||||||
|
test.send(app)
|
||||||
|
app.handleServerMessage(&test.event)
|
||||||
|
|
||||||
|
lines, unread := bufferState(app.ui, testChannel)
|
||||||
|
if lines != 1 || unread != 0 {
|
||||||
|
t.Fatalf("got %d lines and %d unread, want 1 and 0",
|
||||||
|
lines, unread)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOwnEventFromOtherClient checks that a message, notice,
|
||||||
|
// join or part that the user's session makes from another
|
||||||
|
// client shows as one line in the buffer it belongs to.
|
||||||
|
func TestOwnEventFromOtherClient(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
event api.Message
|
||||||
|
buffer string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "channel message",
|
||||||
|
event: api.Message{
|
||||||
|
Command: irc.CmdPrivmsg, From: testNick, To: testChannel,
|
||||||
|
Body: []string{testText},
|
||||||
|
},
|
||||||
|
buffer: testChannel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "direct message",
|
||||||
|
event: api.Message{
|
||||||
|
Command: irc.CmdPrivmsg, From: testNick, To: testOtherNick,
|
||||||
|
Body: []string{testText},
|
||||||
|
},
|
||||||
|
buffer: testOtherNick,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "notice",
|
||||||
|
event: api.Message{
|
||||||
|
Command: irc.CmdNotice, From: testNick, To: testChannel,
|
||||||
|
Body: []string{testText},
|
||||||
|
},
|
||||||
|
buffer: "(status)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "join",
|
||||||
|
event: api.Message{
|
||||||
|
Command: irc.CmdJoin, From: testNick, To: testChannel,
|
||||||
|
},
|
||||||
|
buffer: testChannel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "part",
|
||||||
|
event: api.Message{
|
||||||
|
Command: irc.CmdPart, From: testNick, To: testChannel,
|
||||||
|
},
|
||||||
|
buffer: testChannel,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
app := &App{ui: NewUI(), nick: testNick}
|
||||||
|
runUI(t, app.ui)
|
||||||
|
|
||||||
|
app.handleServerMessage(&test.event)
|
||||||
|
|
||||||
|
lines, _ := bufferState(app.ui, test.buffer)
|
||||||
|
if lines != 1 {
|
||||||
|
t.Fatalf("got %d lines in %s, want 1", lines, test.buffer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufferState returns the number of lines in the named
|
||||||
|
// buffer and how many of them are unread.
|
||||||
|
func bufferState(ui *UI, name string) (int, int) {
|
||||||
|
var lines, unread int
|
||||||
|
|
||||||
|
ui.app.QueueUpdate(func() {
|
||||||
|
buf := ui.getOrCreateBuffer(name)
|
||||||
|
lines = len(buf.Lines)
|
||||||
|
unread = buf.Unread
|
||||||
|
})
|
||||||
|
|
||||||
|
return lines, unread
|
||||||
|
}
|
||||||
|
|
||||||
// runUI runs ui on a simulated screen until the test ends.
|
// runUI runs ui on a simulated screen until the test ends.
|
||||||
func runUI(t *testing.T, ui *UI) {
|
func runUI(t *testing.T, ui *UI) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|||||||
@@ -95,6 +95,19 @@ func (ui *UI) AddLine(bufferName, line string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddLineAsRead adds a line to the specified buffer, as
|
||||||
|
// AddLine does, but does not mark the buffer unread.
|
||||||
|
func (ui *UI) AddLineAsRead(bufferName, line string) {
|
||||||
|
ui.app.QueueUpdateDraw(func() {
|
||||||
|
buf := ui.getOrCreateBuffer(bufferName)
|
||||||
|
buf.Lines = append(buf.Lines, line)
|
||||||
|
|
||||||
|
if ui.buffers[ui.currentBuffer] == buf {
|
||||||
|
_, _ = fmt.Fprintln(ui.messages, line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// AddStatus adds a line to the status buffer.
|
// AddStatus adds a line to the status buffer.
|
||||||
func (ui *UI) AddStatus(line string) {
|
func (ui *UI) AddStatus(line string) {
|
||||||
ts := time.Now().Format("15:04")
|
ts := time.Now().Format("15:04")
|
||||||
|
|||||||
Reference in New Issue
Block a user