Charm-based CLI with JSON persistence, status/list filters, external editor editing, command-history undo/redo, and glamour markdown without AutoStyle OSC hangs. Co-authored-by: Cursor <cursoragent@cursor.com>
116 lines
2.1 KiB
Go
116 lines
2.1 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:
|
|
return []helpEntry{
|
|
{"enter", "save"},
|
|
{"esc", "cancel"},
|
|
}
|
|
case ModeSearch:
|
|
return []helpEntry{
|
|
{"enter", "apply"},
|
|
{"esc", "cancel"},
|
|
}
|
|
case ModeProjects:
|
|
return []helpEntry{
|
|
{"j/k", "navigate"},
|
|
{"enter", "select"},
|
|
{"n", "new project"},
|
|
{"esc", "cancel"},
|
|
}
|
|
case ModeStatus:
|
|
return []helpEntry{
|
|
{"j/k", "select"},
|
|
{"enter", "apply"},
|
|
{"esc", "cancel"},
|
|
}
|
|
case ModeBoard:
|
|
return []helpEntry{
|
|
{"j/k", "select"},
|
|
{"enter", "apply"},
|
|
{"esc", "cancel"},
|
|
}
|
|
default:
|
|
return []helpEntry{
|
|
{"j/k", "navigate"},
|
|
{"n", "new"},
|
|
{"e", "edit"},
|
|
{"u/U", "undo/redo"},
|
|
{"/", "search"},
|
|
{"p", "project"},
|
|
{"l", "list"},
|
|
{"s", "status"},
|
|
{"X", "delete"},
|
|
{"q", "quit"},
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m Model) renderHelpbar() string {
|
|
var parts []string
|
|
for _, e := range m.helpEntries() {
|
|
parts = append(parts, m.styles.HelpKey.Render(e.key)+" "+m.styles.HelpDesc.Render(e.desc))
|
|
}
|
|
sep := m.styles.HelpSep.String()
|
|
if sep == "" {
|
|
sep = " "
|
|
}
|
|
return strings.Join(parts, sep)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
helpW := lipgloss.Width(help)
|
|
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
|
|
}
|
|
|
|
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) + "…"
|
|
}
|
|
}
|
|
return ""
|
|
}
|