Add offline-first task-master TUI with tickets, board lists, and undo.
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>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
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 ""
|
||||
}
|
||||
Reference in New Issue
Block a user