Add interactive Markdown checklist toggles in the ticket preview.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Skruecken
2026-10-19 14:37:14 +02:00
co-authored by Cursor
parent 7666715f6c
commit f08084d5e1
8 changed files with 366 additions and 21 deletions
+76
View File
@@ -1,11 +1,15 @@
package views
import (
"strings"
"sync"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/glamour/styles"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/termenv"
"task/internal/ticket"
)
type mdCacheKey struct {
@@ -79,6 +83,78 @@ func getRenderer(width int) (*glamour.TermRenderer, error) {
return r, nil
}
// RenderDescription renders ticket Markdown with interactive task-list items.
// checklistCursor is the 0-based index among checkboxes (highlight); ignored when
// there are none.
func RenderDescription(src string, width int, checklistCursor int) string {
if strings.TrimSpace(src) == "" {
return ""
}
boxes := ticket.ListCheckboxes(src)
if len(boxes) == 0 {
return RenderMarkdown(src, width)
}
boxByLine := make(map[int]int, len(boxes))
for i, b := range boxes {
boxByLine[b.Line] = i
}
lines := strings.Split(src, "\n")
var parts []string
var mdBuf []string
flushMD := func() {
if len(mdBuf) == 0 {
return
}
block := strings.Join(mdBuf, "\n")
if strings.TrimSpace(block) != "" {
parts = append(parts, RenderMarkdown(block, width))
}
mdBuf = nil
}
for i, line := range lines {
if bi, ok := boxByLine[i]; ok {
flushMD()
parts = append(parts, renderCheckboxLine(boxes[bi], bi == checklistCursor, width))
continue
}
mdBuf = append(mdBuf, line)
}
flushMD()
if len(parts) == 0 {
return ""
}
return lipgloss.JoinVertical(lipgloss.Left, parts...)
}
func renderCheckboxLine(box ticket.Checkbox, selected bool, width int) string {
// ASCII markers — widely supported; bold + padding reads a notch bigger.
mark := "[ ]"
if box.Checked {
mark = "[x]"
}
prefix := " "
if selected {
prefix = "> "
}
label := strings.TrimSpace(box.Text)
avail := max(10, width) - lipgloss.Width(prefix+mark+" ")
label = truncateOneLine(label, avail)
markStyle := lipgloss.NewStyle().Bold(true)
labelStyle := lipgloss.NewStyle()
if selected {
markStyle = markStyle.Foreground(lipgloss.Color("39"))
labelStyle = labelStyle.Bold(true).Foreground(lipgloss.Color("39"))
} else if box.Checked {
labelStyle = labelStyle.Faint(true)
}
line := prefix + markStyle.Render(mark) + " " + labelStyle.Render(label)
return lipgloss.NewStyle().Width(max(10, width)).Render(line)
}
// ClearMarkdownCache drops cached renders (e.g. after theme change).
func ClearMarkdownCache() {
mdMu.Lock()