Files
task-master/internal/ui/helpbar.go
T
skrueckenandCursor 98fcb57298
CI / test-and-build (push) Failing after 1m31s
CI / demo (push) Skipped
Align the TUI help legend into a fixed-width column grid.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-28 00:17:44 +01:00

243 lines
4.7 KiB
Go

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")
}