package ui import ( "strings" "github.com/charmbracelet/lipgloss" ) type helpEntry struct { key string desc string } func (m Model) helpEntries() []helpEntry { switch m.mode { case ModeCreate: entries := []helpEntry{ {"enter", "save"}, {"esc", "cancel"}, } if !m.creatingProject { entries = append([]helpEntry{{"ctrl+e", "editor"}}, entries...) } return entries case ModeSearch: return []helpEntry{ {"enter", "apply"}, {"esc", "cancel"}, } case ModeProjects: return []helpEntry{ {"j/k", "navigate"}, {"a-z", "jump"}, {"enter", "select"}, {"n", "new project"}, {"e", "edit"}, {"Y", "Sync"}, {"x", "delete"}, {"X", "force delete"}, {"esc", "cancel"}, } case ModeEditProject: return []helpEntry{ {"tab/↑↓", "fields"}, {"enter", "save"}, {"esc", "cancel"}, } case ModeMoveTicket: return []helpEntry{ {"j/k", "navigate"}, {"a-z", "jump"}, {"enter", "move here"}, {"esc", "cancel"}, } case ModeStatus: return []helpEntry{ {"j/k", "select"}, {"o/w/d", "jump"}, {"enter", "apply"}, {"esc", "cancel"}, } case ModeSync: return []helpEntry{ {"j/k", "select"}, {"p/u", "jump"}, {"enter", "run"}, {"esc", "cancel"}, } case ModeBoard: return []helpEntry{ {"j/k", "navigate"}, {"a/b/i/d", "jump"}, {"space", "toggle"}, {"enter", "apply"}, {"esc", "cancel"}, } case ModeConfirmDelete: return []helpEntry{ {"y/enter", "confirm"}, {"esc", "cancel"}, } case ModeTimeManual: return []helpEntry{ {"enter", "save"}, {"esc", "cancel"}, } case ModeTimeEntries: return []helpEntry{ {"j/k", "navigate"}, {"e", "edit times"}, {"x", "delete"}, {"u/U", "undo/redo"}, {"esc", "back"}, } case ModeTimeEdit: return []helpEntry{ {"tab/↑↓", "fields"}, {"enter", "save"}, {"esc", "cancel"}, } case ModeReport: return []helpEntry{ {"g", "group"}, {"j/k", "scroll"}, {"esc", "back"}, } case ModeReportGroup: return []helpEntry{ {"j/k", "select"}, {"p/s/t/d", "jump"}, {"enter", "apply"}, {"esc", "cancel"}, } default: return []helpEntry{ {"j/k", "navigate"}, {"J/K", "checklist"}, {"S-↑/↓", "checklist"}, {"space", "check"}, {"n", "new"}, {"e", "edit"}, {"t/T", "timer/log"}, {"m", "entries"}, {"r", "report"}, {"Y", "Sync"}, {"u/U", "undo/redo"}, {"/", "search"}, {"p", "project"}, {"P", "move"}, {"l", "list"}, {"s", "status"}, {"x", "delete"}, {"X", "force delete"}, {"q", "quit"}, } } } func (m Model) helpParts() []string { parts := make([]string, 0, len(m.helpEntries())) for _, e := range m.helpEntries() { parts = append(parts, m.styles.HelpKey.Render(e.key)+" "+m.styles.HelpDesc.Render(e.desc)) } return parts } func (m Model) helpSep() string { sep := m.styles.HelpSep.String() if sep == "" { return " " } return sep } // padHelpCell pads a styled help snippet to cellW visible columns. func padHelpCell(part string, cellW int) string { pad := cellW - lipgloss.Width(part) if pad < 0 { pad = 0 } return part + strings.Repeat(" ", pad) } // tableHelpParts lays out styled help snippets as a fixed-width column grid. func tableHelpParts(parts []string, width int, sep string) []string { if len(parts) == 0 { return []string{""} } if width < 1 { width = 1 } cellW := 0 for _, p := range parts { if w := lipgloss.Width(p); w > cellW { cellW = w } } if cellW < 1 { cellW = 1 } sepW := lipgloss.Width(sep) cols := 1 if cellW <= width { cols = (width + sepW) / (cellW + sepW) if cols < 1 { cols = 1 } } var lines []string for i := 0; i < len(parts); i += cols { end := i + cols if end > len(parts) { end = len(parts) } row := parts[i:end] cells := make([]string, len(row)) for j, p := range row { // Pad every cell except a lone oversized entry on its own row. if len(row) == 1 && lipgloss.Width(p) > width { cells[j] = p } else { cells[j] = padHelpCell(p, cellW) } } lines = append(lines, strings.Join(cells, sep)) } return lines } func (m Model) renderFooter() string { width := m.width if width <= 0 { width = 80 } ver := m.styles.Version.Render(m.version) verW := lipgloss.Width(ver) sep := m.helpSep() parts := m.helpParts() // Table layout so key/desc columns stay aligned across rows. lines := tableHelpParts(parts, width, sep) // Place version on the last line to the right when it fits; otherwise // put it on an extra right-aligned line. last := lines[len(lines)-1] gap := width - lipgloss.Width(last) - verW if gap >= 1 { lines[len(lines)-1] = last + strings.Repeat(" ", gap) + ver } else { pad := width - verW if pad < 0 { pad = 0 } lines = append(lines, strings.Repeat(" ", pad)+ver) } return strings.Join(lines, "\n") }