diff options
| author | Paul Buetow <paul@buetow.org> | 2025-06-28 11:22:55 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-06-28 11:22:55 +0300 |
| commit | b659b8cf87c86280f62cef0f499a60b999e6ce6b (patch) | |
| tree | 5c0dc0606a8bda997a3b25f4c8a51479c34bb51b | |
| parent | ab986c63c75a3114b409d171ab2aaf616c087554 (diff) | |
feat: add editing and visual feedback to task detail view
- Enable in-place editing of Priority, Tags, Due, and Recurrence fields
- Add blinking effect (bright yellow) after field changes
- Change all status message timeouts from 3 to 2 seconds
- Support disco mode in detail view (random theme on edit)
- Add showLabel parameter to view functions to hide redundant labels
- Temporarily clear input prompts in detail view for cleaner UI
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
| -rw-r--r-- | internal/ui/handlers.go | 148 | ||||
| -rw-r--r-- | internal/ui/keyhandlers.go | 3 | ||||
| -rw-r--r-- | internal/ui/table.go | 68 | ||||
| -rw-r--r-- | internal/ui/taskdetail.go | 186 | ||||
| -rwxr-xr-x | tasksamurai | bin | 5902743 -> 5963040 bytes |
5 files changed, 330 insertions, 75 deletions
diff --git a/internal/ui/handlers.go b/internal/ui/handlers.go index 019e8d2..3be357a 100644 --- a/internal/ui/handlers.go +++ b/internal/ui/handlers.go @@ -20,7 +20,7 @@ func (m *Model) handleTextInput(msg tea.KeyMsg, input *textinput.Model, onEnter value := input.Value() if err := onEnter(value); err != nil { m.statusMsg = fmt.Sprintf("Error: %v", err) - cmd := tea.Tick(3*time.Second, func(time.Time) tea.Msg { + cmd := tea.Tick(2*time.Second, func(time.Time) tea.Msg { return struct{ clearStatus bool }{true} }) return m, cmd @@ -145,6 +145,10 @@ func (m *Model) handleTagsMode(msg tea.KeyMsg) (tea.Model, tea.Cmd) { model, cmd := m.handleTextInput(msg, &m.tagsInput, onEnter, onExit) if msg.Type == tea.KeyEnter { + if m.showTaskDetail { + // In detail view, blink the tags field + return model, m.startDetailBlink(4) // Tags is field index 4 + } return model, m.startBlink(m.tagsID, false) } return model, cmd @@ -156,14 +160,20 @@ func (m *Model) handleDueEditMode(msg tea.KeyMsg) (tea.Model, tea.Cmd) { case tea.KeyEnter: if err := task.SetDueDate(m.dueID, m.dueDate.Format("2006-01-02")); err != nil { m.statusMsg = fmt.Sprintf("Error: %v", err) - cmd := tea.Tick(3*time.Second, func(time.Time) tea.Msg { + cmd := tea.Tick(2*time.Second, func(time.Time) tea.Msg { return struct{ clearStatus bool }{true} }) return m, cmd } m.dueEditing = false m.reload() - cmd := m.startBlink(m.dueID, false) + var cmd tea.Cmd + if m.showTaskDetail { + // In detail view, blink the due field + cmd = m.startDetailBlink(5) // Due is field index 5 + } else { + cmd = m.startBlink(m.dueID, false) + } m.updateTableHeight() return m, cmd case tea.KeyEsc: @@ -204,6 +214,14 @@ func (m *Model) handleRecurrenceMode(msg tea.KeyMsg) (tea.Model, tea.Cmd) { model, cmd := m.handleTextInput(msg, &m.recurInput, onEnter, onExit) if msg.Type == tea.KeyEnter { + if m.showTaskDetail { + // In detail view, blink the recurrence field (dynamic index) + // Need to calculate the index based on whether recurrence field exists + fieldIndex := 8 // Base index for recurrence + if m.currentTaskDetail != nil && m.currentTaskDetail.Recur != "" { + return model, m.startDetailBlink(fieldIndex) + } + } return model, m.startBlink(m.recurID, false) } return model, cmd @@ -216,21 +234,27 @@ func (m *Model) handlePriorityMode(msg tea.KeyMsg) (tea.Model, tea.Cmd) { priority := priorityOptions[m.priorityIndex] if err := validatePriority(priority); err != nil { m.statusMsg = fmt.Sprintf("Error: %v", err) - cmd := tea.Tick(3*time.Second, func(time.Time) tea.Msg { + cmd := tea.Tick(2*time.Second, func(time.Time) tea.Msg { return struct{ clearStatus bool }{true} }) return m, cmd } if err := task.SetPriority(m.priorityID, priority); err != nil { m.statusMsg = fmt.Sprintf("Error: %v", err) - cmd := tea.Tick(3*time.Second, func(time.Time) tea.Msg { + cmd := tea.Tick(2*time.Second, func(time.Time) tea.Msg { return struct{ clearStatus bool }{true} }) return m, cmd } m.prioritySelecting = false m.reload() - cmd := m.startBlink(m.priorityID, false) + var cmd tea.Cmd + if m.showTaskDetail { + // In detail view, blink the priority field + cmd = m.startDetailBlink(3) // Priority is field index 3 + } else { + cmd = m.startBlink(m.priorityID, false) + } m.updateTableHeight() return m, cmd case tea.KeyEsc: @@ -274,7 +298,7 @@ func (m *Model) handleAddTaskMode(msg tea.KeyMsg) (tea.Model, tea.Cmd) { if err := task.AddLine(m.addInput.Value()); err != nil { m.statusMsg = fmt.Sprintf("Error: %v", err) - cmd := tea.Tick(3*time.Second, func(time.Time) tea.Msg { + cmd := tea.Tick(2*time.Second, func(time.Time) tea.Msg { return struct{ clearStatus bool }{true} }) return m, cmd @@ -438,9 +462,6 @@ func (m *Model) handleBlinkingState(msg tea.Msg) (tea.Model, tea.Cmd) { // handleEditingModes checks if we're in any editing mode and handles it func (m *Model) handleEditingModes(msg tea.KeyMsg) (handled bool, model tea.Model, cmd tea.Cmd) { switch { - case m.showTaskDetail: - model, cmd = m.handleTaskDetailMode(msg) - return true, model, cmd case m.annotating: model, cmd = m.handleAnnotationMode(msg) return true, model, cmd @@ -557,7 +578,114 @@ func (m *Model) handleTaskDetailMode(msg tea.KeyMsg) (tea.Model, tea.Cmd) { case "G", "end": m.detailFieldIndex = m.getDetailFieldCount() - 1 return m, nil + case "i", "enter": + // Check if current field is editable + return m.handleDetailFieldEdit() + } + + return m, nil +} + +// handleDetailFieldEdit starts editing for the current field in detail view +func (m *Model) handleDetailFieldEdit() (tea.Model, tea.Cmd) { + if m.currentTaskDetail == nil { + return m, nil + } + + id := m.currentTaskDetail.ID + + // Map detail field index to editable fields + // fieldPriority = 3, fieldTags = 4, fieldDue = 5, fieldStart = 6, fieldRecur = 8 or 9 (depending on if fields exist) + + // Count fields up to current position to handle dynamic fields + fieldPos := 0 + + // ID, UUID, Status (0-2) + if m.detailFieldIndex <= 2 { + return m, nil // Not editable + } + fieldPos = 3 + + // Priority (3) + if m.detailFieldIndex == fieldPos { + m.clearEditingModes() + m.priorityID = id + m.prioritySelecting = true + + // Set current priority index + switch m.currentTaskDetail.Priority { + case "H": + m.priorityIndex = 0 + case "M": + m.priorityIndex = 1 + case "L": + m.priorityIndex = 2 + default: + m.priorityIndex = 3 + } + m.updateTableHeight() + return m, nil + } + fieldPos++ + + // Tags (4) + if m.detailFieldIndex == fieldPos { + m.clearEditingModes() + m.tagsID = id + m.tagsEditing = true + m.tagsInput.SetValue("") + m.tagsInput.Focus() + m.updateTableHeight() + return m, nil + } + fieldPos++ + + // Due (5) + if m.detailFieldIndex == fieldPos { + m.dueID = id + if m.currentTaskDetail.Due != "" { + if ts, err := parseTaskDate(m.currentTaskDetail.Due); err == nil { + m.dueDate = ts + } else { + m.dueDate = time.Now() + } + } else { + m.dueDate = time.Now() + } + m.clearEditingModes() + m.dueEditing = true + m.updateTableHeight() + return m, nil + } + fieldPos++ + + // Start (6) + if m.detailFieldIndex == fieldPos { + // Start date is not editable in the original code, only toggled via 's' key + return m, nil + } + fieldPos++ + + // Entry (7) + if m.detailFieldIndex == fieldPos { + return m, nil // Not editable + } + fieldPos++ + + // Recurrence (8) - only if it exists + if m.currentTaskDetail.Recur != "" { + if m.detailFieldIndex == fieldPos { + m.clearEditingModes() + m.recurID = id + m.recurEditing = true + m.recurInput.SetValue(m.currentTaskDetail.Recur) + m.recurInput.Focus() + m.updateTableHeight() + return m, nil + } + fieldPos++ } + // Description and Annotations are not in the editable list return m, nil }
\ No newline at end of file diff --git a/internal/ui/keyhandlers.go b/internal/ui/keyhandlers.go index e3bed58..2ff0ed7 100644 --- a/internal/ui/keyhandlers.go +++ b/internal/ui/keyhandlers.go @@ -459,6 +459,9 @@ func (m *Model) handleShowTaskDetail() (tea.Model, tea.Cmd) { m.detailSearching = false m.detailSearchRegex = nil m.detailFieldIndex = 0 + m.detailBlinkField = -1 + m.detailBlinkOn = false + m.detailBlinkCount = 0 m.detailSearchInput = textinput.New() m.detailSearchInput.Placeholder = "Search..." m.detailSearchInput.Width = 30 diff --git a/internal/ui/table.go b/internal/ui/table.go index 281f3ba..cb9d13d 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -128,6 +128,9 @@ type Model struct { detailSearchInput textinput.Model detailSearchRegex *regexp.Regexp detailFieldIndex int // Current selected field in detail view + detailBlinkField int // Field that is currently blinking (-1 for none) + detailBlinkOn bool // Whether the blink is currently on + detailBlinkCount int // Number of blinks remaining } // editDoneMsg is emitted when the external editor process finishes. @@ -167,6 +170,21 @@ func (m *Model) clearEditingModes() { m.prioritySelecting = false } +// startDetailBlink starts blinking a field in the detail view +func (m *Model) startDetailBlink(fieldIndex int) tea.Cmd { + if !m.showTaskDetail { + return nil + } + if m.disco { + m.theme = RandomTheme() + m.applyTheme() + } + m.detailBlinkField = fieldIndex + m.detailBlinkOn = true + m.detailBlinkCount = blinkCycles + return blinkCmd() +} + func (m *Model) startBlink(id int, markDone bool) tea.Cmd { m.blinkID = id m.blinkMarkDone = markDone @@ -264,6 +282,11 @@ func (m *Model) reload() error { m.total = task.TotalTasks(tasks) m.inProgress = task.InProgressTasks(tasks) m.due = task.DueTasks(tasks, time.Now()) + + // Refresh current task detail if in detail view + if m.showTaskDetail { + m.refreshCurrentTaskDetail() + } m.computeColumnWidths() @@ -323,6 +346,18 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.handleBlinkingState(msg) } + // Check if we're in detail view + if m.showTaskDetail { + // If we're editing in detail view, let editing modes handle it + if m.prioritySelecting || m.tagsEditing || m.dueEditing || m.recurEditing { + if handled, model, cmd := m.handleEditingModes(msg); handled { + return model, cmd + } + } + // Otherwise handle detail view navigation + return m.handleTaskDetailMode(msg) + } + // Check if we're in any editing mode if handled, model, cmd := m.handleEditingModes(msg); handled { return model, cmd @@ -364,6 +399,21 @@ func (m *Model) handleEditDone(msg editDoneMsg) (tea.Model, tea.Cmd) { // handleBlinkMsg handles the blinking animation timer func (m *Model) handleBlinkMsg() (tea.Model, tea.Cmd) { + // Handle detail view blinking + if m.showTaskDetail && m.detailBlinkField != -1 { + m.detailBlinkOn = !m.detailBlinkOn + m.detailBlinkCount++ + + if m.detailBlinkCount >= blinkCycles { + m.detailBlinkField = -1 + m.detailBlinkOn = false + m.detailBlinkCount = 0 + } else { + return m, blinkCmd() + } + return m, nil + } + if m.blinkID == 0 { return m, nil } @@ -427,13 +477,13 @@ func (m Model) View() string { if m.dueEditing { view = lipgloss.JoinVertical(lipgloss.Left, view, - m.dueView(), + m.dueView(true), ) } if m.prioritySelecting { view = lipgloss.JoinVertical(lipgloss.Left, view, - m.priorityView(), + m.priorityView(true), ) } if m.descEditing { @@ -667,11 +717,14 @@ func (m Model) formatUrgency(u string, width int) string { return u } -func (m Model) dueView() string { - return fmt.Sprintf("due: %s", m.dueDate.Format("2006-01-02")) +func (m Model) dueView(showLabel bool) string { + if showLabel { + return fmt.Sprintf("due: %s", m.dueDate.Format("2006-01-02")) + } + return m.dueDate.Format("2006-01-02") } -func (m Model) priorityView() string { +func (m Model) priorityView(showLabel bool) string { var parts []string for i, p := range priorityOptions { label := p @@ -684,7 +737,10 @@ func (m Model) priorityView() string { } parts = append(parts, style.Render(label)) } - return "priority: " + strings.Join(parts, " ") + if showLabel { + return "priority: " + strings.Join(parts, " ") + } + return strings.Join(parts, " ") } func (m Model) highlightCell(base lipgloss.Style, re *regexp.Regexp, raw string) string { diff --git a/internal/ui/taskdetail.go b/internal/ui/taskdetail.go index d98416c..9d68e4f 100644 --- a/internal/ui/taskdetail.go +++ b/internal/ui/taskdetail.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/charmbracelet/lipgloss" - ) // Define field indices for navigation @@ -32,33 +31,33 @@ func (m *Model) renderTaskDetail() string { } t := m.currentTaskDetail - + // Create styles based on theme titleStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color(m.theme.SelectedFG)). Background(lipgloss.Color(m.theme.SelectedBG)). Padding(0, 1) - + labelStyle := lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color(m.theme.HeaderFG)) - + valueStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("252")) - + descStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("250")). PaddingLeft(2) - + // Build the detail view var lines []string - + // Title bar title := fmt.Sprintf("Task %d Details", t.ID) lines = append(lines, titleStyle.Render(title)) lines = append(lines, "") - + // Task fields currentField := 0 lines = append(lines, m.renderTaskFieldWithIndex("ID", fmt.Sprintf("%d", t.ID), labelStyle, valueStyle, currentField)) @@ -67,49 +66,77 @@ func (m *Model) renderTaskDetail() string { currentField++ lines = append(lines, m.renderTaskFieldWithIndex("Status", t.Status, labelStyle, valueStyle, currentField)) currentField++ - + // Priority with color - priorityValue := t.Priority - if priorityValue == "" { - priorityValue = "-" - } - priorityStyle := valueStyle.Copy() - switch t.Priority { - case "H": - priorityStyle = priorityStyle.Background(lipgloss.Color(m.theme.PrioHighBG)) - priorityValue = "H (High)" - case "M": - priorityStyle = priorityStyle.Background(lipgloss.Color(m.theme.PrioMedBG)) - priorityValue = "M (Medium)" - case "L": - priorityStyle = priorityStyle.Background(lipgloss.Color(m.theme.PrioLowBG)) - priorityValue = "L (Low)" + if m.prioritySelecting && m.priorityID == t.ID { + // Show priority selection UI + lines = append(lines, m.renderEditingField("Priority", m.priorityView(false), labelStyle, currentField)) + } else { + priorityValue := t.Priority + if priorityValue == "" { + priorityValue = "-" + } + priorityStyle := valueStyle.Copy() + switch t.Priority { + case "H": + priorityStyle = priorityStyle.Background(lipgloss.Color(m.theme.PrioHighBG)) + priorityValue = "H (High)" + case "M": + priorityStyle = priorityStyle.Background(lipgloss.Color(m.theme.PrioMedBG)) + priorityValue = "M (Medium)" + case "L": + priorityStyle = priorityStyle.Background(lipgloss.Color(m.theme.PrioLowBG)) + priorityValue = "L (Low)" + } + lines = append(lines, m.renderTaskFieldWithIndex("Priority", priorityValue, labelStyle, priorityStyle, currentField)) } - lines = append(lines, m.renderTaskFieldWithIndex("Priority", priorityValue, labelStyle, priorityStyle, currentField)) currentField++ - + // Tags - tagStr := strings.Join(t.Tags, ", ") - if tagStr == "" { - tagStr = "-" + if m.tagsEditing && m.tagsID == t.ID { + // Show tags editing UI without prompt + originalPrompt := m.tagsInput.Prompt + m.tagsInput.Prompt = "" + tagsView := m.tagsInput.View() + m.tagsInput.Prompt = originalPrompt + lines = append(lines, m.renderEditingField("Tags", tagsView, labelStyle, currentField)) + } else { + tagStr := strings.Join(t.Tags, ", ") + if tagStr == "" { + tagStr = "-" + } + lines = append(lines, m.renderTaskFieldWithIndex("Tags", tagStr, labelStyle, valueStyle, currentField)) } - lines = append(lines, m.renderTaskFieldWithIndex("Tags", tagStr, labelStyle, valueStyle, currentField)) currentField++ - + // Dates - lines = append(lines, m.renderTaskFieldWithIndex("Due", m.formatTaskDate(t.Due), labelStyle, valueStyle, currentField)) + if m.dueEditing && m.dueID == t.ID { + // Show due date editing UI + lines = append(lines, m.renderEditingField("Due", m.dueView(false), labelStyle, currentField)) + } else { + lines = append(lines, m.renderTaskFieldWithIndex("Due", m.formatTaskDate(t.Due), labelStyle, valueStyle, currentField)) + } currentField++ lines = append(lines, m.renderTaskFieldWithIndex("Start", m.formatTaskDate(t.Start), labelStyle, valueStyle, currentField)) currentField++ lines = append(lines, m.renderTaskFieldWithIndex("Entry", m.formatTaskDate(t.Entry), labelStyle, valueStyle, currentField)) currentField++ - + // Recurrence if t.Recur != "" { - lines = append(lines, m.renderTaskFieldWithIndex("Recurrence", t.Recur, labelStyle, valueStyle, currentField)) + if m.recurEditing && m.recurID == t.ID { + // Show recurrence editing UI without prompt + originalPrompt := m.recurInput.Prompt + m.recurInput.Prompt = "" + recurView := m.recurInput.View() + m.recurInput.Prompt = originalPrompt + lines = append(lines, m.renderEditingField("Recurrence", recurView, labelStyle, currentField)) + } else { + lines = append(lines, m.renderTaskFieldWithIndex("Recurrence", t.Recur, labelStyle, valueStyle, currentField)) + } currentField++ } - + // Description - with full space lines = append(lines, "") descLabelStyle := labelStyle.Copy() @@ -130,7 +157,7 @@ func (m *Model) renderTaskDetail() string { lines = append(lines, descValueStyle.Render("-")) } currentField++ - + // Annotations if len(t.Annotations) > 0 { lines = append(lines, "") @@ -150,44 +177,66 @@ func (m *Model) renderTaskDetail() string { lines = append(lines, annValueStyle.Render(annText)) } } - + // Instructions at bottom lines = append(lines, "") lines = append(lines, "") instructionStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("245")). Italic(true) - lines = append(lines, instructionStyle.Render("Press ESC or Q to return to table view")) - lines = append(lines, instructionStyle.Render("Use ↑/k and ↓/j to navigate fields")) - if m.detailSearching { - lines = append(lines, instructionStyle.Render("Type to search, Enter to confirm")) + // Check if we're in any editing mode + if m.prioritySelecting || m.tagsEditing || m.dueEditing || m.recurEditing { + lines = append(lines, instructionStyle.Render("Editing mode - Follow on-screen prompts")) } else { - lines = append(lines, instructionStyle.Render("Press / to search")) + lines = append(lines, instructionStyle.Render("Press ESC or q to return to table view")) + lines = append(lines, instructionStyle.Render("Use ↑/k and ↓/j to navigate fields")) + lines = append(lines, instructionStyle.Render("Press i or Enter to edit (Priority, Tags, Due, Recurrence)")) + if m.detailSearching { + lines = append(lines, instructionStyle.Render("Type to search, Enter to confirm")) + } else { + lines = append(lines, instructionStyle.Render("Press / to search")) + } } - + // Add search input if searching if m.detailSearching { searchStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("248")). PaddingTop(1) - lines = append(lines, searchStyle.Render("Search: " + m.detailSearchInput.View())) + lines = append(lines, searchStyle.Render("Search: "+m.detailSearchInput.View())) } - + return strings.Join(lines, "\n") } +// renderEditingField renders a field that is currently being edited +func (m *Model) renderEditingField(label, editView string, labelStyle lipgloss.Style, fieldIndex int) string { + // Apply selection highlighting if this field is selected + if m.detailFieldIndex == fieldIndex { + labelStyle = labelStyle.Background(lipgloss.Color(m.theme.SelectedBG)) + } + + return fmt.Sprintf("%s %s", labelStyle.Render(label+":"), editView) +} + // renderTaskFieldWithIndex renders a single field with highlighting based on index func (m *Model) renderTaskFieldWithIndex(label, value string, labelStyle, valueStyle lipgloss.Style, fieldIndex int) string { if value == "" { value = "-" } - - // Apply selection highlighting if this field is selected - if m.detailFieldIndex == fieldIndex { + + // Apply blinking if this field is blinking + if m.detailBlinkField == fieldIndex && m.detailBlinkOn { + // Use a bright background for blinking + blinkBG := lipgloss.Color("226") // Bright yellow + labelStyle = labelStyle.Background(blinkBG).Foreground(lipgloss.Color("0")) + valueStyle = valueStyle.Background(blinkBG).Foreground(lipgloss.Color("0")) + } else if m.detailFieldIndex == fieldIndex { + // Apply selection highlighting if this field is selected labelStyle = labelStyle.Background(lipgloss.Color(m.theme.SelectedBG)) valueStyle = valueStyle.Background(lipgloss.Color(m.theme.SelectedBG)) } - + // Highlight search matches if m.detailSearchRegex != nil && m.detailSearchRegex.MatchString(value) { value = m.highlightMatches(value, m.detailSearchRegex) @@ -207,25 +256,44 @@ func (m *Model) formatTaskDate(dateStr string) string { return dateStr } +// refreshCurrentTaskDetail updates the current task detail pointer after a reload +func (m *Model) refreshCurrentTaskDetail() { + if m.currentTaskDetail == nil { + return + } + + id := m.currentTaskDetail.ID + for i := range m.tasks { + if m.tasks[i].ID == id { + m.currentTaskDetail = &m.tasks[i] + return + } + } + + // Task no longer exists, clear detail view + m.showTaskDetail = false + m.currentTaskDetail = nil +} + // getDetailFieldCount returns the actual number of navigable fields for the current task func (m *Model) getDetailFieldCount() int { if m.currentTaskDetail == nil { return 0 } - + // Basic fields that are always present: ID, UUID, Status, Priority, Tags, Due, Start, Entry, Description count := 9 - + // Add recurrence if present if m.currentTaskDetail.Recur != "" { count++ } - + // Add annotations if present if len(m.currentTaskDetail.Annotations) > 0 { count++ } - + return count } @@ -234,15 +302,15 @@ func (m *Model) highlightMatches(text string, re *regexp.Regexp) string { highlightStyle := lipgloss.NewStyle(). Background(lipgloss.Color(m.theme.SearchBG)). Foreground(lipgloss.Color(m.theme.SearchFG)) - + matches := re.FindAllStringIndex(text, -1) if len(matches) == 0 { return text } - + var result strings.Builder lastEnd := 0 - + for _, match := range matches { start, end := match[0], match[1] // Add text before match @@ -253,11 +321,11 @@ func (m *Model) highlightMatches(text string, re *regexp.Regexp) string { result.WriteString(highlightStyle.Render(text[start:end])) lastEnd = end } - + // Add remaining text if lastEnd < len(text) { result.WriteString(text[lastEnd:]) } - + return result.String() -}
\ No newline at end of file +} diff --git a/tasksamurai b/tasksamurai Binary files differindex bee372e..0c7731e 100755 --- a/tasksamurai +++ b/tasksamurai |
