1package ui
2
3import (
4 "slices"
5 "strings"
6 "unicode"
7
8 "github.com/bnema/dumber/internal/domain/entity"
9 "github.com/bnema/dumber/internal/ui/input"
10)
11
12// legendGroupOrder is the fixed display order of legend groups.
13var legendGroupOrder = []string{"SPLIT", "FOCUS", "SCROLL", "RESIZE", "SWITCH", "JUMP", "SELECT", "MANAGE", "MODES", "EXIT"}
14
15// legendRow is one action of a legend group.
16type legendRow struct {
17 Action string
18 Keys []string // as configured; the view formats them for display
19 Desc string
20}
21
22// legendGroup is a titled run of related actions. A group is never split
23// across columns.
24type legendGroup struct {
25 Name string
26 Rows []legendRow
27}
28
29func legendActions(mode input.Mode, workspace entity.WorkspaceConfig, session entity.SessionConfig) map[string]entity.ActionBinding {
30 switch mode {
31 case input.ModePane:
32 return workspace.PaneMode.Actions
33 case input.ModeResize:
34 return workspace.ResizeMode.Actions
35 case input.ModeVim:
36 return workspace.VimMode.Actions
37 case input.ModeTab:
38 return workspace.TabMode.Actions
39 case input.ModeSession:
40 return session.SessionMode.Actions
41 case input.ModePrefix:
42 return workspace.PrefixMode.Actions
43 default:
44 return nil
45 }
46}
47
48// buildLegendGroups arranges the bound actions of mode in legendGroupOrder,
49// each group sorted by action name. Actions without keys are left out.
50func buildLegendGroups(mode input.Mode, actions map[string]entity.ActionBinding) []legendGroup {
51 names := make(map[string][]string)
52 for name, binding := range actions {
53 if len(binding.Keys) == 0 {
54 continue
55 }
56 group := modeLegendGroup(mode, name)
57 names[group] = append(names[group], name)
58 }
59 var groups []legendGroup
60 for _, groupName := range legendGroupOrder {
61 members := names[groupName]
62 if len(members) == 0 {
63 continue
64 }
65 slices.Sort(members)
66 group := legendGroup{Name: groupName}
67 for _, name := range members {
68 binding := actions[name]
69 desc := binding.Desc
70 if desc == "" {
71 desc = strings.ReplaceAll(name, "-", " ")
72 }
73 group.Rows = append(group.Rows, legendRow{Action: name, Keys: binding.Keys, Desc: desc})
74 }
75 groups = append(groups, group)
76 }
77 return groups
78}
79
80// legendGroupHeights measures groups in rows: the title counts as one.
81func legendGroupHeights(groups []legendGroup) []int {
82 heights := make([]int, len(groups))
83 for i, group := range groups {
84 heights[i] = 1 + len(group.Rows)
85 }
86 return heights
87}
88
89func displayLegendKey(key string) string {
90 switch strings.ToLower(key) {
91 case "arrowright":
92 return "→"
93 case "arrowleft":
94 return "←"
95 case "arrowup":
96 return "↑"
97 case "arrowdown":
98 return "↓"
99 case "escape":
100 return "esc"
101 case "enter":
102 return "↵"
103 default:
104 return key
105 }
106}
107
108func legendSequenceMatches(pending string, keys []string) bool {
109 sequence := strings.TrimLeftFunc(pending, unicode.IsDigit)
110 if sequence == "" {
111 return true
112 }
113 for _, key := range keys {
114 if strings.HasPrefix(key, sequence) {
115 return true
116 }
117 }
118 return false
119}
120
121// vimLegendGroup returns the legend group of a Vim Mode action, or "" when the
122// action belongs to a generic group.
123func vimLegendGroup(name string) string {
124 switch {
125 case strings.HasPrefix(name, "hint-") || strings.HasPrefix(name, "yank-") || name == "visual":
126 return "SELECT"
127 case strings.Contains(name, "next") || strings.Contains(name, "prev") || name == "outline":
128 return "JUMP"
129 default:
130 return ""
131 }
132}
133
134func modeLegendGroup(mode input.Mode, name string) string {
135 switch {
136 case name == "confirm" || name == "cancel":
137 return "EXIT"
138 case strings.HasPrefix(name, "split-"):
139 return "SPLIT"
140 case strings.HasPrefix(name, "enter-"):
141 return "MODES"
142 case strings.HasPrefix(name, "focus-"):
143 return "FOCUS"
144 case strings.HasPrefix(name, "resize-"):
145 return "RESIZE"
146 case strings.HasPrefix(name, "vim-scroll-") || strings.HasPrefix(name, "half-page-"):
147 return "SCROLL"
148 case mode == input.ModeVim && vimLegendGroup(name) != "":
149 return vimLegendGroup(name)
150 case (mode == input.ModeTab || mode == input.ModePrefix) && (name == "next-tab" || name == "previous-tab"):
151 return "SWITCH"
152 default:
153 return "MANAGE"
154 }
155}