diff --git a/go.mod b/go.mod index 73b9163..34fbfea 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ 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-20260315074416-50a7d9c11304 + github.com/ayn2op/tview v0.0.0-20260318010246-0993cedfe0c2 github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb github.com/diamondburned/arikawa/v3 v3.6.1-0.20260311205148-176ad9b9440f github.com/diamondburned/ningen/v3 v3.0.1-0.20260306213430-5a08d3a709b4 diff --git a/go.sum b/go.sum index 06e069b..8b4f54c 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-20260315074416-50a7d9c11304 h1:IbClNDuGoPrJtP4WkKr/iLXIxsR1WKSSJH46wLM0azM= -github.com/ayn2op/tview v0.0.0-20260315074416-50a7d9c11304/go.mod h1:IXZrCv1VAZyWPdKDUJYs9ZYFiJ1GmCQUo9tLjQWoSnU= +github.com/ayn2op/tview v0.0.0-20260318010246-0993cedfe0c2 h1:AMOGrvQ1+TnDRuKC2Jnr+H1WL4bq8GSNd2GDxvy3dig= +github.com/ayn2op/tview v0.0.0-20260318010246-0993cedfe0c2/go.mod h1:IXZrCv1VAZyWPdKDUJYs9ZYFiJ1GmCQUo9tLjQWoSnU= 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= diff --git a/internal/ui/chat/attachments_picker.go b/internal/ui/chat/attachments_picker.go index ff15853..dbcdc64 100644 --- a/internal/ui/chat/attachments_picker.go +++ b/internal/ui/chat/attachments_picker.go @@ -7,7 +7,6 @@ import ( "github.com/ayn2op/tview" "github.com/ayn2op/tview/help" "github.com/ayn2op/tview/keybind" - "github.com/gdamore/tcell/v3" ) type attachmentItem struct { @@ -74,7 +73,7 @@ func (ap *attachmentsPicker) close() { ap.chatView.app.SetFocus(ap.chatView.messagesList) } -func (ap *attachmentsPicker) HandleEvent(event tcell.Event) tview.Command { +func (ap *attachmentsPicker) HandleEvent(event tview.Event) tview.Command { switch event := event.(type) { case *picker.SelectedEvent: index, ok := event.Reference.(int) diff --git a/internal/ui/chat/channels_picker.go b/internal/ui/chat/channels_picker.go index e1446df..1cb39ac 100644 --- a/internal/ui/chat/channels_picker.go +++ b/internal/ui/chat/channels_picker.go @@ -11,7 +11,6 @@ import ( "github.com/ayn2op/tview/help" "github.com/ayn2op/tview/keybind" "github.com/diamondburned/arikawa/v3/discord" - "github.com/gdamore/tcell/v3" ) type channelsPicker struct { @@ -52,7 +51,7 @@ func newChannelsPicker(cfg *config.Config, chatView *Model) *channelsPicker { return cp } -func (cp *channelsPicker) HandleEvent(event tcell.Event) tview.Command { +func (cp *channelsPicker) HandleEvent(event tview.Event) tview.Command { switch event := event.(type) { case *picker.SelectedEvent: channelID, ok := event.Reference.(discord.ChannelID) diff --git a/internal/ui/chat/events.go b/internal/ui/chat/events.go index e0032ad..77e92c7 100644 --- a/internal/ui/chat/events.go +++ b/internal/ui/chat/events.go @@ -10,7 +10,7 @@ import ( type LogoutEvent struct{ tcell.EventTime } func (m *Model) logout() tview.Command { - return func() tcell.Event { + return func() tview.Event { return &LogoutEvent{} } } @@ -18,7 +18,7 @@ func (m *Model) logout() tview.Command { type QuitEvent struct{ tcell.EventTime } func (m *Model) closeState() tview.Command { - return func() tcell.Event { + return func() tview.Event { if m.state != nil { if err := m.state.Close(); err != nil { slog.Error("failed to close the session", "err", err) @@ -35,7 +35,7 @@ type closeLayerEvent struct { } func closeLayer(name string) tview.Command { - return func() tcell.Event { + return func() tview.Event { return &closeLayerEvent{name: name} } } diff --git a/internal/ui/chat/guilds_tree.go b/internal/ui/chat/guilds_tree.go index 8d687af..fd6e57e 100644 --- a/internal/ui/chat/guilds_tree.go +++ b/internal/ui/chat/guilds_tree.go @@ -393,7 +393,7 @@ func (gt *guildsTree) collapseParentNode(node *tview.TreeNode) { }) } -func (gt *guildsTree) HandleEvent(event tcell.Event) tview.Command { +func (gt *guildsTree) HandleEvent(event tview.Event) tview.Command { switch event := event.(type) { case *tview.TreeViewSelectedEvent: gt.onSelected(event.Node) @@ -437,7 +437,7 @@ func (gt *guildsTree) yankID() tview.Command { // Reference of a tree node in the guilds tree is its ID. // discord.Snowflake (discord.GuildID and discord.ChannelID) have the String method. if id, ok := node.GetReference().(fmt.Stringer); ok { - return func() tcell.Event { + return func() tview.Event { if err := clipboard.Write(clipboard.FmtText, []byte(id.String())); err != nil { slog.Error("failed to copy node id", "err", err) } diff --git a/internal/ui/chat/message_input.go b/internal/ui/chat/message_input.go index d1c48cc..e94ffda 100644 --- a/internal/ui/chat/message_input.go +++ b/internal/ui/chat/message_input.go @@ -103,7 +103,7 @@ func (mi *messageInput) stopTypingTimer() { } } -func (mi *messageInput) HandleEvent(event tcell.Event) tview.Command { +func (mi *messageInput) HandleEvent(event tview.Event) tview.Command { handler := mi.TextArea.HandleEvent switch event := event.(type) { case *tview.KeyEvent: diff --git a/internal/ui/chat/messages_list.go b/internal/ui/chat/messages_list.go index 37de56f..81499fc 100644 --- a/internal/ui/chat/messages_list.go +++ b/internal/ui/chat/messages_list.go @@ -838,7 +838,7 @@ func (ml *messagesList) selectedMessage() (*discord.Message, error) { return &ml.messages[cursor], nil } -func (ml *messagesList) HandleEvent(event tcell.Event) tview.Command { +func (ml *messagesList) HandleEvent(event tview.Event) tview.Command { switch event := event.(type) { case *tview.KeyEvent: switch { @@ -1018,7 +1018,7 @@ func (ml *messagesList) yankMessageID() tview.Command { return nil } - return func() tcell.Event { + return func() tview.Event { if err := clipboard.Write(clipboard.FmtText, []byte(msg.ID.String())); err != nil { slog.Error("failed to copy message id", "err", err) } @@ -1033,7 +1033,7 @@ func (ml *messagesList) yankContent() tview.Command { return nil } - return func() tcell.Event { + return func() tview.Event { if err := clipboard.Write(clipboard.FmtText, []byte(msg.Content)); err != nil { slog.Error("failed to copy message content", "err", err) } @@ -1048,7 +1048,7 @@ func (ml *messagesList) yankURL() tview.Command { return nil } - return func() tcell.Event { + return func() tview.Event { if err := clipboard.Write(clipboard.FmtText, []byte(msg.URL())); err != nil { slog.Error("failed to copy message url", "err", err) } @@ -1289,7 +1289,7 @@ func (ml *messagesList) deleteSelectedMessage() tview.Command { return nil } - return func() tcell.Event { + return func() tview.Event { if selectedMessage.GuildID.IsValid() { me, _ := ml.chatView.state.Cabinet.Me() if selectedMessage.Author.ID != me.ID && !ml.chatView.state.HasPermissions(selectedMessage.ChannelID, discord.PermissionManageMessages) { diff --git a/internal/ui/chat/model.go b/internal/ui/chat/model.go index e777f43..b19fb4b 100644 --- a/internal/ui/chat/model.go +++ b/internal/ui/chat/model.go @@ -48,7 +48,7 @@ type Model struct { typers map[discord.UserID]*time.Timer confirmModalDone func(label string) - confirmModalPreviousFocus tview.Primitive + confirmModalPreviousFocus tview.Model app *tview.Application cfg *config.Config @@ -216,10 +216,10 @@ func (m *Model) focusNext() tview.Command { return nil } -func (m *Model) HandleEvent(event tcell.Event) tview.Command { +func (m *Model) HandleEvent(event tview.Event) tview.Command { switch event := event.(type) { case *tview.InitEvent: - return func() tcell.Event { + return func() tview.Event { if err := m.OpenState(m.token); err != nil { slog.Error("failed to open chat state", "err", err) return tcell.NewEventError(err) diff --git a/internal/ui/login/events.go b/internal/ui/login/events.go index 14105a6..1e3508b 100644 --- a/internal/ui/login/events.go +++ b/internal/ui/login/events.go @@ -9,7 +9,7 @@ import ( ) func setClipboard(content string) tview.Command { - return func() tcell.Event { + return func() tview.Event { if err := clipboard.Write(clipboard.FmtText, []byte(content)); err != nil { slog.Error("failed to copy error message", "err", err) return tcell.NewEventError(err) diff --git a/internal/ui/login/model.go b/internal/ui/login/model.go index 87f46f0..a6edcd8 100644 --- a/internal/ui/login/model.go +++ b/internal/ui/login/model.go @@ -41,7 +41,7 @@ func NewModel(cfg *config.Config) *Model { } } -func (m *Model) HandleEvent(event tcell.Event) tview.Command { +func (m *Model) HandleEvent(event tview.Event) tview.Command { switch event := event.(type) { case *tcell.EventError: if m.HasLayer(errorLayerName) { diff --git a/internal/ui/login/qr/events.go b/internal/ui/login/qr/events.go index 0affd0d..d254fca 100644 --- a/internal/ui/login/qr/events.go +++ b/internal/ui/login/qr/events.go @@ -34,7 +34,7 @@ type connCreateEvent struct { type connCloseEvent struct{ tcell.EventTime } func (m *Model) connect() tview.Command { - return func() tcell.Event { + return func() tview.Event { headers := http.Headers() headers.Set("User-Agent", http.BrowserUserAgent) conn, _, err := websocket.DefaultDialer.Dial(remoteAuthGatewayURL, headers) @@ -46,7 +46,7 @@ func (m *Model) connect() tview.Command { } func (m *Model) close() tview.Command { - return func() tcell.Event { + return func() tview.Event { if m.conn != nil { if err := m.conn.Close(); err != nil { return tcell.NewEventError(err) @@ -85,7 +85,7 @@ type pendingLoginEvent struct { type cancelEvent struct{ tcell.EventTime } func (m *Model) listen() tview.Command { - return func() tcell.Event { + return func() tview.Event { if m.conn == nil { return nil } @@ -155,14 +155,14 @@ func (m *Model) listen() tview.Command { type heartbeatTickEvent struct{ tcell.EventTime } func (m *Model) heartbeat() tview.Command { - return func() tcell.Event { + return func() tview.Event { time.Sleep(m.heartbeatInterval) return &heartbeatTickEvent{} } } func (m *Model) sendHeartbeat() tview.Command { - return func() tcell.Event { + return func() tview.Event { if m.conn == nil { return nil } @@ -182,7 +182,7 @@ type privateKeyEvent struct { } func (m *Model) generatePrivateKey() tview.Command { - return func() tcell.Event { + return func() tview.Event { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return tcell.NewEventError(err) @@ -192,7 +192,7 @@ func (m *Model) generatePrivateKey() tview.Command { } func (m *Model) sendInit() tview.Command { - return func() tcell.Event { + return func() tview.Event { if m.privateKey == nil { return tcell.NewEventError(errors.New("missing private key")) } @@ -213,7 +213,7 @@ func (m *Model) sendInit() tview.Command { } func (m *Model) sendNonceProof(encryptedNonce string) tview.Command { - return func() tcell.Event { + return func() tview.Event { decodedNonce, err := base64.StdEncoding.DecodeString(encryptedNonce) if err != nil { return tcell.NewEventError(err) @@ -242,7 +242,7 @@ type qrCodeEvent struct { } func (m *Model) generateQRCode(fingerprint string) tview.Command { - return func() tcell.Event { + return func() tview.Event { content := "https://discord.com/ra/" + fingerprint qrCode, err := qrcode.New(content, qrcode.Low) if err != nil { @@ -260,7 +260,7 @@ type userEvent struct { } func (m *Model) decryptUserPayload(encryptedPayload string) tview.Command { - return func() tcell.Event { + return func() tview.Event { decodedPayload, err := base64.StdEncoding.DecodeString(encryptedPayload) if err != nil { return tcell.NewEventError(err) @@ -281,7 +281,7 @@ func (m *Model) decryptUserPayload(encryptedPayload string) tview.Command { } func (m *Model) exchangeTicket(ticket string) tview.Command { - return func() tcell.Event { + return func() tview.Event { headers := http.Headers() headers.Set("Referer", "https://discord.com/login") if m.fingerprint != "" { diff --git a/internal/ui/login/qr/model.go b/internal/ui/login/qr/model.go index 0e3d7d3..221404f 100644 --- a/internal/ui/login/qr/model.go +++ b/internal/ui/login/qr/model.go @@ -44,7 +44,7 @@ func (m *Model) Label() string { return "QR" } -func (m *Model) HandleEvent(event tcell.Event) tview.Command { +func (m *Model) HandleEvent(event tview.Event) tview.Command { switch event := event.(type) { case *tview.InitEvent: m.msg = "Connecting to Remote Auth Gateway..." @@ -103,7 +103,7 @@ func (m *Model) HandleEvent(event tcell.Event) tview.Command { case *tcell.EventError: m.msg = event.Error() - return tview.Batch(m.close(), tview.Command(func() tcell.Event { return event })) + return tview.Batch(m.close(), tview.Command(func() tview.Event { return event })) } return nil diff --git a/internal/ui/login/token/events.go b/internal/ui/login/token/events.go index e321018..e906393 100644 --- a/internal/ui/login/token/events.go +++ b/internal/ui/login/token/events.go @@ -11,7 +11,7 @@ type TokenEvent struct { } func tokenCommand(token string) tview.Command { - return func() tcell.Event { + return func() tview.Event { return &TokenEvent{Token: token} } } diff --git a/internal/ui/login/token/model.go b/internal/ui/login/token/model.go index 03d9a6e..72256d1 100644 --- a/internal/ui/login/token/model.go +++ b/internal/ui/login/token/model.go @@ -3,7 +3,6 @@ package token import ( "github.com/ayn2op/tview" "github.com/ayn2op/tview/tabs" - "github.com/gdamore/tcell/v3" ) type Model struct { @@ -23,7 +22,7 @@ func (m *Model) Label() string { return "Token" } -func (m *Model) HandleEvent(event tcell.Event) tview.Command { +func (m *Model) HandleEvent(event tview.Event) tview.Command { switch event.(type) { case *tview.FormSubmitEvent: token := m.GetFormItem(0).(*tview.InputField).GetText() diff --git a/internal/ui/root/events.go b/internal/ui/root/events.go index 7051a5d..87627b9 100644 --- a/internal/ui/root/events.go +++ b/internal/ui/root/events.go @@ -15,7 +15,7 @@ type tokenEvent struct { } func tokenCommand(token string) tview.Command { - return func() tcell.Event { + return func() tview.Event { return &tokenEvent{token: token} } } @@ -23,7 +23,7 @@ func tokenCommand(token string) tview.Command { type loginEvent struct{ tcell.EventTime } func getToken() tview.Command { - return func() tcell.Event { + return func() tview.Event { token, err := keyring.GetToken() if err != nil { slog.Info("failed to retrieve token from keyring", "err", err) @@ -34,7 +34,7 @@ func getToken() tview.Command { } func setToken(token string) tview.Command { - return func() tcell.Event { + return func() tview.Event { if err := keyring.SetToken(token); err != nil { slog.Error("failed to set token to keyring", "err", err) return tcell.NewEventError(err) @@ -44,7 +44,7 @@ func setToken(token string) tview.Command { } func deleteToken() tview.Command { - return func() tcell.Event { + return func() tview.Event { if err := keyring.DeleteToken(); err != nil { slog.Error("failed to delete token from keyring", "err", err) return tcell.NewEventError(err) @@ -54,7 +54,7 @@ func deleteToken() tview.Command { } func initClipboard() tview.Command { - return func() tcell.Event { + return func() tview.Event { if err := clipboard.Init(); err != nil { slog.Error("failed to init clipboard", "err", err) return tcell.NewEventError(err) diff --git a/internal/ui/root/model.go b/internal/ui/root/model.go index 53553c1..b424c50 100644 --- a/internal/ui/root/model.go +++ b/internal/ui/root/model.go @@ -21,7 +21,7 @@ const tokenEnvVarKey = "DISCORDO_TOKEN" type Model struct { app *tview.Application rootFlex *flex.Model // inner + help - inner tview.Primitive + inner tview.Model help *help.Help cfg *config.Config @@ -74,13 +74,13 @@ func (m *Model) buildLayout() { m.updateHelpHeight() } -var _ tview.Primitive = (*Model)(nil) +var _ tview.Model = (*Model)(nil) func (m *Model) Draw(screen tcell.Screen) { m.rootFlex.Draw(screen) } -func (m *Model) HandleEvent(event tcell.Event) tview.Command { +func (m *Model) HandleEvent(event tview.Event) tview.Command { switch event := event.(type) { case *tview.InitEvent: var cmd tview.Command @@ -151,7 +151,7 @@ func (m *Model) SetRect(x int, y int, width int, height int) { m.rootFlex.SetRect(x, y, width, height) } -func (m *Model) Focus(delegate func(p tview.Primitive)) { +func (m *Model) Focus(delegate func(tview.Model)) { if m.inner != nil { delegate(m.inner) } diff --git a/internal/ui/util.go b/internal/ui/util.go index 56f05dd..f9481af 100644 --- a/internal/ui/util.go +++ b/internal/ui/util.go @@ -55,11 +55,11 @@ func ConfigureBox(box *tview.Box, cfg *config.Theme) *tview.Box { } // Centered creates a new grid with provided primitive aligned in the center. -func Centered(p tview.Primitive, width, height int) tview.Primitive { +func Centered(m tview.Model, width, height int) tview.Model { return tview.NewGrid(). SetColumns(0, width, 0). SetRows(0, height, 0). - AddItem(p, 1, 1, 1, 1, 0, 0, true) + AddItem(m, 1, 1, 1, 1, 0, 0, true) } func ChannelToString(channel discord.Channel, icons config.Icons, state *ningen.State) string { diff --git a/pkg/picker/events.go b/pkg/picker/events.go index 2622b18..9ffd4ea 100644 --- a/pkg/picker/events.go +++ b/pkg/picker/events.go @@ -18,7 +18,7 @@ func (m *Model) _select() tview.Command { index := m.list.Cursor() if index >= 0 && index < len(m.filtered) { item := m.filtered[index] - return func() tcell.Event { + return func() tview.Event { return newSelectedEvent(item) } } @@ -28,7 +28,7 @@ func (m *Model) _select() tview.Command { type CancelEvent struct{ tcell.EventTime } func cancel() tview.Command { - return func() tcell.Event { + return func() tview.Event { return &CancelEvent{} } } diff --git a/pkg/picker/model.go b/pkg/picker/model.go index bfeeefb..0b9d082 100644 --- a/pkg/picker/model.go +++ b/pkg/picker/model.go @@ -128,7 +128,7 @@ func (m *Model) onInputChanged(text string) { m.setFilteredItems(fuzzied) } -func (m *Model) HandleEvent(event tcell.Event) tview.Command { +func (m *Model) HandleEvent(event tview.Event) tview.Command { switch event := event.(type) { case *tview.KeyEvent: if m.keyMap != nil {