ソースを参照

refactor(ui/chat): cache emoji items, use stdlib IndexByte, clear on close

Replace custom indexOf with strings.IndexByte, split update() into
loadItems() + rebuildWithFavorites() so only favorites rebuild on toggle,
and clear the cache on picker close so guild emoji refresh across channels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
claude 1 ヶ月 前
コミット
c98d66e716
2 ファイル変更29 行追加20 行削除
  1. 28 20
      internal/ui/chat/emoji_picker.go
  2. 1 0
      internal/ui/chat/model.go

+ 28 - 20
internal/ui/chat/emoji_picker.go

@@ -3,6 +3,7 @@ package chat
 import (
 	"fmt"
 	"log/slog"
+	"strings"
 
 	"github.com/ayn2op/discordo/internal/config"
 	"github.com/ayn2op/tview"
@@ -21,6 +22,9 @@ type emojiPicker struct {
 	browseMode bool
 	favorites  *emojiFavorites
 
+	// allItems is cached on picker open; only favorites change on toggle.
+	allItems []picker.Item
+
 	targetMessageID discord.MessageID
 	targetChannelID discord.ChannelID
 }
@@ -95,19 +99,29 @@ var commonEmoji = []struct {
 	{"🏳️", "white_flag surrender"},
 }
 
+// update loads all emoji items (cached) and rebuilds with favorites.
 func (ep *emojiPicker) update() {
-	items := make(picker.Items, 0, maxFavoriteEmoji+len(commonEmoji)+50)
+	if ep.allItems == nil {
+		ep.loadItems()
+	}
+	ep.rebuildWithFavorites()
+}
 
-	allItems := make(picker.Items, 0, len(commonEmoji)+50)
+// loadItems builds the static emoji list (common + guild). Called once per picker open.
+func (ep *emojiPicker) loadItems() {
+	items := make(picker.Items, 0, len(commonEmoji)+50)
 	for _, e := range commonEmoji {
-		allItems = append(allItems, picker.Item{
-			Text:       e.emoji + " " + e.names[:min(len(e.names), indexOf(e.names, ' '))],
+		firstName := e.names
+		if i := strings.IndexByte(e.names, ' '); i != -1 {
+			firstName = e.names[:i]
+		}
+		items = append(items, picker.Item{
+			Text:       e.emoji + " " + firstName,
 			FilterText: e.names,
 			Reference:  discord.APIEmoji(e.emoji),
 		})
 	}
 
-	// Guild custom emoji
 	selectedChannel := ep.chatView.SelectedChannel()
 	if selectedChannel != nil && selectedChannel.GuildID.IsValid() {
 		emojis, err := ep.chatView.state.Cabinet.Emojis(selectedChannel.GuildID)
@@ -116,7 +130,7 @@ func (ep *emojiPicker) update() {
 				if !emoji.Available {
 					continue
 				}
-				allItems = append(allItems, picker.Item{
+				items = append(items, picker.Item{
 					Text:       ":" + emoji.Name + ":",
 					FilterText: emoji.Name,
 					Reference:  emoji.APIString(),
@@ -124,14 +138,17 @@ func (ep *emojiPicker) update() {
 			}
 		}
 	}
+	ep.allItems = items
+}
 
-	// Index for favorite lookup.
-	byKey := make(map[string]picker.Item, len(allItems))
-	for _, item := range allItems {
+// rebuildWithFavorites prepends favorites to the cached item list.
+func (ep *emojiPicker) rebuildWithFavorites() {
+	byKey := make(map[string]picker.Item, len(ep.allItems))
+	for _, item := range ep.allItems {
 		byKey[emojiItemKey(item)] = item
 	}
 
-	// Prepend favorites.
+	items := make(picker.Items, 0, maxFavoriteEmoji+len(ep.allItems))
 	favSet := make(map[string]struct{})
 	for _, key := range ep.favorites.list() {
 		if item, ok := byKey[key]; ok {
@@ -144,7 +161,7 @@ func (ep *emojiPicker) update() {
 		}
 	}
 
-	for _, item := range allItems {
+	for _, item := range ep.allItems {
 		if _, isFav := favSet[emojiItemKey(item)]; !isFav {
 			items = append(items, item)
 		}
@@ -160,15 +177,6 @@ func emojiItemKey(item picker.Item) string {
 	return ""
 }
 
-func indexOf(s string, c byte) int {
-	for i := range len(s) {
-		if s[i] == c {
-			return i
-		}
-	}
-	return len(s)
-}
-
 func (ep *emojiPicker) HandleEvent(event tview.Event) tview.Command {
 	switch event := event.(type) {
 	case *tview.KeyEvent:

+ 1 - 0
internal/ui/chat/model.go

@@ -212,6 +212,7 @@ func (m *Model) openEmojiPicker(messageID discord.MessageID, channelID discord.C
 
 func (m *Model) closeEmojiPicker() {
 	m.RemoveLayer(emojiPickerLayerName)
+	m.emojiPicker.allItems = nil // clear cache so guild emoji refresh next open
 	m.emojiPicker.Update()
 }