summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-06-29 12:59:39 +0300
committerPaul Buetow <paul@buetow.org>2025-06-29 12:59:39 +0300
commite5f026af5979a4068055cf69ab34dea472f51950 (patch)
tree2e5227cdc67785fc471a7058ec4179ad4551a367
parent53d55ca359bfc5ea53759173c94051714dc0ff7d (diff)
fix: resolve text wrapping and task ID issues in TaskSamurai
- Add word wrapping for long descriptions and annotations in detail view - Fix task ID showing as 0 after undo operation by improving task lookup - Ensure restored tasks are properly fetched from Taskwarrior with correct IDs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
-rw-r--r--TODO.md5
-rw-r--r--internal/ui/keyhandlers.go36
-rw-r--r--internal/ui/taskdetail.go73
3 files changed, 105 insertions, 9 deletions
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..0ae8797
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,5 @@
+# To-do's
+
+## Bugs
+
+* In the detailed task view, when the description doesn't fit in one line, there are no linebreaks, so the description can not be fully read.
diff --git a/internal/ui/keyhandlers.go b/internal/ui/keyhandlers.go
index e806dbf..14f6abc 100644
--- a/internal/ui/keyhandlers.go
+++ b/internal/ui/keyhandlers.go
@@ -246,17 +246,51 @@ func (m *Model) handleUndo() (tea.Model, tea.Cmd) {
return m, nil
}
- m.reload()
+ // Reload the task list to get the updated task with its new ID
+ if err := m.reload(); err != nil {
+ m.showError(err)
+ return m, nil
+ }
// Find the task ID for blinking
var id int
+ var found bool
for _, tsk := range m.tasks {
if tsk.UUID == uuid {
id = tsk.ID
+ found = true
break
}
}
+ // If task not found or has ID 0, try to get it directly from Taskwarrior
+ if !found || id == 0 {
+ // Use task export with UUID filter to get the specific task
+ filters := []string{uuid}
+ if m.filters != nil {
+ filters = append(filters, m.filters...)
+ }
+ filters = append(filters, "status:pending")
+
+ tasks, err := task.Export(filters...)
+ if err == nil && len(tasks) > 0 {
+ id = tasks[0].ID
+ // Also update our local task list
+ for i, tsk := range m.tasks {
+ if tsk.UUID == uuid {
+ m.tasks[i].ID = id
+ break
+ }
+ }
+ }
+ }
+
+ // If we still don't have a valid ID, don't try to blink
+ if id == 0 {
+ m.statusMsg = "Task restored"
+ return m, nil
+ }
+
return m, m.startBlink(id, false)
}
diff --git a/internal/ui/taskdetail.go b/internal/ui/taskdetail.go
index c7a5b89..0fb8cb4 100644
--- a/internal/ui/taskdetail.go
+++ b/internal/ui/taskdetail.go
@@ -8,6 +8,36 @@ import (
"github.com/charmbracelet/lipgloss"
)
+// wordWrap wraps text to fit within the specified width, breaking at word boundaries
+func wordWrap(text string, width int) []string {
+ if width <= 0 {
+ return []string{text}
+ }
+
+ var lines []string
+ words := strings.Fields(text)
+ if len(words) == 0 {
+ return []string{""}
+ }
+
+ currentLine := words[0]
+ for i := 1; i < len(words); i++ {
+ word := words[i]
+ testLine := currentLine + " " + word
+ if len(testLine) > width {
+ lines = append(lines, currentLine)
+ currentLine = word
+ } else {
+ currentLine = testLine
+ }
+ }
+ if currentLine != "" {
+ lines = append(lines, currentLine)
+ }
+
+ return lines
+}
+
// Define field indices for navigation
const (
fieldID = iota
@@ -177,12 +207,24 @@ func (m *Model) renderTaskDetail() string {
Italic(true)
lines = append(lines, editingStyle.Render(" [Editing in external editor...]"))
} else if t.Description != "" {
- // Highlight search matches if searching
+ // Calculate available width for description (terminal width - margins)
+ availableWidth := m.tbl.Width() - 4
+ if availableWidth < 20 {
+ availableWidth = 20 // Minimum width
+ }
+
+ // Wrap the description text
desc := t.Description
- if m.detailSearchRegex != nil && m.detailSearchRegex.MatchString(desc) {
- desc = m.highlightMatches(desc, m.detailSearchRegex)
+ wrappedLines := wordWrap(desc, availableWidth)
+
+ // Apply search highlighting and render each wrapped line
+ for _, line := range wrappedLines {
+ displayLine := line
+ if m.detailSearchRegex != nil && m.detailSearchRegex.MatchString(line) {
+ displayLine = m.highlightMatches(line, m.detailSearchRegex)
+ }
+ lines = append(lines, descValueStyle.Render(displayLine))
}
- lines = append(lines, descValueStyle.Render(desc))
} else {
lines = append(lines, descValueStyle.Render("-"))
}
@@ -198,13 +240,28 @@ func (m *Model) renderTaskDetail() string {
annValueStyle = annValueStyle.Background(lipgloss.Color(m.theme.SelectedBG))
}
lines = append(lines, annLabelStyle.Render("Annotations:"))
+ // Calculate available width for annotations
+ availableWidth := m.tbl.Width() - 4
+ if availableWidth < 20 {
+ availableWidth = 20 // Minimum width
+ }
+
for _, ann := range t.Annotations {
annText := fmt.Sprintf("[%s] %s", m.formatTaskDate(ann.Entry), ann.Description)
- // Highlight search matches
- if m.detailSearchRegex != nil && m.detailSearchRegex.MatchString(annText) {
- annText = m.highlightMatches(annText, m.detailSearchRegex)
+ wrappedAnnLines := wordWrap(annText, availableWidth)
+
+ // Apply search highlighting and render each wrapped line
+ for i, line := range wrappedAnnLines {
+ displayLine := line
+ if m.detailSearchRegex != nil && m.detailSearchRegex.MatchString(line) {
+ displayLine = m.highlightMatches(line, m.detailSearchRegex)
+ }
+ // Add some indentation for continuation lines
+ if i > 0 {
+ displayLine = " " + displayLine
+ }
+ lines = append(lines, annValueStyle.Render(displayLine))
}
- lines = append(lines, annValueStyle.Render(annText))
}
}