From aa8f89b43ad44d94491c2d67a08acb824960e12c Mon Sep 17 00:00:00 2001 From: Skruecken Date: Mon, 19 Oct 2026 10:38:28 +0200 Subject: [PATCH] Jump to status options with their first-letter shortcuts. o/w/d select OPEN/WIP/DONE in the status menu, with the hotkeys shown inline. Co-authored-by: Cursor --- internal/ticket/ticket.go | 14 +++++++++++++- internal/ui/app.go | 7 +++++++ internal/ui/helpbar.go | 1 + internal/ui/views/status.go | 21 +++++++++++++++++---- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/internal/ticket/ticket.go b/internal/ticket/ticket.go index f2bbc6b..6ea9839 100644 --- a/internal/ticket/ticket.go +++ b/internal/ticket/ticket.go @@ -1,6 +1,9 @@ package ticket -import "time" +import ( + "time" + "unicode" +) // Status represents the workflow state of a ticket. type Status string @@ -28,6 +31,15 @@ func (s Status) Label() string { } } +// Shortcut returns the status-menu hotkey (first letter of the label). +func (s Status) Shortcut() rune { + label := s.Label() + if label == "" { + return 0 + } + return unicode.ToLower([]rune(label)[0]) +} + // Index returns the position in AllStatuses, or 0 if unknown. func (s Status) Index() int { for i, st := range AllStatuses { diff --git a/internal/ui/app.go b/internal/ui/app.go index af9476a..4182a14 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -311,6 +311,13 @@ func (m Model) updateStatus(msg tea.KeyMsg) (tea.Model, tea.Cmd) { m.statusMenu.MoveUp() return m, nil } + + // Jump to option by first letter of its label (o/w/d). + if msg.Type == tea.KeyRunes && len(msg.Runes) == 1 { + if m.statusMenu.JumpToShortcut(msg.Runes[0]) { + return m, nil + } + } return m, nil } diff --git a/internal/ui/helpbar.go b/internal/ui/helpbar.go index 9174f96..6ad98a1 100644 --- a/internal/ui/helpbar.go +++ b/internal/ui/helpbar.go @@ -37,6 +37,7 @@ func (m Model) helpEntries() []helpEntry { case ModeStatus: return []helpEntry{ {"j/k", "select"}, + {"o/w/d", "jump"}, {"enter", "apply"}, {"esc", "cancel"}, } diff --git a/internal/ui/views/status.go b/internal/ui/views/status.go index 3204cb8..e8d5118 100644 --- a/internal/ui/views/status.go +++ b/internal/ui/views/status.go @@ -1,7 +1,9 @@ package views import ( + "fmt" "strings" + "unicode" "github.com/charmbracelet/lipgloss" @@ -46,6 +48,18 @@ func (m *StatusMenu) MoveUp() { } } +// JumpToShortcut moves the cursor to the status whose label starts with r. +func (m *StatusMenu) JumpToShortcut(r rune) bool { + r = unicode.ToLower(r) + for i, st := range ticket.AllStatuses { + if st.Shortcut() == r { + m.Cursor = i + return true + } + } + return false +} + // Selected returns the currently highlighted status. func (m StatusMenu) Selected() ticket.Status { if m.Cursor < 0 || m.Cursor >= len(ticket.AllStatuses) { @@ -59,13 +73,12 @@ func (m StatusMenu) View() string { var lines []string lines = append(lines, m.Styles.Title.Render("Set status")) for i, st := range ticket.AllStatuses { - label := " " + st.Label() + entry := fmt.Sprintf("%c %s", st.Shortcut(), st.Label()) if i == m.Cursor { - label = m.Styles.Selected.Render("▸ " + st.Label()) + lines = append(lines, m.Styles.Selected.Render("▸ "+entry)) } else { - label = m.Styles.Normal.Render(label) + lines = append(lines, m.Styles.Normal.Render(" "+entry)) } - lines = append(lines, label) } body := strings.Join(lines, "\n") w := m.Width