[WIP] Editing
This commit is contained in:
@ -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"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user