From 2d79e45f6fc6be63cf8e6c96f3d13cd81ab9080a Mon Sep 17 00:00:00 2001 From: ayn2op Date: Sat, 14 Mar 2026 05:06:21 -0400 Subject: [PATCH] refactor(ui): remove redraw commands and inline command events --- go.mod | 14 +-- go.sum | 28 ++--- internal/ui/chat/events.go | 29 +++-- internal/ui/chat/guilds_tree.go | 7 +- internal/ui/chat/message_input.go | 33 +++--- internal/ui/chat/messages_list.go | 33 +++--- internal/ui/chat/model.go | 81 ++++---------- internal/ui/login/events.go | 4 +- internal/ui/login/model.go | 21 ++-- internal/ui/login/qr/events.go | 174 +++++++++++------------------- internal/ui/login/qr/model.go | 27 ++--- internal/ui/login/token/events.go | 14 +-- internal/ui/root/events.go | 44 ++++---- internal/ui/root/model.go | 22 ++-- pkg/picker/picker.go | 13 ++- 15 files changed, 215 insertions(+), 329 deletions(-) diff --git a/go.mod b/go.mod index 3ea9a43..0198396 100644 --- a/go.mod +++ b/go.mod @@ -11,9 +11,9 @@ require ( github.com/alecthomas/chroma/v2 v2.23.1 github.com/andybalholm/brotli v1.2.0 github.com/ayn2op/clipboard v0.0.0-20260308203959-c5ad7df3fc97 - github.com/ayn2op/tview v0.0.0-20260311012550-9b46cad522bf + github.com/ayn2op/tview v0.0.0-20260314084311-e6fda4e5838b github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb - github.com/diamondburned/arikawa/v3 v3.6.1-0.20260309010533-e61165a61b64 + github.com/diamondburned/arikawa/v3 v3.6.1-0.20260311205148-176ad9b9440f github.com/diamondburned/ningen/v3 v3.0.1-0.20260306213430-5a08d3a709b4 github.com/gdamore/tcell/v3 v3.1.2 github.com/gen2brain/beeep v0.11.2 @@ -53,11 +53,11 @@ require ( github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect github.com/twmb/murmur3 v1.1.8 // indirect go4.org v0.0.0-20260112195520-a5071408f32f // indirect - golang.org/x/exp/shiny v0.0.0-20260218203240-3dfff04db8fa // indirect - golang.org/x/image v0.36.0 // indirect - golang.org/x/mobile v0.0.0-20260217195705-b56b3793a9c4 // indirect + golang.org/x/exp/shiny v0.0.0-20260312153236-7ab1446f8b90 // indirect + golang.org/x/image v0.37.0 // indirect + golang.org/x/mobile v0.0.0-20260312152759-81488f6aeb60 // indirect golang.org/x/sys v0.42.0 // indirect - golang.org/x/term v0.40.0 // indirect - golang.org/x/text v0.34.0 // indirect + golang.org/x/term v0.41.0 // indirect + golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.15.0 // indirect ) diff --git a/go.sum b/go.sum index 1d3acb0..57d7e38 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/ayn2op/clipboard v0.0.0-20260308203959-c5ad7df3fc97 h1:WujETUV+v0DEJyZgjeLzQvihWyL80c0Tg4qf0dDo+Io= github.com/ayn2op/clipboard v0.0.0-20260308203959-c5ad7df3fc97/go.mod h1:3kFnpNCa3dF6WryzOMCDao7PfZ7DTCh+pievlfuwV80= -github.com/ayn2op/tview v0.0.0-20260311012550-9b46cad522bf h1:ay4OtKYsFGvK4Ng1pmZ/n8MA1CWzZO80lDlrDiCpdag= -github.com/ayn2op/tview v0.0.0-20260311012550-9b46cad522bf/go.mod h1:lZ8RdOegQWBQafTOasGE7Ps1/Ymy4jmXoPt5vz2QsS0= +github.com/ayn2op/tview v0.0.0-20260314084311-e6fda4e5838b h1:ZGDcIf5Vrk+/Tl3GMaxuXNQek8oT/FW27nn5xdkXx5A= +github.com/ayn2op/tview v0.0.0-20260314084311-e6fda4e5838b/go.mod h1:lZ8RdOegQWBQafTOasGE7Ps1/Ymy4jmXoPt5vz2QsS0= github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -27,8 +27,8 @@ github.com/dchest/jsmin v1.0.0 h1:Y2hWXmGZiRxtl+VcTksyucgTlYxnhPzTozCwx9gy9zI= github.com/dchest/jsmin v1.0.0/go.mod h1:AVBIund7Mr7lKXT70hKT2YgL3XEXUaUk5iw9DZ8b0Uc= github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb h1:6S+TKObz6+Io2c8IOkcbK4Sz7nj6RpEVU7TkvmsZZcw= github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb/go.mod h1:wf3nKtOnQqCp7kp9xB7hHnNlZ6m3NoiOxjrB9hFRq4Y= -github.com/diamondburned/arikawa/v3 v3.6.1-0.20260309010533-e61165a61b64 h1:ZdzbwItbGTvOdNfoCH7QIbRiBPDufsq8AwfJUaM4mHM= -github.com/diamondburned/arikawa/v3 v3.6.1-0.20260309010533-e61165a61b64/go.mod h1:TpV2GvCJIYSwAXUEAx4sutPcftyAbVidiFlG6l7K0go= +github.com/diamondburned/arikawa/v3 v3.6.1-0.20260311205148-176ad9b9440f h1:IQWwTKdWCWl0MiD2ln0LSTiO9FMOrp4FlbOfKgaF8/A= +github.com/diamondburned/arikawa/v3 v3.6.1-0.20260311205148-176ad9b9440f/go.mod h1:TpV2GvCJIYSwAXUEAx4sutPcftyAbVidiFlG6l7K0go= github.com/diamondburned/ningen/v3 v3.0.1-0.20260306213430-5a08d3a709b4 h1:m1WyrOUuFE4BuIcQzPeRmmLQmJMBNElT/tfx/s2+AoE= github.com/diamondburned/ningen/v3 v3.0.1-0.20260306213430-5a08d3a709b4/go.mod h1:JSqSCBN5MAI9yjAO6tZlSayXM9KWx+8q3EhhlkCuscM= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= @@ -117,12 +117,12 @@ go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH go4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp/shiny v0.0.0-20260218203240-3dfff04db8fa h1:+7e7RPzOw2fG8DBbddatlOmHGNCg+VlA2Ar0yVMw7sM= -golang.org/x/exp/shiny v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:zxsA7NyDTOUjcveVwAMFI/YIErWwayTW/4RGqB/RzKk= -golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc= -golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4= -golang.org/x/mobile v0.0.0-20260217195705-b56b3793a9c4 h1:uT3oYo9M38vJa7JpT4kCie2lJwOpoUrx7FvV0H7kXSc= -golang.org/x/mobile v0.0.0-20260217195705-b56b3793a9c4/go.mod h1:4OGHIUSBiIqyFAQDaX1tpY0BVnO20DvNDeATBu8aeFQ= +golang.org/x/exp/shiny v0.0.0-20260312153236-7ab1446f8b90 h1:kyPrwnEYXdME284bE7xgS9BPxhG7MCa5hw1/TpaTJVs= +golang.org/x/exp/shiny v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:jqkJFnLVkS8zgKKY4+MOPCZtuZGw3hONUjhapUSwZ8c= +golang.org/x/image v0.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA= +golang.org/x/image v0.37.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= +golang.org/x/mobile v0.0.0-20260312152759-81488f6aeb60 h1:MOzyaj0wu2xneBkzkg9LHNYjDBB4W5vP043A2SYQRPA= +golang.org/x/mobile v0.0.0-20260312152759-81488f6aeb60/go.mod h1:th6VJvzjMbrYF8SduQY5rpD0HG0GleGxjadkqSxFs3k= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -144,15 +144,15 @@ golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/ui/chat/events.go b/internal/ui/chat/events.go index 6c2aa30..fb47d6f 100644 --- a/internal/ui/chat/events.go +++ b/internal/ui/chat/events.go @@ -9,14 +9,12 @@ import ( type LogoutEvent struct{ tcell.EventTime } -func newLogoutEvent() *LogoutEvent { - event := &LogoutEvent{} - event.SetEventNow() - return event -} - func (v *Model) logout() tview.Command { - return tview.EventCommand(func() tcell.Event { return newLogoutEvent() }) + return func() tcell.Event { + event := &LogoutEvent{} + event.SetEventNow() + return event + } } type QuitEvent struct{ tcell.EventTime } @@ -28,11 +26,24 @@ func NewQuitEvent() *QuitEvent { } func (v *Model) closeState() tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { if err := v.CloseState(); err != nil { slog.Error("failed to close the session", "err", err) return tcell.NewEventError(err) } return nil - }) + } +} + +type closeLayerEvent struct { + tcell.EventTime + name string +} + +func closeLayer(name string) tview.Command { + return func() tcell.Event { + event := &closeLayerEvent{name: name} + event.SetEventNow() + return event + } } diff --git a/internal/ui/chat/guilds_tree.go b/internal/ui/chat/guilds_tree.go index 19c0cef..f0e7547 100644 --- a/internal/ui/chat/guilds_tree.go +++ b/internal/ui/chat/guilds_tree.go @@ -397,13 +397,12 @@ func (gt *guildsTree) collapseParentNode(node *tview.TreeNode) { func (gt *guildsTree) HandleEvent(event tcell.Event) tview.Command { switch event := event.(type) { case *tview.KeyEvent: - redraw := tview.RedrawCommand{} handler := gt.TreeView.HandleEvent switch { case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.CollapseParentNode.Keybind): gt.collapseParentNode(gt.GetCurrentNode()) - return redraw + return nil case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.MoveToParentNode.Keybind): return handler(tcell.NewEventKey(tcell.KeyRune, "K", tcell.ModNone)) case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.Up.Keybind): @@ -412,10 +411,10 @@ func (gt *guildsTree) HandleEvent(event tcell.Event) tview.Command { return handler(tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone)) case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.Top.Keybind): gt.Move(gt.GetRowCount() * -1) - return redraw + return nil case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.Bottom.Keybind): gt.Move(gt.GetRowCount()) - return redraw + return nil case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.SelectCurrent.Keybind): return handler(tcell.NewEventKey(tcell.KeyEnter, "", tcell.ModNone)) case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.YankID.Keybind): diff --git a/internal/ui/chat/message_input.go b/internal/ui/chat/message_input.go index 16f258d..9e9df1a 100644 --- a/internal/ui/chat/message_input.go +++ b/internal/ui/chat/message_input.go @@ -21,7 +21,6 @@ import ( "github.com/ayn2op/tview" "github.com/ayn2op/tview/help" "github.com/ayn2op/tview/keybind" - "github.com/ayn2op/tview/layers" "github.com/diamondburned/arikawa/v3/api" "github.com/diamondburned/arikawa/v3/discord" "github.com/diamondburned/arikawa/v3/state" @@ -107,7 +106,6 @@ func (mi *messageInput) stopTypingTimer() { func (mi *messageInput) HandleEvent(event tcell.Event) tview.Command { switch event := event.(type) { case *tview.KeyEvent: - redraw := tview.RedrawCommand{} handler := mi.TextArea.HandleEvent switch { case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.Paste.Keybind): @@ -119,29 +117,27 @@ func (mi *messageInput) HandleEvent(event tcell.Event) tview.Command { } else { mi.send() } - return redraw + return nil case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.OpenEditor.Keybind): - var cmds tview.BatchCommand + var cmds []tview.Command mi.stopTabCompletion(func(next tview.Command) { if next != nil { cmds = append(cmds, next) } }) mi.editor() - cmds = append(cmds, redraw) - return cmds + return tview.Batch(cmds...) case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.OpenFilePicker.Keybind): - var cmds tview.BatchCommand + var cmds []tview.Command mi.stopTabCompletion(func(next tview.Command) { if next != nil { cmds = append(cmds, next) } }) mi.openFilePicker() - cmds = append(cmds, redraw) - return cmds + return tview.Batch(cmds...) case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.Cancel.Keybind): - var cmds tview.BatchCommand + var cmds []tview.Command if mi.chat.GetVisible(mentionsListLayerName) { mi.stopTabCompletion(func(next tview.Command) { if next != nil { @@ -151,11 +147,10 @@ func (mi *messageInput) HandleEvent(event tcell.Event) tview.Command { } else { mi.reset() } - cmds = append(cmds, redraw) - return cmds + return tview.Batch(cmds...) case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.TabComplete.Keybind): go mi.chat.app.QueueUpdateDraw(func() { mi.tabComplete() }) - return redraw + return nil case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.Undo.Keybind): return handler(tcell.NewEventKey(tcell.KeyCtrlZ, "", tcell.ModNone)) } @@ -177,16 +172,16 @@ func (mi *messageInput) HandleEvent(event tcell.Event) tview.Command { switch { case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Up.Keybind): mi.mentionsList.HandleEvent(tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone)) - return redraw + return nil case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Down.Keybind): mi.mentionsList.HandleEvent(tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone)) - return redraw + return nil case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Top.Keybind): mi.mentionsList.HandleEvent(tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone)) - return redraw + return nil case keybind.Matches(event, mi.cfg.Keybinds.MentionsList.Bottom.Keybind): mi.mentionsList.HandleEvent(tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone)) - return redraw + return nil } } @@ -631,8 +626,8 @@ func (mi *messageInput) stopTabCompletion(emit func(tview.Command)) { if mi.cfg.AutocompleteLimit > 0 { mi.mentionsList.clear() if emit != nil { - emit(layers.CloseLayerCommand{Name: mentionsListLayerName}) - emit(tview.SetFocusCommand{Target: mi}) + emit(closeLayer(mentionsListLayerName)) + emit(tview.SetFocus(mi)) } else { mi.removeMentionsList() mi.chat.app.SetFocus(mi) diff --git a/internal/ui/chat/messages_list.go b/internal/ui/chat/messages_list.go index ce8e267..4bbb8ec 100644 --- a/internal/ui/chat/messages_list.go +++ b/internal/ui/chat/messages_list.go @@ -840,38 +840,37 @@ func (ml *messagesList) selectedMessage() (*discord.Message, error) { func (ml *messagesList) HandleEvent(event tcell.Event) tview.Command { switch event := event.(type) { case *tview.KeyEvent: - redraw := tview.RedrawCommand{} switch { case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollUp.Keybind): ml.ScrollUp() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollDown.Keybind): ml.ScrollDown() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollTop.Keybind): ml.ScrollToStart() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ScrollBottom.Keybind): ml.ScrollToEnd() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Cancel.Keybind): ml.clearSelection() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectUp.Keybind): ml.selectUp() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectDown.Keybind): ml.selectDown() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectTop.Keybind): ml.selectTop() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectBottom.Keybind): ml.selectBottom() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.SelectReply.Keybind): ml.selectReply() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.YankID.Keybind): ml.yankID() return nil @@ -883,22 +882,22 @@ func (ml *messagesList) HandleEvent(event tcell.Event) tview.Command { return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Open.Keybind): ml.open() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Reply.Keybind): ml.reply(false) - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.ReplyMention.Keybind): ml.reply(true) - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Edit.Keybind): ml.edit() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.Delete.Keybind): ml.delete() - return redraw + return nil case keybind.Matches(event, ml.cfg.Keybinds.MessagesList.DeleteConfirm.Keybind): ml.confirmDelete() - return redraw + return nil } // Do not fall through to List defaults for unmatched keys. return nil diff --git a/internal/ui/chat/model.go b/internal/ui/chat/model.go index 3337ccb..af60b3a 100644 --- a/internal/ui/chat/model.go +++ b/internal/ui/chat/model.go @@ -203,18 +203,18 @@ func (v *Model) focusNext() { func (v *Model) HandleEvent(event tcell.Event) tview.Command { switch event := event.(type) { case *tview.InitEvent: - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { if err := v.OpenState(v.token); err != nil { slog.Error("failed to open chat state", "err", err) return tcell.NewEventError(err) } return nil - }) + } case *QuitEvent: - return tview.BatchCommand{ + return tview.Batch( v.closeState(), tview.Quit(), - } + ) case *tview.ModalDoneEvent: if v.HasLayer(confirmModalLayerName) { v.RemoveLayer(confirmModalLayerName) @@ -227,88 +227,43 @@ func (v *Model) HandleEvent(event tcell.Event) tview.Command { if onDone != nil { onDone(event.ButtonLabel) } - return tview.RedrawCommand{} + return nil } case *tview.KeyEvent: - redraw := tview.RedrawCommand{} switch { case keybind.Matches(event, v.cfg.Keybinds.FocusGuildsTree.Keybind): v.messageInput.removeMentionsList() v.focusGuildsTree() - return redraw + return nil case keybind.Matches(event, v.cfg.Keybinds.FocusMessagesList.Keybind): v.messageInput.removeMentionsList() v.app.SetFocus(v.messagesList) - return redraw + return nil case keybind.Matches(event, v.cfg.Keybinds.FocusMessageInput.Keybind): v.focusMessageInput() - return redraw + return nil case keybind.Matches(event, v.cfg.Keybinds.FocusPrevious.Keybind): v.focusPrevious() - return redraw + return nil case keybind.Matches(event, v.cfg.Keybinds.FocusNext.Keybind): v.focusNext() - return redraw + return nil case keybind.Matches(event, v.cfg.Keybinds.Logout.Keybind): - return tview.BatchCommand{v.closeState(), v.logout()} + return tview.Batch(v.closeState(), v.logout()) case keybind.Matches(event, v.cfg.Keybinds.ToggleGuildsTree.Keybind): v.toggleGuildsTree() - return redraw + return nil case keybind.Matches(event, v.cfg.Keybinds.ToggleChannelsPicker.Keybind): v.togglePicker() - return redraw + return nil + } + case *closeLayerEvent: + if v.HasLayer(event.name) { + v.HideLayer(event.name) } - } - cmd := v.Layers.HandleEvent(event) - return v.consumeLayerCommands(cmd) -} - -func (v *Model) consumeLayerCommands(command tview.Command) tview.Command { - if command == nil { return nil } - - var commands []tview.Command - switch c := command.(type) { - case tview.BatchCommand: - commands = c - default: - commands = []tview.Command{c} - } - - remaining := make([]tview.Command, 0, len(commands)) - for _, cmd := range commands { - switch c := cmd.(type) { - case layers.OpenLayerCommand: - if v.HasLayer(c.Name) { - v.ShowLayer(c.Name).SendToFront(c.Name) - } - continue - case layers.CloseLayerCommand: - if v.HasLayer(c.Name) { - v.HideLayer(c.Name) - } - continue - case layers.ToggleLayerCommand: - if v.HasLayer(c.Name) { - if v.GetVisible(c.Name) { - v.HideLayer(c.Name) - } else { - v.ShowLayer(c.Name).SendToFront(c.Name) - } - } - continue - } - remaining = append(remaining, cmd) - } - - if len(remaining) == 0 { - return nil - } - if len(remaining) == 1 { - return remaining[0] - } - return tview.BatchCommand(remaining) + return v.Layers.HandleEvent(event) } func (v *Model) showConfirmModal(prompt string, buttons []string, onDone func(label string)) { diff --git a/internal/ui/login/events.go b/internal/ui/login/events.go index 7b765e4..14105a6 100644 --- a/internal/ui/login/events.go +++ b/internal/ui/login/events.go @@ -9,11 +9,11 @@ import ( ) func setClipboard(content string) tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { if err := clipboard.Write(clipboard.FmtText, []byte(content)); err != nil { slog.Error("failed to copy error message", "err", err) return tcell.NewEventError(err) } return nil - }) + } } diff --git a/internal/ui/login/model.go b/internal/ui/login/model.go index 318df99..87f46f0 100644 --- a/internal/ui/login/model.go +++ b/internal/ui/login/model.go @@ -22,14 +22,13 @@ const ( type Model struct { *layers.Layers tabs *tabs.Model - app *tview.Application cfg *config.Config errorModalText string } -func NewModel(app *tview.Application, cfg *config.Config) *Model { - tabs := tabs.NewModel([]tabs.Tab{token.NewModel(), qr.NewModel(app)}) +func NewModel(cfg *config.Config) *Model { + tabs := tabs.NewModel([]tabs.Tab{token.NewModel(), qr.NewModel()}) l := layers.New() ui.ConfigureBox(l.Box, &cfg.Theme) @@ -38,9 +37,7 @@ func NewModel(app *tview.Application, cfg *config.Config) *Model { return &Model{ Layers: l, tabs: tabs, - app: app, - - cfg: cfg, + cfg: cfg, } } @@ -48,10 +45,9 @@ func (m *Model) HandleEvent(event tcell.Event) tview.Command { switch event := event.(type) { case *tcell.EventError: if m.HasLayer(errorLayerName) { - return tview.RedrawCommand{} + return nil } - m.onError(event) - return tview.RedrawCommand{} + return m.showErrorDialog(event) case *tview.ModalDoneEvent: if !m.HasLayer(errorLayerName) { return nil @@ -61,12 +57,12 @@ func (m *Model) HandleEvent(event tcell.Event) tview.Command { } m.RemoveLayer(errorLayerName) m.errorModalText = "" - return tview.RedrawCommand{} + return nil } return m.Layers.HandleEvent(event) } -func (m *Model) onError(err error) { +func (m *Model) showErrorDialog(err error) tview.Command { slog.Error("failed to login", "err", err) message := err.Error() @@ -99,6 +95,5 @@ func (m *Model) onError(err error) { layers.WithOverlay(), ). SendToFront(errorLayerName) - modal.SetFocus(0) - m.app.SetFocus(modal) + return tview.SetFocus(modal) } diff --git a/internal/ui/login/qr/events.go b/internal/ui/login/qr/events.go index e671d62..7af2b82 100644 --- a/internal/ui/login/qr/events.go +++ b/internal/ui/login/qr/events.go @@ -24,12 +24,6 @@ type TokenEvent struct { Token string } -func newTokenEvent(token string) *TokenEvent { - event := &TokenEvent{Token: token} - event.SetEventNow() - return event -} - const remoteAuthGatewayURL = "wss://remote-auth-gateway.discord.gg/?v=2" type connCreateEvent struct { @@ -37,41 +31,33 @@ type connCreateEvent struct { conn *websocket.Conn } -func newConnCreateEvent(conn *websocket.Conn) *connCreateEvent { - event := &connCreateEvent{conn: conn} - event.SetEventNow() - return event -} - type connCloseEvent struct{ tcell.EventTime } -func newConnCloseEvent() *connCloseEvent { - event := &connCloseEvent{} - event.SetEventNow() - return event -} - func (m *Model) connect() tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { headers := http.Headers() headers.Set("User-Agent", http.BrowserUserAgent) conn, _, err := websocket.DefaultDialer.Dial(remoteAuthGatewayURL, headers) if err != nil { return tcell.NewEventError(err) } - return newConnCreateEvent(conn) - }) + event := &connCreateEvent{conn: conn} + event.SetEventNow() + return event + } } func (m *Model) close() tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { if m.conn != nil { if err := m.conn.Close(); err != nil { return tcell.NewEventError(err) } } - return newConnCloseEvent() - }) + event := &connCloseEvent{} + event.SetEventNow() + return event + } } type helloEvent struct { @@ -80,66 +66,30 @@ type helloEvent struct { timeoutMS int } -func newHelloEvent(heartbeatInterval, timeoutMS int) *helloEvent { - event := &helloEvent{heartbeatInterval: heartbeatInterval, timeoutMS: timeoutMS} - event.SetEventNow() - return event -} - type nonceProofEvent struct { tcell.EventTime encryptedNonce string } -func newNonceProofEvent(encryptedNonce string) *nonceProofEvent { - event := &nonceProofEvent{encryptedNonce: encryptedNonce} - event.SetEventNow() - return event -} - type pendingRemoteInitEvent struct { tcell.EventTime fingerprint string } -func newPendingRemoteInitEvent(fingerprint string) *pendingRemoteInitEvent { - event := &pendingRemoteInitEvent{fingerprint: fingerprint} - event.SetEventNow() - return event -} - type pendingTicketEvent struct { tcell.EventTime encryptedUserPayload string } -func newPendingTicketEvent(encryptedUserPayload string) *pendingTicketEvent { - event := &pendingTicketEvent{encryptedUserPayload: encryptedUserPayload} - event.SetEventNow() - return event -} - type pendingLoginEvent struct { tcell.EventTime ticket string } -func newPendingLoginEvent(ticket string) *pendingLoginEvent { - event := &pendingLoginEvent{ticket: ticket} - event.SetEventNow() - return event -} - type cancelEvent struct{ tcell.EventTime } -func newCancelEvent() *cancelEvent { - event := &cancelEvent{} - event.SetEventNow() - return event -} - func (m *Model) listen() tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { if m.conn == nil { return nil } @@ -165,7 +115,9 @@ func (m *Model) listen() tview.Command { if err := json.Unmarshal(data, &payload); err != nil { return tcell.NewEventError(err) } - return newHelloEvent(payload.HeartbeatInterval, payload.TimeoutMS) + event := &helloEvent{heartbeatInterval: payload.HeartbeatInterval, timeoutMS: payload.TimeoutMS} + event.SetEventNow() + return event case "nonce_proof": var payload struct { EncryptedNonce string `json:"encrypted_nonce"` @@ -173,7 +125,9 @@ func (m *Model) listen() tview.Command { if err := json.Unmarshal(data, &payload); err != nil { return tcell.NewEventError(err) } - return newNonceProofEvent(payload.EncryptedNonce) + event := &nonceProofEvent{encryptedNonce: payload.EncryptedNonce} + event.SetEventNow() + return event case "pending_remote_init": var payload struct { Fingerprint string `json:"fingerprint"` @@ -181,7 +135,9 @@ func (m *Model) listen() tview.Command { if err := json.Unmarshal(data, &payload); err != nil { return tcell.NewEventError(err) } - return newPendingRemoteInitEvent(payload.Fingerprint) + event := &pendingRemoteInitEvent{fingerprint: payload.Fingerprint} + event.SetEventNow() + return event case "pending_ticket": var payload struct { EncryptedUserPayload string `json:"encrypted_user_payload"` @@ -189,9 +145,13 @@ func (m *Model) listen() tview.Command { if err := json.Unmarshal(data, &payload); err != nil { return tcell.NewEventError(err) } - return newPendingTicketEvent(payload.EncryptedUserPayload) + event := &pendingTicketEvent{encryptedUserPayload: payload.EncryptedUserPayload} + event.SetEventNow() + return event case "cancel": - return newCancelEvent() + event := &cancelEvent{} + event.SetEventNow() + return event case "pending_login": var payload struct { Ticket string `json:"ticket"` @@ -199,30 +159,28 @@ func (m *Model) listen() tview.Command { if err := json.Unmarshal(data, &payload); err != nil { return tcell.NewEventError(err) } - return newPendingLoginEvent(payload.Ticket) + event := &pendingLoginEvent{ticket: payload.Ticket} + event.SetEventNow() + return event default: return nil } - }) + } } type heartbeatTickEvent struct{ tcell.EventTime } -func newHeartbeatTickEvent() *heartbeatTickEvent { - event := &heartbeatTickEvent{} - event.SetEventNow() - return event -} - func (m *Model) heartbeat() tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { time.Sleep(m.heartbeatInterval) - return newHeartbeatTickEvent() - }) + event := &heartbeatTickEvent{} + event.SetEventNow() + return event + } } func (m *Model) sendHeartbeat() tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { if m.conn == nil { return nil } @@ -233,7 +191,7 @@ func (m *Model) sendHeartbeat() tview.Command { return tcell.NewEventError(err) } return nil - }) + } } type privateKeyEvent struct { @@ -241,24 +199,20 @@ type privateKeyEvent struct { privateKey *rsa.PrivateKey } -func newPrivateKeyEvent(privateKey *rsa.PrivateKey) *privateKeyEvent { - event := &privateKeyEvent{privateKey: privateKey} - event.SetEventNow() - return event -} - func (m *Model) generatePrivateKey() tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return tcell.NewEventError(err) } - return newPrivateKeyEvent(privateKey) - }) + event := &privateKeyEvent{privateKey: privateKey} + event.SetEventNow() + return event + } } func (m *Model) sendInit() tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { if m.privateKey == nil { return tcell.NewEventError(errors.New("missing private key")) } @@ -275,11 +229,11 @@ func (m *Model) sendInit() tview.Command { return tcell.NewEventError(err) } return nil - }) + } } func (m *Model) sendNonceProof(encryptedNonce string) tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { decodedNonce, err := base64.StdEncoding.DecodeString(encryptedNonce) if err != nil { return tcell.NewEventError(err) @@ -299,7 +253,7 @@ func (m *Model) sendNonceProof(encryptedNonce string) tview.Command { return tcell.NewEventError(err) } return nil - }) + } } type qrCodeEvent struct { @@ -307,22 +261,18 @@ type qrCodeEvent struct { qrCode *qrcode.QRCode } -func newQRCodeEvent(qrCode *qrcode.QRCode) *qrCodeEvent { - event := &qrCodeEvent{qrCode: qrCode} - event.SetEventNow() - return event -} - func (m *Model) generateQRCode(fingerprint string) tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { content := "https://discord.com/ra/" + fingerprint qrCode, err := qrcode.New(content, qrcode.Low) if err != nil { return tcell.NewEventError(err) } qrCode.DisableBorder = true - return newQRCodeEvent(qrCode) - }) + event := &qrCodeEvent{qrCode: qrCode} + event.SetEventNow() + return event + } } type userEvent struct { @@ -331,14 +281,8 @@ type userEvent struct { username string } -func newUserEvent(discriminator, username string) *userEvent { - event := &userEvent{discriminator: discriminator, username: username} - event.SetEventNow() - return event -} - func (m *Model) decryptUserPayload(encryptedPayload string) tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { decodedPayload, err := base64.StdEncoding.DecodeString(encryptedPayload) if err != nil { return tcell.NewEventError(err) @@ -354,12 +298,14 @@ func (m *Model) decryptUserPayload(encryptedPayload string) tview.Command { return tcell.NewEventError(errors.New("invalid user payload")) } - return newUserEvent(parts[1], parts[3]) - }) + event := &userEvent{discriminator: parts[1], username: parts[3]} + event.SetEventNow() + return event + } } func (m *Model) exchangeTicket(ticket string) tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { headers := http.Headers() headers.Set("Referer", "https://discord.com/login") if m.fingerprint != "" { @@ -383,6 +329,8 @@ func (m *Model) exchangeTicket(ticket string) tview.Command { if err != nil { return tcell.NewEventError(err) } - return newTokenEvent(string(decryptedToken)) - }) + event := &TokenEvent{Token: string(decryptedToken)} + event.SetEventNow() + return event + } } diff --git a/internal/ui/login/qr/model.go b/internal/ui/login/qr/model.go index 3e8ebe0..a3a9aae 100644 --- a/internal/ui/login/qr/model.go +++ b/internal/ui/login/qr/model.go @@ -15,7 +15,6 @@ import ( type Model struct { *tview.TextView - app *tview.Application conn *websocket.Conn heartbeatInterval time.Duration @@ -26,18 +25,14 @@ type Model struct { msg string } -func NewModel(app *tview.Application) *Model { +func NewModel() *Model { m := &Model{ TextView: tview.NewTextView(), - app: app, } m. SetScrollable(true). SetWrap(false). - SetTextAlign(tview.AlignmentCenter). - SetChangedFunc(func() { - m.app.QueueUpdateDraw(func() {}) - }) + SetTextAlign(tview.AlignmentCenter) m.msg = "Press Ctrl+N to open QR login" return m @@ -57,7 +52,7 @@ func (m *Model) HandleEvent(event tcell.Event) tview.Command { case *tview.KeyEvent: if event.Key() == tcell.KeyEsc { m.msg = "Canceled" - return tview.BatchCommand{m.close(), tview.RedrawCommand{}} + return tview.Batch(m.close(), nil) } return m.TextView.HandleEvent(event) @@ -71,21 +66,21 @@ func (m *Model) HandleEvent(event tcell.Event) tview.Command { case *helloEvent: m.heartbeatInterval = time.Duration(event.heartbeatInterval) * time.Millisecond - return tview.BatchCommand{m.listen(), m.heartbeat(), m.generatePrivateKey()} + return tview.Batch(m.listen(), m.heartbeat(), m.generatePrivateKey()) case *privateKeyEvent: m.privateKey = event.privateKey - return tview.BatchCommand{m.listen(), m.sendInit()} + return tview.Batch(m.listen(), m.sendInit()) case *nonceProofEvent: - return tview.BatchCommand{m.listen(), m.sendNonceProof(event.encryptedNonce)} + return tview.Batch(m.listen(), m.sendNonceProof(event.encryptedNonce)) case *pendingRemoteInitEvent: m.fingerprint = event.fingerprint - return tview.BatchCommand{m.listen(), m.generateQRCode(event.fingerprint)} + return tview.Batch(m.listen(), m.generateQRCode(event.fingerprint)) case *qrCodeEvent: m.qrCode = event.qrCode m.msg = "Scan this with the Discord mobile app to log in instantly." return m.listen() case *pendingTicketEvent: - return tview.BatchCommand{m.listen(), m.decryptUserPayload(event.encryptedUserPayload)} + return tview.Batch(m.listen(), m.decryptUserPayload(event.encryptedUserPayload)) case *userEvent: name := event.username if event.discriminator != "0" { @@ -95,7 +90,7 @@ func (m *Model) HandleEvent(event tcell.Event) tview.Command { return m.listen() case *pendingLoginEvent: m.msg = "Authenticating..." - return tview.BatchCommand{m.close(), m.exchangeTicket(event.ticket)} + return tview.Batch(m.close(), m.exchangeTicket(event.ticket)) case *cancelEvent: m.msg = "Login canceled on mobile" return m.close() @@ -104,11 +99,11 @@ func (m *Model) HandleEvent(event tcell.Event) tview.Command { if m.conn == nil { return nil } - return tview.BatchCommand{m.heartbeat(), m.sendHeartbeat()} + return tview.Batch(m.heartbeat(), m.sendHeartbeat()) case *tcell.EventError: m.msg = event.Error() - return tview.BatchCommand{m.close(), event} + return tview.Batch(m.close(), tview.Command(func() tcell.Event { return event })) } return nil diff --git a/internal/ui/login/token/events.go b/internal/ui/login/token/events.go index abf1c68..3e772f8 100644 --- a/internal/ui/login/token/events.go +++ b/internal/ui/login/token/events.go @@ -10,14 +10,10 @@ type TokenEvent struct { Token string } -func newTokenEvent(token string) *TokenEvent { - event := &TokenEvent{Token: token} - event.SetEventNow() - return event -} - func tokenCommand(token string) tview.Command { - return tview.EventCommand(func() tcell.Event { - return newTokenEvent(token) - }) + return func() tcell.Event { + event := &TokenEvent{Token: token} + event.SetEventNow() + return event + } } diff --git a/internal/ui/root/events.go b/internal/ui/root/events.go index 4b210cb..bf6bcda 100644 --- a/internal/ui/root/events.go +++ b/internal/ui/root/events.go @@ -14,63 +14,57 @@ type tokenEvent struct { token string } -func newTokenEvent(token string) *tokenEvent { - event := &tokenEvent{token: token} - event.SetEventNow() - return event -} - func tokenCommand(token string) tview.Command { - return tview.EventCommand(func() tcell.Event { - return newTokenEvent(token) - }) + return func() tcell.Event { + event := &tokenEvent{token: token} + event.SetEventNow() + return event + } } type loginEvent struct{ tcell.EventTime } -func newLoginEvent() *loginEvent { - event := &loginEvent{} - event.SetEventNow() - return event -} - func getToken() tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { token, err := keyring.GetToken() if err != nil { slog.Info("failed to retrieve token from keyring", "err", err) - return newLoginEvent() + event := &loginEvent{} + event.SetEventNow() + return event } - return newTokenEvent(token) - }) + event := &tokenEvent{token: token} + event.SetEventNow() + return event + } } func setToken(token string) tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { if err := keyring.SetToken(token); err != nil { slog.Error("failed to set token to keyring", "err", err) return tcell.NewEventError(err) } return nil - }) + } } func deleteToken() tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { if err := keyring.DeleteToken(); err != nil { slog.Error("failed to delete token from keyring", "err", err) return tcell.NewEventError(err) } return nil - }) + } } func initClipboard() tview.Command { - return tview.EventCommand(func() tcell.Event { + return func() tcell.Event { if err := clipboard.Init(); err != nil { slog.Error("failed to init clipboard", "err", err) return tcell.NewEventError(err) } return nil - }) + } } diff --git a/internal/ui/root/model.go b/internal/ui/root/model.go index 150029c..532654e 100644 --- a/internal/ui/root/model.go +++ b/internal/ui/root/model.go @@ -53,15 +53,15 @@ func NewModel(cfg *config.Config, app *tview.Application) *Model { } func (m *Model) showLogin() tview.Command { - m.inner = login.NewModel(m.app, m.cfg) + m.inner = login.NewModel(m.cfg) m.buildLayout() - return tview.BatchCommand{m.inner.HandleEvent(tview.NewInitEvent()), tview.SetFocusCommand{Target: m}} + return tview.Batch(m.inner.HandleEvent(tview.NewInitEvent()), tview.SetFocus(m)) } func (m *Model) showChat(token string) tview.Command { m.inner = chat.NewView(m.app, m.cfg, token) m.buildLayout() - return tview.BatchCommand{m.inner.HandleEvent(tview.NewInitEvent()), tview.SetFocusCommand{Target: m}} + return tview.Batch(m.inner.HandleEvent(tview.NewInitEvent()), tview.SetFocus(m)) } func (m *Model) buildLayout() { @@ -88,11 +88,11 @@ func (m *Model) HandleEvent(event tcell.Event) tview.Command { } else { cmd = getToken() } - return tview.BatchCommand{ + return tview.Batch( tview.SetTitle(consts.Name), initClipboard(), cmd, - } + ) case *loginEvent: return m.showLogin() @@ -100,22 +100,22 @@ func (m *Model) HandleEvent(event tcell.Event) tview.Command { return m.showChat(event.token) case *token.TokenEvent: - return tview.BatchCommand{m.showChat(event.Token), setToken(event.Token)} + return tview.Batch(m.showChat(event.Token), setToken(event.Token)) case *qr.TokenEvent: - return tview.BatchCommand{m.showChat(event.Token), setToken(event.Token)} + return tview.Batch(m.showChat(event.Token), setToken(event.Token)) case *chat.LogoutEvent: - return tview.BatchCommand{ + return tview.Batch( m.showLogin(), deleteToken(), - } + ) case *tview.KeyEvent: switch { case keybind.Matches(event, m.cfg.Keybinds.ToggleHelp.Keybind): m.help.SetShowAll(!m.help.ShowAll()) m.updateHelpHeight() - return tview.RedrawCommand{} + return nil case keybind.Matches(event, m.cfg.Keybinds.Suspend.Keybind): m.suspend() return nil @@ -124,7 +124,7 @@ func (m *Model) HandleEvent(event tcell.Event) tview.Command { if m.inner != nil { innerCmd = m.inner.HandleEvent(chat.NewQuitEvent()) } - return tview.BatchCommand{innerCmd, tview.Quit()} + return tview.Batch(innerCmd, tview.Quit()) } } diff --git a/pkg/picker/picker.go b/pkg/picker/picker.go index 3ee1e7b..1fe259b 100644 --- a/pkg/picker/picker.go +++ b/pkg/picker/picker.go @@ -151,29 +151,28 @@ func (p *Picker) onInputChanged(text string) { func (p *Picker) HandleEvent(event tcell.Event) tview.Command { switch event := event.(type) { case *tview.KeyEvent: - redraw := tview.RedrawCommand{} if p.keyMap != nil { switch { case keybind.Matches(event, p.keyMap.Up): p.list.HandleEvent(tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone)) - return redraw + return nil case keybind.Matches(event, p.keyMap.Down): p.list.HandleEvent(tcell.NewEventKey(tcell.KeyDown, "", tcell.ModNone)) - return redraw + return nil case keybind.Matches(event, p.keyMap.Top): p.list.HandleEvent(tcell.NewEventKey(tcell.KeyHome, "", tcell.ModNone)) - return redraw + return nil case keybind.Matches(event, p.keyMap.Bottom): p.list.HandleEvent(tcell.NewEventKey(tcell.KeyEnd, "", tcell.ModNone)) - return redraw + return nil case keybind.Matches(event, p.keyMap.Select): p.onListSelected(p.list.Cursor()) - return redraw + return nil case keybind.Matches(event, p.keyMap.Cancel): if p.onCancel != nil { p.onCancel() } - return redraw + return nil } }