diff --git a/internal/ui/chat/messageslist/model.go b/internal/ui/chat/messageslist/model.go index 73e1ca6..21ae188 100644 --- a/internal/ui/chat/messageslist/model.go +++ b/internal/ui/chat/messageslist/model.go @@ -842,6 +842,11 @@ func (ml Model) Update(msg tview.Msg) (Model, tview.Cmd) { return ml, cmd } +// atTop reports whether the oldest message shown is selected. +func (ml *Model) atTop() bool { + return ml.cursor() >= 0 && ml.cursor() == ml.messageIndex(-1, 1) +} + // update changes ml in response to msg and returns a command to run, or nil. func (ml *Model) update(msg tview.Msg) tview.Cmd { switch msg := msg.(type) { @@ -856,8 +861,7 @@ func (ml *Model) update(msg tview.Msg) tview.Cmd { ml.selectDown() return nil case keybind.Matches(msg, ml.cfg.Keybinds.MessagesList.SelectTop.Keybind): - ml.selectTop() - return nil + return ml.selectTop() case keybind.Matches(msg, ml.cfg.Keybinds.MessagesList.SelectBottom.Keybind): ml.selectBottom() return nil @@ -898,12 +902,8 @@ func (ml *Model) update(msg tview.Msg) tview.Cmd { for i, message := range msg.Older { older[i] = messageItem{message: message} } - first := ml.messageIndex(-1, 1) ml.items = slices.Concat(older, ml.items) - switch { - case prevCursor == first && len(older) > 0: - ml.setCursor(len(older) - 1) - case prevCursor >= 0: + if prevCursor >= 0 { ml.setCursor(prevCursor + len(older)) } ml.rebuildItems() @@ -943,8 +943,12 @@ func (ml *Model) update(msg tview.Msg) tview.Cmd { ml.kitty = msg.Name == "kitty" || msg.Name == "ghostty" } case listMsg: + top := ml.atTop() ml.selectionState.Apply(list.Change(msg)) ml.onRowCursorChanged(ml.cursor()) + if !top && ml.atTop() { + return ml.fetchOlderMessages() + } } return nil } @@ -957,8 +961,12 @@ func (ml *Model) selectDown() { } } -func (ml *Model) selectTop() { +func (ml *Model) selectTop() tview.Cmd { + if ml.atTop() { + return nil + } ml.setCursor(ml.messageIndex(-1, 1)) + return ml.fetchOlderMessages() } func (ml *Model) selectBottom() { diff --git a/internal/ui/chat/messageslist/model_test.go b/internal/ui/chat/messageslist/model_test.go index 9386d28..651221b 100644 --- a/internal/ui/chat/messageslist/model_test.go +++ b/internal/ui/chat/messageslist/model_test.go @@ -42,6 +42,13 @@ func TestModelBuildItem(t *testing.T) { if ml.buildItem(ml.messageIndex(len(ml.items), -1)) != view { t.Fatal("message view was not reused") } + if ml.selectTop() == nil || ml.selectTop() != nil || ml.selectUp() != nil { + t.Fatal("older messages were not fetched once on reaching the top") + } + ml.selectBottom() + if _, cmd := ml.Update(listMsg{}); cmd == nil { + t.Fatal("clicking the top message did not fetch older messages") + } ml.selectBottom() *ml, _ = ml.Update(olderMessagesLoadedMsg{ChannelID: 1, Older: []discord.Message{{ID: 1}}}) selected, ok := ml.selectedMessage() @@ -127,8 +134,7 @@ func TestModelRebuildItems(t *testing.T) { assertSelected(2) *ml, _ = ml.Update(olderMessagesLoadedMsg{ChannelID: 1, Older: messages[2:]}) assertItems(1, 2, 3) - assertSelected(1) - ml.selectDown() + assertSelected(2) ml.deleteMessage(ml.cursor()) assertItems(1, 3) assertSelected(1) diff --git a/internal/ui/chat/messageslist/msg.go b/internal/ui/chat/messageslist/msg.go index 64777e4..d1167d8 100644 --- a/internal/ui/chat/messageslist/msg.go +++ b/internal/ui/chat/messageslist/msg.go @@ -53,8 +53,10 @@ func (ml *Model) selectUp() tview.Cmd { ml.selectBottom() } else if previous := ml.messageIndex(cursor, -1); previous >= 0 { ml.setCursor(previous) - } else { - return ml.fetchOlderMessages() + // Older messages are fetched once as the cursor reaches the top, rather than on each key press there. + if ml.atTop() { + return ml.fetchOlderMessages() + } } return nil }