summaryrefslogtreecommitdiff
path: root/internal/ui/table.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-06-28 11:32:30 +0300
committerPaul Buetow <paul@buetow.org>2025-06-28 11:32:30 +0300
commitd06b179332e82635f6a7c8366e51fb5b421a7c2c (patch)
treec571672a146d07de8d3969dc7e2c272331abef6a /internal/ui/table.go
parentb659b8cf87c86280f62cef0f499a60b999e6ce6b (diff)
feat: add external editor support for description editing in detail viewv0.9.1
- Enable editing task description using external editor ($EDITOR) - Create temporary file for editing and handle TUI editor properly - Show editing indicator while external editor is active - Add blinking feedback after successful description update - Increment version from 0.9.0 to 0.9.1 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'internal/ui/table.go')
-rw-r--r--internal/ui/table.go100
1 files changed, 96 insertions, 4 deletions
diff --git a/internal/ui/table.go b/internal/ui/table.go
index cb9d13d..74d5440 100644
--- a/internal/ui/table.go
+++ b/internal/ui/table.go
@@ -3,6 +3,8 @@ package ui
import (
"fmt"
"math/rand"
+ "os"
+ "os/exec"
"regexp"
"strconv"
"strings"
@@ -127,15 +129,23 @@ type Model struct {
detailSearching bool
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
+ 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
+ detailDescEditing bool // Whether we're editing description in detail view
+ detailDescTempFile string // Temp file path for description editing
}
// editDoneMsg is emitted when the external editor process finishes.
type editDoneMsg struct{ err error }
+// descEditDoneMsg is emitted when the external editor for description finishes.
+type descEditDoneMsg struct{
+ err error
+ tempFile string
+}
+
type blinkMsg struct{}
// blinkInterval controls how quickly the row flashes when a task changes.
@@ -153,6 +163,43 @@ func editCmd(id int) tea.Cmd {
return tea.ExecProcess(c, func(err error) tea.Msg { return editDoneMsg{err: err} })
}
+// editDescriptionCmd returns a command that opens the description in external editor
+func editDescriptionCmd(description string) tea.Cmd {
+ return func() tea.Msg {
+ // Create temp file
+ tmpFile, err := os.CreateTemp("", "tasksamurai-desc-*.txt")
+ if err != nil {
+ return descEditDoneMsg{err: err, tempFile: ""}
+ }
+ tmpPath := tmpFile.Name()
+
+ // Write current description to temp file
+ _, err = tmpFile.WriteString(description)
+ tmpFile.Close()
+ if err != nil {
+ os.Remove(tmpPath)
+ return descEditDoneMsg{err: err, tempFile: ""}
+ }
+
+ // Get editor from environment
+ editor := os.Getenv("EDITOR")
+ if editor == "" {
+ editor = "vi" // fallback to vi
+ }
+
+ // Create the command
+ c := exec.Command(editor, tmpPath)
+ c.Stdin = os.Stdin
+ c.Stdout = os.Stdout
+ c.Stderr = os.Stderr
+
+ // Use ExecProcess to properly handle the external TUI editor
+ return tea.ExecProcess(c, func(err error) tea.Msg {
+ return descEditDoneMsg{err: err, tempFile: tmpPath}
+ })()
+ }
+}
+
func blinkCmd() tea.Cmd {
return tea.Tick(blinkInterval, func(time.Time) tea.Msg { return blinkMsg{} })
}
@@ -335,6 +382,8 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.handleWindowResize(msg)
case editDoneMsg:
return m.handleEditDone(msg)
+ case descEditDoneMsg:
+ return m.handleDescEditDone(msg)
case blinkMsg:
return m.handleBlinkMsg()
case struct{ clearStatus bool }:
@@ -397,6 +446,49 @@ func (m *Model) handleEditDone(msg editDoneMsg) (tea.Model, tea.Cmd) {
return m, cmd
}
+// handleDescEditDone handles the completion of description editing
+func (m *Model) handleDescEditDone(msg descEditDoneMsg) (tea.Model, tea.Cmd) {
+ m.detailDescEditing = false
+ defer os.Remove(msg.tempFile) // Clean up temp file
+
+ if msg.err != nil {
+ m.statusMsg = fmt.Sprintf("Edit error: %v", msg.err)
+ cmd := tea.Tick(2*time.Second, func(time.Time) tea.Msg {
+ return struct{ clearStatus bool }{true}
+ })
+ return m, cmd
+ }
+
+ // Read the edited content
+ content, err := os.ReadFile(msg.tempFile)
+ if err != nil {
+ m.statusMsg = fmt.Sprintf("Error reading file: %v", err)
+ cmd := tea.Tick(2*time.Second, func(time.Time) tea.Msg {
+ return struct{ clearStatus bool }{true}
+ })
+ return m, cmd
+ }
+
+ // Update the description
+ newDesc := strings.TrimSpace(string(content))
+ if m.currentTaskDetail != nil {
+ err = task.SetDescription(m.currentTaskDetail.ID, newDesc)
+ if err != nil {
+ m.statusMsg = fmt.Sprintf("Error updating description: %v", err)
+ cmd := tea.Tick(2*time.Second, func(time.Time) tea.Msg {
+ return struct{ clearStatus bool }{true}
+ })
+ return m, cmd
+ }
+
+ // Reload and start blinking
+ m.reload()
+ return m, m.startDetailBlink(9) // Description field index
+ }
+
+ return m, nil
+}
+
// handleBlinkMsg handles the blinking animation timer
func (m *Model) handleBlinkMsg() (tea.Model, tea.Cmd) {
// Handle detail view blinking