Improve delete UX and wrap the helpbar on narrow terminals.

Confirm with x, force-delete with X, and wrap keybinding hints so all shortcuts stay visible.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Skruecken
2026-10-19 10:32:40 +02:00
co-authored by Cursor
parent 73216ec4ae
commit 28cd2628e4
4 changed files with 143 additions and 50 deletions
+75 -38
View File
@@ -42,6 +42,11 @@ func (m Model) helpEntries() []helpEntry {
{"enter", "apply"},
{"esc", "cancel"},
}
case ModeConfirmDelete:
return []helpEntry{
{"y/enter", "confirm"},
{"esc", "cancel"},
}
default:
return []helpEntry{
{"j/k", "navigate"},
@@ -52,64 +57,96 @@ func (m Model) helpEntries() []helpEntry {
{"p", "project"},
{"l", "list"},
{"s", "status"},
{"X", "delete"},
{"x", "delete"},
{"X", "force delete"},
{"q", "quit"},
}
}
}
func (m Model) renderHelpbar() string {
var parts []string
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 == "" {
sep = " "
return " "
}
return strings.Join(parts, sep)
return sep
}
// wrapHelpParts packs styled help snippets into lines that fit within width.
func wrapHelpParts(parts []string, width int, sep string) []string {
if width < 1 {
width = 1
}
sepW := lipgloss.Width(sep)
var lines []string
var cur string
for _, p := range parts {
pw := lipgloss.Width(p)
if cur == "" {
cur = p
// Oversized single entry still gets its own line so nothing is dropped.
if pw > width {
lines = append(lines, cur)
cur = ""
}
continue
}
if lipgloss.Width(cur)+sepW+pw <= width {
cur = cur + sep + p
continue
}
lines = append(lines, cur)
cur = p
if pw > width {
lines = append(lines, cur)
cur = ""
}
}
if cur != "" {
lines = append(lines, cur)
}
if len(lines) == 0 {
return []string{""}
}
return lines
}
func (m Model) renderFooter() string {
help := m.renderHelpbar()
ver := m.styles.Version.Render(m.version)
width := m.width
if width <= 0 {
width = lipgloss.Width(help) + lipgloss.Width(ver) + 1
width = 80
}
helpW := lipgloss.Width(help)
ver := m.styles.Version.Render(m.version)
verW := lipgloss.Width(ver)
gap := width - helpW - verW
if gap < 1 {
maxHelp := width - verW - 1
if maxHelp < 0 {
maxHelp = 0
}
help = truncateWidth(help, maxHelp)
helpW = lipgloss.Width(help)
gap = width - helpW - verW
if gap < 1 {
gap = 1
}
}
return help + strings.Repeat(" ", gap) + ver
}
sep := m.helpSep()
parts := m.helpParts()
func truncateWidth(s string, max int) string {
if max <= 0 {
return ""
}
if lipgloss.Width(s) <= max {
return s
}
runes := []rune(s)
for len(runes) > 0 {
runes = runes[:len(runes)-1]
if lipgloss.Width(string(runes)) <= max-1 {
return string(runes) + "…"
// Wrap help across the full width so all bindings stay visible.
lines := wrapHelpParts(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 ""
return strings.Join(lines, "\n")
}