diff --git a/ChangeLog b/ChangeLog index 07ca4bb1a..94fe52489 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,12 @@ 2.6.0 () - +- TW #2554 Waiting is now an entirely "virtual" concept, based on a task's + 'wait' property and the current time. This is reflected in the +WAITING + tag, and in the now-deprecated `waiting` status. Please upgrade filters + and other automation to use `+WAITING` or `wait.after:now` instead of + `status:waiting`, as support will be dropped in a future version. + TaskWarrior no longer explicitly "unwaits" a task, so the "unwait' verbosity + token is no longer available. - TW #1654 "Due" parsing behaviour seems inconsistent Thanks to Max Rossmannek. - TW #1788 When deleting recurring task all tasks, including completed tasks, diff --git a/doc/man/taskrc.5.in b/doc/man/taskrc.5.in index 3c22e9519..f628f3b60 100644 --- a/doc/man/taskrc.5.in +++ b/doc/man/taskrc.5.in @@ -299,11 +299,10 @@ control specific occasions when output is generated. This list may contain: sync Feedback about sync filter Shows the filter used in the command context Show the current context. Displayed in footnote. - unwait Notification when a task leaves the 'waiting' state override Notification when configuration options are overridden recur Notification when a new recurring task instance is created -"affected", "new-id", "new-uuid", "project", "unwait", "override" and "recur" +"affected", "new-id", "new-uuid", "project", "override" and "recur" imply "footnote". Note that the "1" setting is equivalent to all the tokens being specified, @@ -312,7 +311,7 @@ and the "nothing" setting is equivalent to none of the tokens being specified. Here are the shortcut equivalents: verbose=on - verbose=blank,header,footnote,label,new-id,affected,edit,special,project,sync,filter,unwait,override,recur + verbose=blank,header,footnote,label,new-id,affected,edit,special,project,sync,filter,override,recur verbose=0 verbose=blank,label,new-id,edit diff --git a/src/Context.cpp b/src/Context.cpp index fc7cfe130..085c2a87c 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -87,7 +87,7 @@ std::string configurationDefaults = "\n" "# Miscellaneous\n" "# # Comma-separated list. May contain any subset of:\n" - "verbose=blank,header,footnote,label,new-id,affected,edit,special,project,sync,filter,unwait,override,recur\n" + "verbose=blank,header,footnote,label,new-id,affected,edit,special,project,sync,filter,override,recur\n" "confirmation=1 # Confirmation on delete, big changes\n" "recurrence=1 # Enable recurrence\n" "recurrence.confirmation=prompt # Confirmation for propagating changes among recurring tasks (yes/no/prompt)\n" @@ -1029,7 +1029,6 @@ bool Context::verbose (const std::string& token) v != "project" && // v != "sync" && // v != "filter" && // - v != "unwait" && // v != "override" && // v != "context" && // v != "recur") // @@ -1043,7 +1042,7 @@ bool Context::verbose (const std::string& token) if (! verbosity.count ("footnote")) { // TODO: Some of these may not use footnotes yet. They should. - for (auto flag : {"affected", "new-id", "new-uuid", "project", "unwait", "override", "recur"}) + for (auto flag : {"affected", "new-id", "new-uuid", "project", "override", "recur"}) { if (verbosity.count (flag)) { diff --git a/src/TDB2.cpp b/src/TDB2.cpp index 21234ebc1..9b1cf7443 100644 --- a/src/TDB2.cpp +++ b/src/TDB2.cpp @@ -355,23 +355,6 @@ void TF2::load_gc (Task& task) { Context::getContext ().tdb2.pending._tasks.push_back (task); } - else if (status == "waiting") - { - Datetime wait (task.get_date ("wait")); - if (wait < now) - { - task.set ("status", "pending"); - task.remove ("wait"); - // Unwaiting pending tasks is the only case not caught by the size() - // checks in TDB2::gc(), so we need to signal it here. - Context::getContext ().tdb2.pending._dirty = true; - - if (Context::getContext ().verbose ("unwait")) - Context::getContext ().footnote (format ("Un-waiting task {1} '{2}'", task.id, task.get ("description"))); - } - - Context::getContext ().tdb2.pending._tasks.push_back (task); - } else { Context::getContext ().tdb2.completed._tasks.push_back (task); @@ -1249,7 +1232,6 @@ void TDB2::show_diff ( // Possible scenarios: // - task in pending that needs to be in completed // - task in completed that needs to be in pending -// - waiting task in pending that needs to be un-waited void TDB2::gc () { Timer timer; diff --git a/src/Task.cpp b/src/Task.cpp index e7c6c377a..83e60def3 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -147,7 +147,9 @@ Task::status Task::textToStatus (const std::string& input) else if (input[0] == 'c') return Task::completed; else if (input[0] == 'd') return Task::deleted; else if (input[0] == 'r') return Task::recurring; - else if (input[0] == 'w') return Task::waiting; + // for compatibility, parse `w` as pending; Task::getStatus will + // apply the virtual waiting status if appropriate + else if (input[0] == 'w') return Task::pending; throw format ("The status '{1}' is not valid.", input); } @@ -301,12 +303,25 @@ Task::status Task::getStatus () const if (! has ("status")) return Task::pending; - return textToStatus (get ("status")); + auto status = textToStatus (get ("status")); + + // implement the "virtual" Task::waiting status, which is not stored on-disk + // but is defined as a pending task with a `wait` attribute in the future. + if (status == Task::pending && is_waiting ()) { + return Task::waiting; + } + + return status; } //////////////////////////////////////////////////////////////////////////////// void Task::setStatus (Task::status status) { + // the 'waiting' status is a virtual version of 'pending', so translate + // that back to 'pending' here + if (status == Task::waiting) + status = Task::pending; + set ("status", statusToText (status)); recalc_urgency = true; @@ -559,6 +574,23 @@ bool Task::is_overdue () const } #endif +//////////////////////////////////////////////////////////////////////////////// +bool Task::is_waiting () const +{ + // note that is_waiting can return true for tasks in an actual status other + // than pending; in this case +WAITING will be set but the status will not be + // "waiting" + if (has ("wait")) + { + Datetime now; + Datetime wait (get_date ("wait")); + if (wait > now) + return true; + } + + return false; +} + //////////////////////////////////////////////////////////////////////////////// // Attempt an FF4 parse first, using Task::parse, and in the event of an error // try a JSON parse, otherwise a legacy parse (currently no legacy formats are @@ -1311,7 +1343,7 @@ bool Task::hasTag (const std::string& tag) const if (tag == "TAGGED") return getTagCount() > 0; if (tag == "PARENT") return has ("mask") || has ("last"); // 2017-01-07: Deprecated in 2.6.0 if (tag == "TEMPLATE") return has ("last") || has ("mask"); - if (tag == "WAITING") return get ("status") == "waiting"; + if (tag == "WAITING") return is_waiting (); if (tag == "PENDING") return get ("status") == "pending"; if (tag == "COMPLETED") return get ("status") == "completed"; if (tag == "DELETED") return get ("status") == "deleted"; @@ -2048,7 +2080,7 @@ float Task::urgency_scheduled () const //////////////////////////////////////////////////////////////////////////////// float Task::urgency_waiting () const { - if (get_ref ("status") == "waiting") + if (is_waiting ()) return 1.0; return 0.0; diff --git a/src/Task.h b/src/Task.h index 5112dc3f1..1936e2a50 100644 --- a/src/Task.h +++ b/src/Task.h @@ -114,6 +114,7 @@ public: bool is_udaPresent () const; bool is_orphanPresent () const; #endif + bool is_waiting () const; status getStatus () const; void setStatus (status); diff --git a/test/wait.t b/test/wait.t index 489a8318f..748dac78c 100755 --- a/test/wait.t +++ b/test/wait.t @@ -62,8 +62,6 @@ class TestWait(TestCase): self.assertIn("visible", out) self.assertIn("hidden", out) - self.assertIn("Un-waiting task 2 'hidden'", err) - class TestBug434(TestCase): # Bug #434: Task should not prevent users from marking as done tasks with @@ -106,7 +104,7 @@ class TestFeature2322(TestCase): self.t = Task() def test_done_unwait(self): - """2322: Done should un-wait a waiting task""" + """2322: Done should remove the wait attribute""" self.t("add foo wait:tomorrow") code, out, err = self.t("export") self.assertIn('"wait":', out) @@ -116,7 +114,7 @@ class TestFeature2322(TestCase): self.assertNotIn('"wait":', out) def test_delete_unwait(self): - """2322: Deleteion should un-wait a waiting task""" + """2322: Delete should remove the wait attribute""" self.t("add bar wait:tomorrow") code, out, err = self.t("export") self.assertIn('"wait":', out)