Răsfoiți Sursa

Merge branch 'main' into update-preview

ayn2op 2 luni în urmă
părinte
comite
06ee7fdad1

+ 1 - 1
go.mod

@@ -8,7 +8,7 @@ require (
 	github.com/BurntSushi/toml v1.6.0
 	github.com/alecthomas/chroma/v2 v2.23.1
 	github.com/andybalholm/brotli v1.2.0
-	github.com/ayn2op/tview v0.0.0-20260223065535-6a0c066d0bdd
+	github.com/ayn2op/tview v0.0.0-20260223211434-6859308d597f
 	github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb
 	github.com/diamondburned/arikawa/v3 v3.6.1-0.20260221051847-b81b70d1a5cb
 	github.com/diamondburned/ningen/v3 v3.0.1-0.20250920191746-98fbd92e134d

+ 2 - 2
go.sum

@@ -14,8 +14,8 @@ github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs
 github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
 github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
-github.com/ayn2op/tview v0.0.0-20260223065535-6a0c066d0bdd h1:DdR19f4xNbd3XM9vxmC9LtF5XY1nv0rDMMzn0FSTwd0=
-github.com/ayn2op/tview v0.0.0-20260223065535-6a0c066d0bdd/go.mod h1:lZ8RdOegQWBQafTOasGE7Ps1/Ymy4jmXoPt5vz2QsS0=
+github.com/ayn2op/tview v0.0.0-20260223211434-6859308d597f h1:ZxDODg1E/RPoTWlXhnhZOKITe1ebBM0Cpy+GiGaa+xk=
+github.com/ayn2op/tview v0.0.0-20260223211434-6859308d597f/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=

+ 3 - 0
internal/app/app.go

@@ -102,6 +102,9 @@ func (a *App) quit() {
 
 func (a *App) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
 	switch {
+	case keybind.Matches(event, a.cfg.Keybinds.Suspend.Keybind):
+		a.suspend()
+		return nil
 	case keybind.Matches(event, a.cfg.Keybinds.Quit.Keybind):
 		a.quit()
 		return nil

+ 5 - 0
internal/app/suspend_default.go

@@ -0,0 +1,5 @@
+//go:build !unix
+
+package app
+
+func (a *App) suspend() {}

+ 20 - 0
internal/app/suspend_unix.go

@@ -0,0 +1,20 @@
+//go:build unix
+
+package app
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+)
+
+func (a *App) suspend() {
+	a.inner.Suspend(func() {
+		c := make(chan os.Signal, 1)
+		signal.Notify(c, syscall.SIGCONT)
+		defer signal.Stop(c)
+
+		_ = syscall.Kill(0, syscall.SIGTSTP)
+		<-c
+	})
+}

+ 11 - 0
internal/config/config.go

@@ -74,6 +74,16 @@ type (
 		Separator        string `toml:"separator"`
 	}
 
+	SidebarMarkersConfig struct {
+		Expanded  string `toml:"expanded"`
+		Collapsed string `toml:"collapsed"`
+		Leaf      string `toml:"leaf"`
+	}
+
+	SidebarConfig struct {
+		Markers SidebarMarkersConfig `toml:"markers"`
+	}
+
 	Config struct {
 		AutoFocus bool   `toml:"auto_focus"`
 		Mouse     bool   `toml:"mouse"`
@@ -94,6 +104,7 @@ type (
 		DateSeparator   DateSeparator   `toml:"date_separator"`
 		Notifications   Notifications   `toml:"notifications"`
 		TypingIndicator TypingIndicator `toml:"typing_indicator"`
+		Sidebar         SidebarConfig   `toml:"sidebar"`
 
 		Icons Icons `toml:"icons"`
 

+ 8 - 1
internal/config/config.toml

@@ -63,6 +63,11 @@ send = true
 # Whether to receive typing status or not.
 receive = true
 
+[sidebar.markers]
+expanded = "▾ "
+collapsed = "▸ "
+leaf = ""
+
 [icons]
 guild_category = ""
 guild_text = "#"
@@ -87,10 +92,11 @@ focus_previous = "ctrl+h"
 focus_next = "ctrl+l"
 # Hide/show the guilds tree.
 toggle_guilds_tree = "ctrl+b"
-quit = "ctrl+c"
 # Log out and remove the authentication token from keyring.
 # Requires re-login upon restart.
 logout = "ctrl+d"
+suspend = "ctrl+z"
+quit = "ctrl+c"
 
 [keybinds.picker]
 toggle = "ctrl+k"
@@ -152,6 +158,7 @@ send = "enter"
 cancel = "esc"
 # Complete usernames when mentioning
 tab_complete = "tab"
+undo = "ctrl+u"
 
 open_editor = "ctrl+e"
 open_file_picker = "ctrl+\\"

+ 4 - 0
internal/config/keybinds.go

@@ -96,6 +96,7 @@ type MessageInputKeybinds struct {
 	Send        Keybind `toml:"send"`
 	Cancel      Keybind `toml:"cancel"`
 	TabComplete Keybind `toml:"tab_complete"`
+	Undo        Keybind `toml:"undo"`
 
 	OpenEditor     Keybind `toml:"open_editor"`
 	OpenFilePicker Keybind `toml:"open_file_picker"`
@@ -109,6 +110,7 @@ type Keybinds struct {
 	ToggleGuildsTree     Keybind `toml:"toggle_guilds_tree"`
 	ToggleChannelsPicker Keybind `toml:"toggle_channels_picker"`
 	ToggleHelp           Keybind `toml:"toggle_help"`
+	Suspend              Keybind `toml:"suspend"`
 
 	FocusGuildsTree   Keybind `toml:"focus_guilds_tree"`
 	FocusMessagesList Keybind `toml:"focus_messages_list"`
@@ -132,6 +134,7 @@ func defaultKeybinds() Keybinds {
 		ToggleGuildsTree:     newKeybind("ctrl+b", "toggle guilds"),
 		ToggleChannelsPicker: newKeybind("ctrl+k", "channels picker"),
 		ToggleHelp:           newKeybind("ctrl+.", "help"),
+		Suspend:              newKeybind("ctrl+z", "suspend"),
 
 		FocusGuildsTree:   newKeybind("ctrl+g", "guilds"),
 		FocusMessagesList: newKeybind("ctrl+t", "messages"),
@@ -198,6 +201,7 @@ func defaultKeybinds() Keybinds {
 			Send:           newKeybind("enter", "send"),
 			Cancel:         newKeybind("esc", "cancel"),
 			TabComplete:    newKeybind("tab", "complete"),
+			Undo:           newKeybind("ctrl+u", "undo"),
 			OpenEditor:     newKeybind("ctrl+e", "editor"),
 			OpenFilePicker: newKeybind("ctrl+\\", "attach"),
 		},

+ 9 - 1
internal/ui/chat/guilds_tree.go

@@ -47,6 +47,11 @@ func newGuildsTree(cfg *config.Config, chatView *View) *guildsTree {
 	gt.
 		SetRoot(tview.NewTreeNode("")).
 		SetTopLevel(1).
+		SetMarkers(tview.TreeMarkers{
+			Expanded:  cfg.Sidebar.Markers.Expanded,
+			Collapsed: cfg.Sidebar.Markers.Collapsed,
+			Leaf:      cfg.Sidebar.Markers.Leaf,
+		}).
 		SetGraphics(cfg.Theme.GuildsTree.Graphics).
 		SetGraphicsColor(tcell.GetColor(cfg.Theme.GuildsTree.GraphicsColor)).
 		SetSelectedFunc(gt.onSelected).
@@ -189,7 +194,7 @@ func (gt *guildsTree) getChannelNodeStyle(channelID discord.ChannelID) tcell.Sty
 }
 
 func (gt *guildsTree) createGuildNode(n *tview.TreeNode, guild discord.Guild) {
-	guildNode := tview.NewTreeNode(guild.Name).SetReference(guild.ID)
+	guildNode := tview.NewTreeNode(guild.Name).SetReference(guild.ID).SetExpandable(true)
 	gt.setNodeLineStyle(guildNode, gt.getGuildNodeStyle(guild.ID))
 	n.AddChild(guildNode)
 	gt.guildNodeByID[guild.ID] = guildNode
@@ -201,6 +206,9 @@ func (gt *guildsTree) createChannelNode(node *tview.TreeNode, channel discord.Ch
 	}
 
 	channelNode := tview.NewTreeNode(ui.ChannelToString(channel, gt.cfg.Icons)).SetReference(channel.ID)
+	if channel.Type == discord.GuildForum {
+		channelNode.SetExpandable(true)
+	}
 	gt.setNodeLineStyle(channelNode, gt.getChannelNodeStyle(channel.ID))
 	node.AddChild(channelNode)
 	gt.channelNodeByID[channel.ID] = channelNode

+ 1 - 1
internal/ui/chat/keybinds.go

@@ -66,6 +66,6 @@ func (v *View) baseFullHelp() [][]keybind.Keybind {
 		focus,
 		{cfg.FocusPrevious.Keybind, cfg.FocusNext.Keybind},
 		{cfg.ToggleGuildsTree.Keybind, cfg.ToggleChannelsPicker.Keybind},
-		{cfg.ToggleHelp.Keybind, cfg.Logout.Keybind, cfg.Quit.Keybind},
+		{cfg.ToggleHelp.Keybind, cfg.Suspend.Keybind, cfg.Logout.Keybind, cfg.Quit.Keybind},
 	}
 }

+ 3 - 1
internal/ui/chat/message_input.go

@@ -126,6 +126,8 @@ func (mi *messageInput) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
 	case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.TabComplete.Keybind):
 		go mi.chatView.app.QueueUpdateDraw(func() { mi.tabComplete() })
 		return nil
+	case keybind.Matches(event, mi.cfg.Keybinds.MessageInput.Undo.Keybind):
+		return tcell.NewEventKey(tcell.KeyCtrlZ, "", tcell.ModNone)
 	default:
 		if mi.cfg.TypingIndicator.Send && mi.typingTimer == nil {
 			mi.typingTimer = time.AfterFunc(typingDuration, func() {
@@ -717,7 +719,7 @@ func (mi *messageInput) FullHelp() [][]keybind.Keybind {
 	}
 
 	return [][]keybind.Keybind{
-		{cfg.Send.Keybind, cfg.Cancel.Keybind, cfg.TabComplete.Keybind},
+		{cfg.Send.Keybind, cfg.Cancel.Keybind, cfg.TabComplete.Keybind, cfg.Undo.Keybind},
 		openEditor,
 	}
 }

+ 1 - 1
internal/ui/chat/state.go

@@ -85,7 +85,7 @@ func (v *View) onReady(event *gateway.ReadyEvent) {
 	// retain stale pointers to detached tree nodes.
 	v.guildsTree.resetNodeIndex()
 
-	dmNode := tview.NewTreeNode("Direct Messages").SetReference(dmNode{})
+	dmNode := tview.NewTreeNode("Direct Messages").SetReference(dmNode{}).SetExpandable(true)
 	v.guildsTree.dmRootNode = dmNode
 
 	root := v.guildsTree.