From 1433f7a13ede0c819ec4f8fd4027ad3df8daa94f Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 5 Jun 2026 10:26:16 +0300 Subject: Add 'ask edit' subcommand and collapse multi-line task descriptions - ask edit opens $EDITOR and creates a task from the (multi-line) content, reusing the shared internal/editor package - collapse newlines in list output and fish completion so multi-line descriptions render on one line and don't break completion Bump version to 0.40.0 Amp-Thread-ID: https://ampcode.com/threads/T-019e96a1-9c8e-73d6-95b4-b55cb12cc762 Co-authored-by: Amp --- internal/askcli/formatter.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) (limited to 'internal/askcli/formatter.go') diff --git a/internal/askcli/formatter.go b/internal/askcli/formatter.go index 8d62125..93b8921 100644 --- a/internal/askcli/formatter.go +++ b/internal/askcli/formatter.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "os" + "regexp" "slices" "strings" @@ -55,14 +56,15 @@ func taskListWidthsFor(tasks []TaskExport, aliases map[string]string, terminalWi widths.Status = max(widths.Status, len(t.Status)) widths.Started = max(widths.Started, len(formatTaskStarted(t))) widths.Tags = max(widths.Tags, len(formatTaskTags(t.Tags))) - longestDescription = max(longestDescription, len(t.Description)) + longestDescription = max(longestDescription, len(oneLineDescription(t.Description))) } widths.Description = taskListDescriptionWidth(widths, terminalWidth, longestDescription) return widths } func writeTaskListHeader(b *strings.Builder, widths taskListWidths) { - fmt.Fprintf(b, "%-*s | %-*s | %-*s | %-*s | %-*s | %-*s | %-*s\n", + fmt.Fprintf( + b, "%-*s | %-*s | %-*s | %-*s | %-*s | %-*s | %-*s\n", widths.Urgency, "Urg", widths.Priority, "Pri", widths.ID, "ID", @@ -79,7 +81,8 @@ func writeTaskListSeparator(b *strings.Builder, widths taskListWidths) { } func writeTaskListRow(b *strings.Builder, widths taskListWidths, t TaskExport, aliases map[string]string) { - fmt.Fprintf(b, "%-*s | %-*s | %-*s | %-*s | %-*s | %-*s | %-*s\n", + fmt.Fprintf( + b, "%-*s | %-*s | %-*s | %-*s | %-*s | %-*s | %-*s\n", widths.Urgency, fmt.Sprintf("%.1f", t.Urgency), widths.Priority, t.Priority, widths.ID, displayTaskAlias(t.UUID, aliases), @@ -98,6 +101,7 @@ func formatTaskTags(tags []string) string { } func formatTaskDescription(desc string, width int) string { + desc = oneLineDescription(desc) if width <= 0 || len(desc) <= width { return desc } @@ -107,6 +111,20 @@ func formatTaskDescription(desc string, width int) string { return desc[:width-3] + "..." } +// newlineRun matches a run of one or more line breaks together with any inline +// whitespace surrounding them. +var newlineRun = regexp.MustCompile(`[ \t]*[\r\n]+[ \t]*`) + +// oneLineDescription collapses any embedded line breaks in a task description +// into a single space so multi-line descriptions render on one line and never +// break the tab-separated completion output or the task list table. +func oneLineDescription(desc string) string { + if !strings.ContainsAny(desc, "\r\n") { + return desc + } + return strings.TrimSpace(newlineRun.ReplaceAllString(desc, " ")) +} + func formatTaskStarted(t TaskExport) string { if t.Start == "" { return "no" -- cgit v1.2.3