summaryrefslogtreecommitdiff
path: root/internal/ui/ultra.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/ui/ultra.go')
-rw-r--r--internal/ui/ultra.go109
1 files changed, 45 insertions, 64 deletions
diff --git a/internal/ui/ultra.go b/internal/ui/ultra.go
index 8419e78..53bc677 100644
--- a/internal/ui/ultra.go
+++ b/internal/ui/ultra.go
@@ -442,8 +442,7 @@ func (m *Model) ultraModeStatus(tasks []task.Task) string {
func (m *Model) ultraSearchText(t task.Task) string {
return ultraJoinSections(
- m.ultraHeaderText(t),
- m.ultraMetaText(t),
+ m.ultraStatusText(t),
ultraOrDash(strings.TrimSpace(t.Description)),
m.ultraAnnotationsSearchText(t),
)
@@ -549,27 +548,21 @@ func (m *Model) ultraCursorStatus(tasks []task.Task) string {
return fmt.Sprintf("%d/%d", cursor+1, len(tasks))
}
-func (m *Model) ultraHeaderText(t task.Task) string {
+// ultraStatusText returns the plain-text representation of the single
+// consolidated status line shown per card: ID, priority, status, urgency,
+// due date, project, and tags. Age, recur, and start are omitted — started
+// tasks are highlighted in yellow, and the remaining fields are available in
+// the detail view.
+func (m *Model) ultraStatusText(t task.Task) string {
return strings.Join(
[]string{
fmt.Sprintf("#%d", t.ID),
ultraOrDash(t.Priority),
ultraOrDash(t.Status),
fmt.Sprintf("%.1f", t.Urgency),
- ultraTaskAge(t.Entry),
- },
- " | ",
- )
-}
-
-func (m *Model) ultraMetaText(t task.Task) string {
- return strings.Join(
- []string{
- "project: " + ultraOrDash(t.Project),
- "tags: " + ultraOrDash(strings.Join(t.Tags, " ")),
"due: " + ultraDueValue(m, t.Due),
- "recur: " + ultraOrDash(t.Recur),
- "start: " + ultraOrDash(m.formatTaskDate(t.Start)),
+ "proj: " + ultraOrDash(t.Project),
+ "tags: " + ultraOrDash(strings.Join(t.Tags, " ")),
},
" | ",
)
@@ -631,13 +624,10 @@ func (m *Model) renderUltraCard(t task.Task, width int, selected bool, re *regex
bg = m.theme.SelectedBG
}
- // No blank lines between sections — ultraJoinSections passes "" so
- // ultraJoinSectionsWithBlank skips the separator entirely. The outer
- // ultraCardStyle already fills the selected background across all lines,
- // so per-section bg-coloured blank rows are not needed.
+ // Single status line (ID, priority, status, urgency, due, proj, tags)
+ // followed by description and annotations — no blank lines between sections.
card := ultraJoinSections(
- m.renderUltraHeaderWithRegex(t, width, re, bg),
- m.renderUltraMetaWithRegex(t, width, re, bg),
+ m.renderUltraStatusWithRegex(t, width, re, bg),
m.renderUltraDescriptionWithRegex(t, width, re, bg),
m.renderUltraAnnotationsWithRegex(t, width, re, bg),
)
@@ -654,14 +644,9 @@ func (m *Model) renderUltraCard(t task.Task, width int, selected bool, re *regex
return ultraCardStyle(m.theme, width, selected, started, blink).Render(card)
}
-// renderUltraHeader renders the task's primary state line (no selection bg).
-func (m *Model) renderUltraHeader(t task.Task, width int) string {
- return m.renderUltraHeaderWithRegex(t, width, m.ultraSearchRegex, "")
-}
-
-// renderUltraMeta renders the task's secondary metadata line (no selection bg).
-func (m *Model) renderUltraMeta(t task.Task, width int) string {
- return m.renderUltraMetaWithRegex(t, width, m.ultraSearchRegex, "")
+// renderUltraStatus renders the consolidated single-line card status (no selection bg).
+func (m *Model) renderUltraStatus(t task.Task, width int) string {
+ return m.renderUltraStatusWithRegex(t, width, m.ultraSearchRegex, "")
}
// renderUltraDescription renders the wrapped task description body (no selection bg).
@@ -674,14 +659,37 @@ func (m *Model) renderUltraAnnotations(t task.Task, width int) string {
return m.renderUltraAnnotationsWithRegex(t, width, m.ultraSearchRegex, "")
}
-func (m *Model) renderUltraHeaderWithRegex(t task.Task, width int, re *regexp.Regexp, bg string) string {
+// renderUltraStatusWithRegex renders a single consolidated status line combining
+// the former header (ID, priority, status, urgency) and meta (due, project, tags)
+// fields. Age, recur, and start are omitted for compactness.
+func (m *Model) renderUltraStatusWithRegex(t task.Task, width int, re *regexp.Regexp, bg string) string {
+ _ = width
idText := fmt.Sprintf("#%d", t.ID)
priorityText := ultraOrDash(t.Priority)
statusText := ultraOrDash(t.Status)
urgencyText := fmt.Sprintf("%.1f", t.Urgency)
- ageText := ultraTaskAge(t.Entry)
- line := strings.Join([]string{idText, priorityText, statusText, urgencyText, ageText}, " | ")
- if re != nil && re.MatchString(line) && !ultraRegexMatchesAny(re, idText, priorityText, statusText, urgencyText, ageText) {
+ due := ultraDueValue(m, t.Due)
+ project := ultraOrDash(t.Project)
+ tags := ultraOrDash(strings.Join(t.Tags, " "))
+
+ // Priority badges render as 3-char wide pills in the styled path (Width(3)
+ // + Center). Pad the plain text to match so whole-line and styled paths
+ // produce the same visible text after ANSI stripping.
+ priorityPadded := priorityText
+ if t.Priority == "H" || t.Priority == "M" || t.Priority == "L" {
+ priorityPadded = " " + t.Priority + " "
+ }
+
+ line := strings.Join([]string{
+ idText, priorityPadded, statusText, urgencyText,
+ "due: " + due, "proj: " + project, "tags: " + tags,
+ }, " | ")
+ // Fall back to whole-line rendering when the regex spans a field separator
+ // or a full "key: value" pair (e.g. "proj: home") that can't be matched by
+ // checking label and value individually.
+ if re != nil && re.MatchString(line) && !ultraRegexMatchesAny(re,
+ idText, priorityText, statusText, urgencyText, due, project, tags,
+ ) {
return m.renderUltraSearchLine(line, lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("253")), re, bg)
}
@@ -695,38 +703,11 @@ func (m *Model) renderUltraHeaderWithRegex(t task.Task, width int, re *regexp.Re
priority := m.ultraStyledText(re, ultraPriorityStyle(m.theme, t.Priority), priorityText, priorityBG)
status := m.ultraStyledText(re, lipgloss.NewStyle().Foreground(lipgloss.Color("246")), statusText, bg)
urgency := m.ultraStyledText(re, lipgloss.NewStyle().Foreground(lipgloss.Color("214")), urgencyText, bg)
- age := m.ultraStyledText(re, lipgloss.NewStyle().Foreground(lipgloss.Color("240")), ageText, bg)
- return strings.Join([]string{id, priority, status, urgency, age}, sep)
-}
-
-func (m *Model) renderUltraMetaWithRegex(t task.Task, width int, re *regexp.Regexp, bg string) string {
- _ = width
- project := ultraOrDash(t.Project)
- tags := ultraOrDash(strings.Join(t.Tags, " "))
- due := ultraDueValue(m, t.Due)
- recur := ultraOrDash(t.Recur)
- start := ultraOrDash(m.formatTaskDate(t.Start))
- line := strings.Join(
- []string{
- "project: " + project,
- "tags: " + tags,
- "due: " + due,
- "recur: " + recur,
- "start: " + start,
- },
- " | ",
- )
- if re != nil && re.MatchString(line) && !ultraRegexMatchesAny(re, "project:", project, "tags:", tags, "due:", due, "recur:", recur, "start:", start) {
- return m.renderUltraSearchLine(line, lipgloss.NewStyle().Foreground(lipgloss.Color("253")), re, bg)
- }
-
- sep := ultraFieldSep(bg)
parts := []string{
- m.ultraKeyValue(re, "project", project, bg),
- m.ultraKeyValue(re, "tags", tags, bg),
+ id, priority, status, urgency,
m.ultraKeyValue(re, "due", due, bg),
- m.ultraKeyValue(re, "recur", recur, bg),
- m.ultraKeyValue(re, "start", start, bg),
+ m.ultraKeyValue(re, "proj", project, bg),
+ m.ultraKeyValue(re, "tags", tags, bg),
}
return strings.Join(parts, sep)
}