From 7712711736fcbc03d25dc5f4e67684a6d66a3789 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 23 May 2024 07:15:08 +0200 Subject: [PATCH] [WIP] Task editing --- pages/report.go | 3 + pages/taskEditor.go | 9 ++- taskwarrior/models.go | 155 +++++++++++++++++++++++++++++++------ taskwarrior/taskwarrior.go | 12 +++ 4 files changed, 152 insertions(+), 27 deletions(-) diff --git a/pages/report.go b/pages/report.go index fc181de..b519273 100644 --- a/pages/report.go +++ b/pages/report.go @@ -141,6 +141,9 @@ func (p *ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { p.subpageActive = true p.common.PushPage(p) return p.subpage, nil + case key.Matches(msg, p.common.Keymap.Ok): + p.common.TW.SetTaskDone(p.selectedTask) + return p, p.getTasks() case key.Matches(msg, p.common.Keymap.SetProject): p.subpage = NewProjectPickerPage(p.common, p.activeProject) p.subpage.Init() diff --git a/pages/taskEditor.go b/pages/taskEditor.go index 3aff2e7..8262f77 100644 --- a/pages/taskEditor.go +++ b/pages/taskEditor.go @@ -43,7 +43,12 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor p := &TaskEditorPage{ common: common, task: task, - mode: ModeInsert, + } + + if p.task.Uuid == "" { + p.mode = ModeInsert + } else { + p.mode = ModeNormal } if p.task.Priority == "" { @@ -197,7 +202,7 @@ func (p *TaskEditorPage) View() string { return lipgloss.JoinVertical( lipgloss.Left, // lipgloss.Place(p.common.Width(), p.common.Height()-1, 0.5, 0.5, p.form.View(), lipgloss.WithWhitespaceBackground(p.common.Styles.Warning.GetForeground())), - lipgloss.Place(p.common.Width(), p.common.Height()-1, 0.5, 0.5, p.form.View(), lipgloss.WithWhitespaceChars(".")), + lipgloss.Place(p.common.Width(), p.common.Height()-1, 0.5, 0.5, p.form.View(), lipgloss.WithWhitespaceChars(" . ")), p.statusline.View(), ) } diff --git a/taskwarrior/models.go b/taskwarrior/models.go index 32b97f3..31627ba 100644 --- a/taskwarrior/models.go +++ b/taskwarrior/models.go @@ -9,26 +9,36 @@ import ( "time" ) +type Annotation struct { + Entry string `json:"entry,omitempty"` + Description string `json:"description,omitempty"` +} + +func (a Annotation) String() string { + return fmt.Sprintf("%s %s", a.Entry, a.Description) +} + type Task struct { - Id int64 `json:"id,omitempty"` - Uuid string `json:"uuid,omitempty"` - Description string `json:"description,omitempty"` - Project string `json:"project,omitempty"` - Priority string `json:"priority,omitempty"` - Status string `json:"status,omitempty"` - Tags []string `json:"tags,omitempty"` - Depends []string `json:"depends,omitempty"` - Urgency float32 `json:"urgency,omitempty"` - Parent string `json:"parent,omitempty"` - Due string `json:"due,omitempty"` - Wait string `json:"wait,omitempty"` - Scheduled string `json:"scheduled,omitempty"` - Until string `json:"until,omitempty"` - Start string `json:"start,omitempty"` - End string `json:"end,omitempty"` - Entry string `json:"entry,omitempty"` - Modified string `json:"modified,omitempty"` - Recur string `json:"recur,omitempty"` + Id int64 `json:"id,omitempty"` + Uuid string `json:"uuid,omitempty"` + Description string `json:"description,omitempty"` + Project string `json:"project,omitempty"` + Priority string `json:"priority,omitempty"` + Status string `json:"status,omitempty"` + Tags []string `json:"tags,omitempty"` + Depends []string `json:"depends,omitempty"` + Urgency float32 `json:"urgency,omitempty"` + Parent string `json:"parent,omitempty"` + Due string `json:"due,omitempty"` + Wait string `json:"wait,omitempty"` + Scheduled string `json:"scheduled,omitempty"` + Until string `json:"until,omitempty"` + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` + Entry string `json:"entry,omitempty"` + Modified string `json:"modified,omitempty"` + Recur string `json:"recur,omitempty"` + Annotations []Annotation `json:"annotations,omitempty"` } func (t *Task) GetString(fieldWFormat string) string { @@ -37,41 +47,136 @@ func (t *Task) GetString(fieldWFormat string) string { switch field { case "id": return strconv.FormatInt(t.Id, 10) + case "uuid": + if format == "short" { + return t.Uuid[:8] + } return t.Uuid + case "description": - return t.Description + switch format { + case "desc": + return t.Description + case "oneline": + if len(t.Annotations) == 0 { + return t.Description + } else { + var annotations []string + for _, a := range t.Annotations { + annotations = append(annotations, a.String()) + } + return fmt.Sprintf("%s %s", t.Description, strings.Join(annotations, " ")) + } + case "truncated": + if len(t.Description) > 20 { + return t.Description[:20] + "..." + } else { + return t.Description + } + case "count": + return fmt.Sprintf("%s [%d]", t.Description, len(t.Annotations)) + case "truncated_count": + if len(t.Description) > 20 { + return fmt.Sprintf("%s... [%d]", t.Description[:20], len(t.Annotations)) + } else { + return fmt.Sprintf("%s [%d]", t.Description, len(t.Annotations)) + } + } + + if len(t.Annotations) == 0 { + return t.Description + } else { + var annotations []string + for _, a := range t.Annotations { + annotations = append(annotations, a.String()) + } + // TODO support for multiline? + return fmt.Sprintf("%s %s", t.Description, strings.Join(annotations, " ")) + } + case "project": + switch format { + case "parent": + parent, _, _ := strings.Cut(t.Project, ".") + return parent + case "indented": + return fmt.Sprintf(" %s", t.Project) + } return t.Project + case "priority": return t.Priority + case "status": return t.Status + case "tags": - return strings.Join(t.Tags, ", ") + switch format { + case "count": + return strconv.Itoa(len(t.Tags)) + case "indicator": + if len(t.Tags) > 0 { + return "+" + } else { + return "" + } + } + return strings.Join(t.Tags, " ") + + case "parent": + if format == "short" { + return t.Parent[:8] + } + return t.Parent + case "urgency": + if format == "integer" { + return strconv.Itoa(int(t.Urgency)) + } return fmt.Sprintf("%.2f", t.Urgency) + case "due": return formatDate(t.Due, format) + case "wait": - return t.Wait + return formatDate(t.Wait, format) + case "scheduled": return formatDate(t.Scheduled, format) + case "end": - return t.End + return formatDate(t.End, format) + case "entry": return formatDate(t.Entry, format) + case "modified": - return t.Modified + return formatDate(t.Modified, format) + case "start": return formatDate(t.Start, format) + case "until": return formatDate(t.Until, format) - // TODO: implement these fields + case "depends": + switch format { + case "count": + return strconv.Itoa(len(t.Depends)) + case "indicator": + if len(t.Depends) > 0 { + return "D" + } else { + return "" + } + } + // TODO: get Ids from UUIDs return strings.Join(t.Depends, ", ") + case "recur": return t.Recur + default: slog.Error(fmt.Sprintf("Field not implemented: %s", field)) return "" diff --git a/taskwarrior/taskwarrior.go b/taskwarrior/taskwarrior.go index 261260e..a4f1678 100644 --- a/taskwarrior/taskwarrior.go +++ b/taskwarrior/taskwarrior.go @@ -89,6 +89,7 @@ type TaskWarrior interface { GetTasks(report *Report, filter ...string) Tasks AddTask(task *Task) error ImportTask(task *Task) + SetTaskDone(task *Task) } type TaskSquire struct { @@ -343,6 +344,17 @@ func (ts *TaskSquire) ImportTask(task *Task) { } } +func (ts *TaskSquire) SetTaskDone(task *Task) { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"done", task.Uuid}...)...) + err := cmd.Run() + if err != nil { + slog.Error("Failed setting task done:", err) + } +} + func (ts *TaskSquire) extractConfig() *TWConfig { cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"_show"}...)...) output, err := cmd.CombinedOutput()