[WIP] Editing

This commit is contained in:
Martin Pander
2024-05-23 16:01:25 +02:00
parent 7712711736
commit fe00170a5c
6 changed files with 133 additions and 24 deletions

View File

@ -21,6 +21,7 @@ type Keymap struct {
SetProject key.Binding SetProject key.Binding
Select key.Binding Select key.Binding
Insert key.Binding Insert key.Binding
Undo key.Binding
} }
// NewKeymap creates a new Keymap. // NewKeymap creates a new Keymap.
@ -100,5 +101,10 @@ func NewKeymap() *Keymap {
key.WithKeys("i"), key.WithKeys("i"),
key.WithHelp("insert", "Insert mode"), key.WithHelp("insert", "Insert mode"),
), ),
Undo: key.NewBinding(
key.WithKeys("u"),
key.WithHelp("undo", "Undo"),
),
} }
} }

View File

@ -102,7 +102,7 @@ func (p *ContextPickerPage) View() string {
func (p *ContextPickerPage) updateContextCmd() tea.Msg { func (p *ContextPickerPage) updateContextCmd() tea.Msg {
context := p.form.GetString("context") context := p.form.GetString("context")
if context == "(none)" { if context == "(none)" {
context = "none" context = ""
} }
return UpdateContextMsg(p.common.TW.GetContext(context)) return UpdateContextMsg(p.common.TW.GetContext(context))
} }

View File

@ -150,6 +150,9 @@ func (p *ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
p.subpageActive = true p.subpageActive = true
p.common.PushPage(p) p.common.PushPage(p)
return p.subpage, nil return p.subpage, nil
case key.Matches(msg, p.common.Keymap.Undo):
p.common.TW.Undo()
return p, p.getTasks()
} }
} }
@ -175,6 +178,8 @@ func (p *ReportPage) View() string {
} }
func (p *ReportPage) populateTaskTable(tasks taskwarrior.Tasks) { func (p *ReportPage) populateTaskTable(tasks taskwarrior.Tasks) {
var selected int
nCols := len(p.activeReport.Columns) nCols := len(p.activeReport.Columns)
columns := make([]table.Column, 0) columns := make([]table.Column, 0)
columnSizes := make([]int, nCols) columnSizes := make([]int, nCols)
@ -182,6 +187,10 @@ func (p *ReportPage) populateTaskTable(tasks taskwarrior.Tasks) {
rows := make([]table.Row, len(tasks)) rows := make([]table.Row, len(tasks))
for i, task := range tasks { for i, task := range tasks {
if p.selectedTask != nil && task.Uuid == p.selectedTask.Uuid {
selected = i
}
row := table.Row{} row := table.Row{}
for i, col := range p.activeReport.Columns { for i, col := range p.activeReport.Columns {
field := task.GetString(col) field := task.GetString(col)
@ -210,6 +219,10 @@ func (p *ReportPage) populateTaskTable(tasks taskwarrior.Tasks) {
columns = append(columns, table.Column{Title: label, Width: max(columnSizes[i], len(label))}) columns = append(columns, table.Column{Title: label, Width: max(columnSizes[i], len(label))})
} }
if selected == 0 {
selected = p.taskTable.Cursor()
}
p.taskTable = table.New( p.taskTable = table.New(
table.WithColumns(columns), table.WithColumns(columns),
table.WithRows(rows), table.WithRows(rows),
@ -218,6 +231,12 @@ func (p *ReportPage) populateTaskTable(tasks taskwarrior.Tasks) {
// table.WithWidth(100), // table.WithWidth(100),
) )
p.taskTable.SetStyles(p.tableStyle) p.taskTable.SetStyles(p.tableStyle)
if selected < len(p.tasks) {
p.taskTable.SetCursor(selected)
} else {
p.taskTable.SetCursor(len(p.tasks) - 1)
}
} }
func (p *ReportPage) getTasks() tea.Cmd { func (p *ReportPage) getTasks() tea.Cmd {

View File

@ -3,6 +3,7 @@ package pages
import ( import (
"fmt" "fmt"
"log/slog" "log/slog"
"strings"
"tasksquire/common" "tasksquire/common"
"tasksquire/taskwarrior" "tasksquire/taskwarrior"
"time" "time"
@ -24,11 +25,17 @@ const (
) )
type TaskEditorPage struct { type TaskEditorPage struct {
common *common.Common common *common.Common
task taskwarrior.Task task taskwarrior.Task
form *huh.Form form *huh.Form
mode Mode mode Mode
statusline tea.Model statusline tea.Model
nFields int
currentField int
// TODO: rework support for adding tags and projects
additionalTags string
additionalProject string
} }
type TaskEditorKeys struct { type TaskEditorKeys struct {
@ -85,11 +92,27 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor
Title("Project"). Title("Project").
Value(&p.task.Project), Value(&p.task.Project),
huh.NewInput().
Title("Project").
Value(&p.additionalProject).
Validate(func(project string) error {
if strings.Contains(project, " ") {
return fmt.Errorf("project name cannot contain spaces")
}
return nil
}).
Inline(true),
huh.NewMultiSelect[string](). huh.NewMultiSelect[string]().
Options(huh.NewOptions(tagOptions...)...). Options(huh.NewOptions(tagOptions...)...).
Title("Tags"). Title("Tags").
Value(&p.task.Tags), Value(&p.task.Tags),
huh.NewInput().
Title("Tags").
Value(&p.additionalTags).
Inline(true),
huh.NewInput(). huh.NewInput().
Title("Due"). Title("Due").
Value(&p.task.Due). Value(&p.task.Due).
@ -112,10 +135,13 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor
WithShowHelp(false). WithShowHelp(false).
WithShowErrors(true). WithShowErrors(true).
// use styles from common // use styles from common
WithHeight(30). WithHeight(40).
WithWidth(50). WithWidth(50).
WithTheme(p.common.Styles.Form) WithTheme(p.common.Styles.Form)
p.nFields = 6
p.currentField = 0
p.statusline = NewStatusLine(common, p.mode) p.statusline = NewStatusLine(common, p.mode)
return p return p
@ -141,7 +167,6 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
p.mode = ModeInsert p.mode = ModeInsert
} }
} }
switch p.mode { switch p.mode {
case ModeNormal: case ModeNormal:
switch msg := msg.(type) { switch msg := msg.(type) {
@ -208,7 +233,19 @@ func (p *TaskEditorPage) View() string {
} }
func (p *TaskEditorPage) updateTasksCmd() tea.Msg { func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
p.common.TW.AddTask(&p.task) if p.task.Project == "(none)" {
p.task.Project = ""
}
if p.task.Priority == "(none)" {
p.task.Priority = ""
}
if p.additionalTags != "" {
p.task.Tags = append(p.task.Tags, strings.Split(p.additionalTags, " ")...)
}
if p.additionalProject != "" {
p.task.Project = p.additionalProject
}
p.common.TW.ImportTask(&p.task)
return UpdatedTasksMsg{} return UpdatedTasksMsg{}
} }

View File

@ -19,25 +19,26 @@ func (a Annotation) String() string {
} }
type Task struct { type Task struct {
Id int64 `json:"id,omitempty"` Id int64 `json:"id,omitempty"`
Uuid string `json:"uuid,omitempty"` Uuid string `json:"uuid,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
Project string `json:"project,omitempty"` Project string `json:"project"`
Priority string `json:"priority,omitempty"` Priority string `json:"priority"`
Status string `json:"status,omitempty"` Status string `json:"status,omitempty"`
Tags []string `json:"tags,omitempty"` Tags []string `json:"tags"`
Depends []string `json:"depends,omitempty"` Depends []string `json:"depends,omitempty"`
DependsIds string
Urgency float32 `json:"urgency,omitempty"` Urgency float32 `json:"urgency,omitempty"`
Parent string `json:"parent,omitempty"` Parent string `json:"parent,omitempty"`
Due string `json:"due,omitempty"` Due string `json:"due"`
Wait string `json:"wait,omitempty"` Wait string `json:"wait"`
Scheduled string `json:"scheduled,omitempty"` Scheduled string `json:"scheduled"`
Until string `json:"until,omitempty"` Until string `json:"until"`
Start string `json:"start,omitempty"` Start string `json:"start,omitempty"`
End string `json:"end,omitempty"` End string `json:"end,omitempty"`
Entry string `json:"entry,omitempty"` Entry string `json:"entry,omitempty"`
Modified string `json:"modified,omitempty"` Modified string `json:"modified,omitempty"`
Recur string `json:"recur,omitempty"` Recur string `json:"recur"`
Annotations []Annotation `json:"annotations,omitempty"` Annotations []Annotation `json:"annotations,omitempty"`
} }
@ -171,8 +172,7 @@ func (t *Task) GetString(fieldWFormat string) string {
return "" return ""
} }
} }
// TODO: get Ids from UUIDs return t.DependsIds
return strings.Join(t.Depends, ", ")
case "recur": case "recur":
return t.Recur return t.Recur

View File

@ -1,3 +1,6 @@
// TODO: error handling
// TODO: split combinedOutput and handle stderr differently
// TODO: reorder functions
package taskwarrior package taskwarrior
import ( import (
@ -90,6 +93,8 @@ type TaskWarrior interface {
AddTask(task *Task) error AddTask(task *Task) error
ImportTask(task *Task) ImportTask(task *Task)
SetTaskDone(task *Task) SetTaskDone(task *Task)
Undo()
} }
type TaskSquire struct { type TaskSquire struct {
@ -138,6 +143,7 @@ func (ts *TaskSquire) GetTasks(report *Report, filter ...string) Tasks {
for _, context := range ts.contexts { for _, context := range ts.contexts {
if context.Active && context.Name != "none" { if context.Active && context.Name != "none" {
args = append(args, context.ReadFilter) args = append(args, context.ReadFilter)
break
} }
} }
} }
@ -160,13 +166,39 @@ func (ts *TaskSquire) GetTasks(report *Report, filter ...string) Tasks {
return nil return nil
} }
for _, task := range tasks {
if task.Depends != nil && len(task.Depends) > 0 {
ids := make([]string, len(task.Depends))
for i, dependUuid := range task.Depends {
ids[i] = ts.getIds([]string{fmt.Sprintf("uuid:%s", dependUuid)})
}
task.DependsIds = strings.Join(ids, " ")
}
}
return tasks return tasks
} }
func (ts *TaskSquire) getIds(filter []string) string {
cmd := exec.Command(twBinary, append(append(ts.defaultArgs, filter...), "_ids")...)
out, err := cmd.CombinedOutput()
if err != nil {
slog.Error("Failed getting field:", err)
return ""
}
return strings.TrimSpace(string(out))
}
func (ts *TaskSquire) GetContext(context string) *Context { func (ts *TaskSquire) GetContext(context string) *Context {
ts.mutex.Lock() ts.mutex.Lock()
defer ts.mutex.Unlock() defer ts.mutex.Unlock()
if context == "" {
context = "none"
}
if context, ok := ts.contexts[context]; ok { if context, ok := ts.contexts[context]; ok {
return context return context
} else { } else {
@ -276,6 +308,10 @@ func (ts *TaskSquire) SetContext(context *Context) error {
ts.mutex.Lock() ts.mutex.Lock()
defer ts.mutex.Unlock() defer ts.mutex.Unlock()
if context.Name == "none" && ts.contexts["none"].Active {
return nil
}
cmd := exec.Command(twBinary, []string{"context", context.Name}...) cmd := exec.Command(twBinary, []string{"context", context.Name}...)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
slog.Error("Failed setting context:", err) slog.Error("Failed setting context:", err)
@ -355,6 +391,17 @@ func (ts *TaskSquire) SetTaskDone(task *Task) {
} }
} }
func (ts *TaskSquire) Undo() {
ts.mutex.Lock()
defer ts.mutex.Unlock()
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"undo"}...)...)
err := cmd.Run()
if err != nil {
slog.Error("Failed undoing task:", err)
}
}
func (ts *TaskSquire) extractConfig() *TWConfig { func (ts *TaskSquire) extractConfig() *TWConfig {
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"_show"}...)...) cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"_show"}...)...)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()