summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Bütow <1224732+snonux@users.noreply.github.com>2025-06-20 20:43:03 +0300
committerPaul Bütow <1224732+snonux@users.noreply.github.com>2025-06-20 20:43:03 +0300
commit363caa6301d67c97b29e83e168ed9c83f571355c (patch)
tree1a4e7f87b15049e63924fa7a27d89f152c0a74b1
parentd596388ebe915d164a9df22dae57be5f9fc2e465 (diff)
Add task sorting
-rw-r--r--internal/task/sort_test.go28
-rw-r--r--internal/task/task.go67
-rw-r--r--internal/ui/table.go7
3 files changed, 101 insertions, 1 deletions
diff --git a/internal/task/sort_test.go b/internal/task/sort_test.go
new file mode 100644
index 0000000..4b034c9
--- /dev/null
+++ b/internal/task/sort_test.go
@@ -0,0 +1,28 @@
+package task
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestSortTasks(t *testing.T) {
+ tasks := []Task{
+ {ID: 2, Due: "20240102T000000Z", Priority: "M", Tags: []string{"b", "a"}},
+ {ID: 1, Due: "20240101T000000Z", Priority: "H", Tags: []string{"a"}},
+ {ID: 3, Due: "", Priority: "", Tags: []string{"c"}},
+ {ID: 4, Due: "20240101T000000Z", Priority: "L", Tags: []string{"a"}},
+ {ID: 5, Due: "20240101T000000Z", Priority: "H", Tags: []string{"b"}},
+ {ID: 6, Due: "20240101T000000Z", Priority: "H", Tags: []string{"b"}},
+ }
+
+ SortTasks(tasks)
+
+ var ids []int
+ for _, tsk := range tasks {
+ ids = append(ids, tsk.ID)
+ }
+ want := []int{1, 5, 6, 4, 2, 3}
+ if !reflect.DeepEqual(ids, want) {
+ t.Fatalf("unexpected order: %v", ids)
+ }
+}
diff --git a/internal/task/task.go b/internal/task/task.go
index fb07333..d10c500 100644
--- a/internal/task/task.go
+++ b/internal/task/task.go
@@ -8,8 +8,10 @@ import (
"io"
"os"
"os/exec"
+ "sort"
"strconv"
"strings"
+ "time"
)
// Task represents a taskwarrior task as returned by `task export`.
@@ -222,3 +224,68 @@ func EditCmd(id int) *exec.Cmd {
func Edit(id int) error {
return EditCmd(id).Run()
}
+
+// SortTasks orders tasks by due date, priority, tag names and id.
+// Tasks without a due date are placed after tasks with a due date.
+func SortTasks(tasks []Task) {
+ joinTags := func(tags []string) string {
+ if len(tags) == 0 {
+ return ""
+ }
+ cpy := append([]string(nil), tags...)
+ sort.Strings(cpy)
+ return strings.Join(cpy, ",")
+ }
+
+ priVal := func(p string) int {
+ switch p {
+ case "H":
+ return 3
+ case "M":
+ return 2
+ case "L":
+ return 1
+ default:
+ return 0
+ }
+ }
+
+ parseDue := func(s string) (time.Time, bool) {
+ if s == "" {
+ return time.Time{}, false
+ }
+ t, err := time.Parse("20060102T150405Z", s)
+ if err != nil {
+ return time.Time{}, false
+ }
+ return t, true
+ }
+
+ sort.Slice(tasks, func(i, j int) bool {
+ ti, tj := tasks[i], tasks[j]
+
+ di, iok := parseDue(ti.Due)
+ dj, jok := parseDue(tj.Due)
+ if iok && !jok {
+ return true
+ }
+ if !iok && jok {
+ return false
+ }
+ if iok && jok && !di.Equal(dj) {
+ return di.Before(dj)
+ }
+
+ pi, pj := priVal(ti.Priority), priVal(tj.Priority)
+ if pi != pj {
+ return pi > pj
+ }
+
+ tgI, tgJ := joinTags(ti.Tags), joinTags(tj.Tags)
+ if tgI != tgJ {
+ return tgI < tgJ
+ }
+
+ return ti.ID < tj.ID
+ })
+}
diff --git a/internal/ui/table.go b/internal/ui/table.go
index 755e077..a789ee4 100644
--- a/internal/ui/table.go
+++ b/internal/ui/table.go
@@ -89,13 +89,18 @@ func (m *Model) reload() error {
return err
}
- var rows []atable.Row
var filtered []task.Task
for _, tsk := range tasks {
if tsk.Status == "completed" {
continue
}
filtered = append(filtered, tsk)
+ }
+
+ task.SortTasks(filtered)
+
+ var rows []atable.Row
+ for _, tsk := range filtered {
rows = append(rows, taskToRow(tsk))
}