diff --git a/common/common.go b/common/common.go index 30158cb..cde38ca 100644 --- a/common/common.go +++ b/common/common.go @@ -15,6 +15,7 @@ type Common struct { TW taskwarrior.TaskWarrior Keymap *Keymap Styles *Styles + Udas []string pageStack *Stack[Component] width int @@ -27,6 +28,7 @@ func NewCommon(ctx context.Context, tw taskwarrior.TaskWarrior) *Common { TW: tw, Keymap: NewKeymap(), Styles: NewStyles(tw.GetConfig()), + Udas: tw.GetUdas(), pageStack: NewStack[Component](), } diff --git a/common/styles.go b/common/styles.go index 29266ea..972e685 100644 --- a/common/styles.go +++ b/common/styles.go @@ -29,6 +29,8 @@ type Styles struct { ColumnBlurred lipgloss.Style ColumnInsert lipgloss.Style + Colors map[string]lipgloss.Style + // TODO: make color config completely dynamic to account for keyword., project., tag. and uda. colors Active lipgloss.Style Alternate lipgloss.Style @@ -114,10 +116,12 @@ func NewStyles(config *taskwarrior.TWConfig) *Styles { func parseColors(config map[string]string) *Styles { styles := Styles{} + colors := make(map[string]lipgloss.Style) for key, value := range config { if strings.HasPrefix(key, "color.") { _, colorValue, _ := strings.Cut(key, ".") + colors[colorValue] = parseColorString(value) switch colorValue { case "active": styles.Active = parseColorString(value) @@ -217,6 +221,8 @@ func parseColors(config map[string]string) *Styles { } } + styles.Colors = colors + return &styles } diff --git a/components/table/table.go b/components/table/table.go index 61a9dea..01a41d5 100644 --- a/components/table/table.go +++ b/components/table/table.go @@ -1,6 +1,7 @@ package table import ( + "fmt" "strings" "time" @@ -189,6 +190,18 @@ func (m *Model) parseRowStyles(rows taskwarrior.Tasks) []lipgloss.Style { styles[i] = m.common.Styles.Recurring.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding()) continue } + // TOOD: move udas to proper location + if len(m.common.Udas) > 0 { + for _, uda := range m.common.Udas { + if u, ok := task.Udas[uda]; ok { + if style, ok := m.common.Styles.Colors[fmt.Sprintf("uda.%s.%s", uda, u)]; ok { + styles[i] = style.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding()) + } + } + } + continue + } + // TODO: make styles optional and discard if empty if len(task.Tags) > 0 { styles[i] = m.common.Styles.Tagged.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding()) taskIteration: @@ -200,7 +213,6 @@ func (m *Model) parseRowStyles(rows taskwarrior.Tasks) []lipgloss.Style { } continue } - // TODO implement uda styles[i] = m.common.Styles.Base.Inherit(m.styles.Cell).Margin(m.styles.Cell.GetMargin()).Padding(m.styles.Cell.GetPadding()) } return styles diff --git a/pages/taskEditor.go b/pages/taskEditor.go index 3406327..71c1b88 100644 --- a/pages/taskEditor.go +++ b/pages/taskEditor.go @@ -3,7 +3,6 @@ package pages import ( "fmt" "log/slog" - "slices" "strings" "tasksquire/common" @@ -52,8 +51,6 @@ func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPag priorityOptions := append([]string{"(none)"}, p.common.TW.GetPriorities()...) projectOptions := append([]string{"(none)"}, p.common.TW.GetProjects()...) tagOptions := p.common.TW.GetTags() - tagOptions = append(tagOptions, strings.Split(p.common.TW.GetConfig().Get("uda.tasksquire.tags.default"), ",")...) - slices.Sort(tagOptions) p.areas = map[area]tea.Model{ areaTask: NewTaskEdit(p.common, &p.task.Description, &p.task.Priority, &p.task.Project, priorityOptions, projectOptions), @@ -459,11 +456,11 @@ func NewTaskEdit(common *common.Common, description *string, priority *string, p return &t } -func (t taskEdit) Init() tea.Cmd { +func (t *taskEdit) Init() tea.Cmd { return nil } -func (t taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (t *taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.(type) { case nextFieldMsg: if t.cursor == len(t.fields)-1 { @@ -490,7 +487,7 @@ func (t taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return t, nil } -func (t taskEdit) View() string { +func (t *taskEdit) View() string { views := make([]string, len(t.fields)) for i, field := range t.fields { views[i] = field.View() @@ -540,11 +537,11 @@ func NewTagEdit(common *common.Common, selected *[]string, options []string) *ta return &t } -func (t tagEdit) Init() tea.Cmd { +func (t *tagEdit) Init() tea.Cmd { return nil } -func (t tagEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (t *tagEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.(type) { case nextFieldMsg: if t.cursor == len(t.fields)-1 { @@ -625,11 +622,11 @@ func NewTimeEdit(common *common.Common, due *string, scheduled *string, wait *st return &t } -func (t timeEdit) Init() tea.Cmd { +func (t *timeEdit) Init() tea.Cmd { return nil } -func (t timeEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (t *timeEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.(type) { case nextFieldMsg: if t.cursor == len(t.fields)-1 { @@ -655,7 +652,7 @@ func (t timeEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return t, nil } -func (t timeEdit) View() string { +func (t *timeEdit) View() string { views := make([]string, len(t.fields)) for i, field := range t.fields { views[i] = field.View() @@ -796,12 +793,15 @@ func (p *TaskEditorPage) updateTasksCmd() tea.Msg { p.task.Priority = "" } - if *p.areas[areaTask].(taskEdit).newProjectName != "" { - p.task.Project = *p.areas[areaTask].(taskEdit).newProjectName + if *(p.areas[areaTask].(*taskEdit).newProjectName) != "" { + p.task.Project = *p.areas[areaTask].(*taskEdit).newProjectName } - if *p.areas[areaTags].(tagEdit).newTagsValue != "" { - p.task.Tags = append(p.task.Tags, strings.Split(*p.areas[areaTags].(tagEdit).newTagsValue, " ")...) + if *(p.areas[areaTags].(*tagEdit).newTagsValue) != "" { + newTags := strings.Split(*p.areas[areaTags].(*tagEdit).newTagsValue, " ") + if len(newTags) > 0 { + p.task.Tags = append(p.task.Tags, newTags...) + } } // if p.additionalProject != "" { diff --git a/taskwarrior/models.go b/taskwarrior/models.go index 3636a9c..4cded98 100644 --- a/taskwarrior/models.go +++ b/taskwarrior/models.go @@ -23,28 +23,29 @@ func (a Annotation) String() string { } type Task struct { - Id int64 `json:"id,omitempty"` - Uuid string `json:"uuid,omitempty"` - Description string `json:"description,omitempty"` - Project string `json:"project"` - Priority string `json:"priority"` - Status string `json:"status,omitempty"` - Tags []string `json:"tags"` - VirtualTags []string `json:"-"` - Depends []string `json:"depends,omitempty"` - DependsIds string `json:"-"` - 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"` + Id int64 `json:"id,omitempty"` + Uuid string `json:"uuid,omitempty"` + Description string `json:"description,omitempty"` + Project string `json:"project"` + Priority string `json:"priority"` + Status string `json:"status,omitempty"` + Tags []string `json:"tags"` + VirtualTags []string `json:"-"` + Depends []string `json:"depends,omitempty"` + DependsIds string `json:"-"` + 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"` + Udas map[string]any `json:"-"` } func (t *Task) GetString(fieldWFormat string) string { diff --git a/taskwarrior/taskwarrior.go b/taskwarrior/taskwarrior.go index 4af37e9..196f0f7 100644 --- a/taskwarrior/taskwarrior.go +++ b/taskwarrior/taskwarrior.go @@ -89,6 +89,8 @@ type TaskWarrior interface { GetReport(report string) *Report GetReports() Reports + GetUdas() []string + GetTasks(report *Report, filter ...string) Tasks AddTask(task *Task) error ImportTask(task *Task) @@ -169,7 +171,15 @@ func (ts *TaskSquire) GetTasks(report *Report, filter ...string) Tasks { return nil } - for _, task := range tasks { + unstructuredTasks := make([]map[string]any, 0) + err = json.Unmarshal(output, &unstructuredTasks) + if err != nil { + slog.Error("Failed unmarshalling tasks:", err) + return nil + } + + for i, task := range tasks { + task.Udas = unstructuredTasks[i] if task.Depends != nil && len(task.Depends) > 0 { ids := make([]string, len(task.Depends)) for i, dependUuid := range task.Depends { @@ -281,10 +291,18 @@ func (ts *TaskSquire) GetTags() []string { } tags := make([]string, 0) + tagSet := make(map[string]struct{}) for _, tag := range strings.Split(string(output), "\n") { if _, ok := virtualTags[tag]; !ok && tag != "" { tags = append(tags, tag) + tagSet[tag] = struct{}{} + } + } + + for _, tag := range strings.Split(ts.config.Get("uda.tasksquire.tags.default"), ",") { + if _, ok := tagSet[tag]; !ok { + tags = append(tags, tag) } } @@ -307,6 +325,27 @@ func (ts *TaskSquire) GetReports() Reports { return ts.reports } +func (ts *TaskSquire) GetUdas() []string { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + cmd := exec.Command(twBinary, append(ts.defaultArgs, "_udas")...) + output, err := cmd.CombinedOutput() + if err != nil { + slog.Error("Failed getting config:", err) + return nil + } + + udas := make([]string, 0) + for _, uda := range strings.Split(string(output), "\n") { + if uda != "" { + udas = append(udas, uda) + } + } + + return udas +} + func (ts *TaskSquire) SetContext(context *Context) error { ts.mutex.Lock() defer ts.mutex.Unlock() diff --git a/test/taskchampion.sqlite3 b/test/taskchampion.sqlite3 index df7e68a..dee8e3b 100644 Binary files a/test/taskchampion.sqlite3 and b/test/taskchampion.sqlite3 differ diff --git a/test/taskrc b/test/taskrc index 66474c5..1d62a28 100644 --- a/test/taskrc +++ b/test/taskrc @@ -4,3 +4,14 @@ context.test.read=+test context.test.write=+test context.home.read=+home context.home.write=+home + +uda.testuda.type=string +uda.testuda.label=testuda +uda.testuda.values=eins,zwei,drei + +report.next.columns=id,testuda,start.age,entry.age,depends,priority,project,tags,recur,scheduled.countdown,due.relative,until.remaining,description,urgency +report.next.context=1 +report.next.description=Most urgent tasks +report.next.filter=status:pending -WAITING +report.next.labels=ID,UDA,Active,Age,Deps,P,Project,Tag,Recur,S,Due,Until,Description,Urg +report.next.sort=urgency-