fix(tasks): never create empty task

This commit is contained in:
xeruf 2024-12-05 23:00:45 +01:00
parent 0dba23bcc6
commit 6fc8b42bcc
1 changed files with 80 additions and 51 deletions

View File

@ -928,7 +928,7 @@ impl TasksRelay {
warn!("New task name needs at least {CHARACTER_THRESHOLD} characters"); warn!("New task name needs at least {CHARACTER_THRESHOLD} characters");
return None; return None;
} }
Some(self.make_task_with(arg, self.position_tags_for(position), true)) self.make_task_with(arg, self.position_tags_for(position), true)
} }
1 => { 1 => {
// One match, activate // One match, activate
@ -1042,19 +1042,6 @@ impl TasksRelay {
self.tags.iter().map(Tag::from) self.tags.iter().map(Tag::from)
} }
/// Creates a task following the current state
///
/// Sanitizes input
pub(crate) fn make_task(&mut self, input: &str) -> EventId {
self.make_task_with(input, self.position_tags(), true)
}
pub(crate) fn make_task_and_enter(&mut self, input: &str, state: State) {
let id = self.make_task_with(input, self.position_tags(), false);
self.set_state_for(id, "", state);
self.move_to(Some(id));
}
/// Moves up and creates a sibling task dependent on the current one /// Moves up and creates a sibling task dependent on the current one
/// ///
/// Returns true if successful, false if there is no current task /// Returns true if successful, false if there is no current task
@ -1074,7 +1061,9 @@ impl TasksRelay {
false false
} }
/// Creates a task including current tag filters /// Creates a task including current tag filters if content is not empty.
///
/// @param set_state: whether to set context state - use false if setting state manually
/// ///
/// Sanitizes input /// Sanitizes input
pub(crate) fn make_task_with( pub(crate) fn make_task_with(
@ -1082,33 +1071,65 @@ impl TasksRelay {
input: &str, input: &str,
tags: impl IntoIterator<Item=Tag>, tags: impl IntoIterator<Item=Tag>,
set_state: bool, set_state: bool,
) -> EventId { ) -> Option<EventId> {
let (input, input_tags) = extract_tags(input.trim(), &self.users); let (input, mut input_tags) = extract_tags(input.trim(), &self.users);
let prio = if input.is_empty() {
if input_tags.iter().any(|t| t.kind().to_string() == PRIO) { warn!("Task content must not be empty!");
None return None;
} else { }
self.priority.map(|p| to_prio_tag(p))
};
info!( info!(
"Created task \"{input}\" with tags [{}]", "Created task \"{input}\" with tags [{}]",
join_tags(&input_tags) join_tags(&input_tags)
); );
let id = self.submit( input_tags.extend(tags);
EventBuilder::new(TASK_KIND, &input) let id = self.make_task_unchecked(&input, input_tags);
.tags(input_tags)
.tags(self.context_hashtags())
.tags(tags)
.tags(prio),
);
if set_state { if set_state {
self.state self.state
.as_option() .as_option()
.inspect(|s| self.set_state_for_with(id, s)); .inspect(|s| self.set_state_for_with(id, s));
} }
id Some(id)
} }
fn make_task_unchecked(
&mut self,
input: &str,
tags: Vec<Tag>,
) -> EventId {
let prio =
if tags.iter().any(|t| t.kind().to_string() == PRIO) {
None
} else {
self.priority.map(|p| to_prio_tag(p))
};
self.submit(
EventBuilder::new(TASK_KIND, input)
.tags(self.context_hashtags())
.tags(tags)
.tags(prio),
)
}
/// Primarily for tests
fn make_task_unwrapped(&mut self, input: &str) -> EventId {
self.make_task(input).unwrap()
}
/// Creates a task following the current state if input is sufficient.
///
/// Sanitizes input
pub(crate) fn make_task(&mut self, input: &str) -> Option<EventId> {
self.make_task_with(input, self.position_tags(), true)
}
pub(crate) fn make_task_and_enter(&mut self, input: &str, state: State) {
if let Some(id) = self.make_task_with(input, self.position_tags(), false) {
self.set_state_for(id, "", state);
self.move_to(Some(id));
}
}
pub(crate) fn get_task_title(&self, id: &EventId) -> String { pub(crate) fn get_task_title(&self, id: &EventId) -> String {
self.get_by_id(id).map_or(id.to_string(), |t| t.get_title()) self.get_by_id(id).map_or(id.to_string(), |t| t.get_title())
} }
@ -1845,9 +1866,9 @@ mod tasks_test {
let mut tasks = stub_tasks(); let mut tasks = stub_tasks();
tasks.custom_time = Some(Timestamp::zero()); tasks.custom_time = Some(Timestamp::zero());
let parent = tasks.make_task("parent #tag1"); let parent = tasks.make_task_unwrapped("parent #tag1");
tasks.move_to(Some(parent)); tasks.move_to(Some(parent));
let sub = tasks.make_task("sub #oi # tag2"); let sub = tasks.make_task_unwrapped("sub #oi # tag2");
assert_eq!( assert_eq!(
tasks.all_hashtags(), tasks.all_hashtags(),
["oi", "tag1", "tag2"].into_iter().map(Hashtag::from).collect() ["oi", "tag1", "tag2"].into_iter().map(Hashtag::from).collect()
@ -1882,7 +1903,7 @@ mod tasks_test {
tasks.set_priority(Some(HIGH_PRIO)); tasks.set_priority(Some(HIGH_PRIO));
assert_eq!(tasks.get_prompt_suffix(), " #dp *85"); assert_eq!(tasks.get_prompt_suffix(), " #dp *85");
let id_hp = tasks.make_task("high prio tagged # tag"); let id_hp = tasks.make_task_unwrapped("high prio tagged # tag");
let hp = tasks.get_by_id(&id_hp).unwrap(); let hp = tasks.get_by_id(&id_hp).unwrap();
assert_eq!(hp.priority(), Some(HIGH_PRIO)); assert_eq!(hp.priority(), Some(HIGH_PRIO));
assert_eq!( assert_eq!(
@ -1905,11 +1926,11 @@ mod tasks_test {
let anid = task2.event.id; let anid = task2.event.id;
tasks.custom_time = Some(Timestamp::now() + 1); tasks.custom_time = Some(Timestamp::now() + 1);
let s1 = tasks.make_task("sub1"); let s1 = tasks.make_task_unwrapped("sub1");
tasks.custom_time = Some(Timestamp::now() + 2); tasks.custom_time = Some(Timestamp::now() + 2);
tasks.set_priority(Some(QUICK_PRIO + 1)); tasks.set_priority(Some(QUICK_PRIO + 1));
let s2 = tasks.make_task("sub2"); let s2 = tasks.make_task_unwrapped("sub2");
let s3 = tasks.make_task("sub3"); let s3 = tasks.make_task_unwrapped("sub3");
tasks.set_priority(Some(QUICK_PRIO)); tasks.set_priority(Some(QUICK_PRIO));
assert_tasks_visible!(tasks, [s1, s2, s3]); assert_tasks_visible!(tasks, [s1, s2, s3]);
@ -1922,7 +1943,7 @@ mod tasks_test {
assert_tasks_visible!(tasks, [s1, s2, s3, id_hp]); assert_tasks_visible!(tasks, [s1, s2, s3, id_hp]);
tasks.set_priority(None); tasks.set_priority(None);
let s4 = tasks.make_task_with("sub4", [tasks.make_event_tag_from_id(anid, MARKER_PARENT)], true); let s4 = tasks.make_task_with("sub4", [tasks.make_event_tag_from_id(anid, MARKER_PARENT)], true).unwrap();
assert_eq!(tasks.get_parent(Some(&s4)), Some(&anid)); assert_eq!(tasks.get_parent(Some(&s4)), Some(&anid));
assert_tasks_view!(tasks, [anid, id_hp]); assert_tasks_view!(tasks, [anid, id_hp]);
// s2-4 are newest while s2,s3,hp are highest prio // s2-4 are newest while s2,s3,hp are highest prio
@ -1934,7 +1955,7 @@ mod tasks_test {
#[test] #[test]
fn test_sibling_dependency() { fn test_sibling_dependency() {
let mut tasks = stub_tasks(); let mut tasks = stub_tasks();
let parent = tasks.make_task("parent"); let parent = tasks.make_task_unwrapped("parent");
let sub = tasks.submit( let sub = tasks.submit(
EventBuilder::new(TASK_KIND, "sub") EventBuilder::new(TASK_KIND, "sub")
.tags([tasks.make_event_tag_from_id(parent, MARKER_PARENT)]), .tags([tasks.make_event_tag_from_id(parent, MARKER_PARENT)]),
@ -1953,11 +1974,11 @@ mod tasks_test {
fn test_bookmarks() { fn test_bookmarks() {
let mut tasks = stub_tasks(); let mut tasks = stub_tasks();
let zero = EventId::all_zeros(); let zero = EventId::all_zeros();
let test = tasks.make_task("test # tag"); let test = tasks.make_task_unwrapped("test # tag");
let parent = tasks.make_task("parent"); let parent = tasks.make_task_unwrapped("parent");
assert_eq!(tasks.viewed_tasks().len(), 2); assert_eq!(tasks.viewed_tasks().len(), 2);
tasks.move_to(Some(parent)); tasks.move_to(Some(parent));
let pin = tasks.make_task("pin"); let pin = tasks.make_task_unwrapped("pin");
tasks.search_depth = 1; tasks.search_depth = 1;
assert_eq!(tasks.filtered_tasks(None, true).len(), 2); assert_eq!(tasks.filtered_tasks(None, true).len(), 2);
@ -1998,7 +2019,7 @@ mod tasks_test {
assert_tasks_visible!(tasks, [pin, test]); assert_tasks_visible!(tasks, [pin, test]);
tasks.set_view_depth(0); tasks.set_view_depth(0);
tasks.custom_time = Some(now()); tasks.custom_time = Some(now());
let mut new = (0..3).map(|t| tasks.make_task(t.to_string().as_str())).collect_vec(); let mut new = (0..3).map(|t| tasks.make_task_unwrapped(t.to_string().as_str())).collect_vec();
// Show the newest tasks in quick access and remove old pin // Show the newest tasks in quick access and remove old pin
new.extend([test, parent]); new.extend([test, parent]);
assert_tasks_visible!(tasks, new); assert_tasks_visible!(tasks, new);
@ -2014,7 +2035,7 @@ mod tasks_test {
.tags([tasks.make_event_tag(&tasks.get_current_task().unwrap().event, MARKER_DEPENDS)]) .tags([tasks.make_event_tag(&tasks.get_current_task().unwrap().event, MARKER_DEPENDS)])
); );
assert_eq!(tasks.viewed_tasks(), Vec::<&Task>::new()); assert_eq!(tasks.viewed_tasks(), Vec::<&Task>::new());
let sub_id = tasks.make_task("sub"); let sub_id = tasks.make_task_unwrapped("sub");
assert_tasks_view!(tasks, [sub_id]); assert_tasks_view!(tasks, [sub_id]);
assert_eq!(tasks.len(), 3); assert_eq!(tasks.len(), 3);
let sub = tasks.get_by_id(&sub_id).unwrap(); let sub = tasks.get_by_id(&sub_id).unwrap();
@ -2034,7 +2055,7 @@ mod tasks_test {
tasks.move_to(zero); tasks.move_to(zero);
assert_eq!(tasks.viewed_tasks().len(), 1); assert_eq!(tasks.viewed_tasks().len(), 1);
let sub = tasks.make_task("test"); let sub = tasks.make_task_unwrapped("test");
assert_eq!(tasks.len(), 2); assert_eq!(tasks.len(), 2);
assert_eq!(tasks.viewed_tasks().len(), 2); assert_eq!(tasks.viewed_tasks().len(), 2);
assert_eq!(tasks.get_by_id(&sub).unwrap().parent_id(), zero.as_ref()); assert_eq!(tasks.get_by_id(&sub).unwrap().parent_id(), zero.as_ref());
@ -2083,7 +2104,7 @@ mod tasks_test {
assert_eq!(tasks.get_own_events_history().count(), 3); assert_eq!(tasks.get_own_events_history().count(), 3);
assert!(tasks.time_tracked(zero) > 999); assert!(tasks.time_tracked(zero) > 999);
let some = tasks.make_task("some"); let some = tasks.make_task_unwrapped("some");
tasks.track_at(Timestamp::from(22 + 1), Some(some)); tasks.track_at(Timestamp::from(22 + 1), Some(some));
assert_eq!(tasks.get_own_events_history().count(), 4); assert_eq!(tasks.get_own_events_history().count(), 4);
assert_eq!(tasks.time_tracked(zero), 12); assert_eq!(tasks.time_tracked(zero), 12);
@ -2128,17 +2149,17 @@ mod tasks_test {
assert_position!(tasks, t1); assert_position!(tasks, t1);
tasks.search_depth = 2; tasks.search_depth = 2;
assert_eq!(tasks.viewed_tasks().len(), 0); assert_eq!(tasks.viewed_tasks().len(), 0);
let t11 = tasks.make_task("t11 # tag"); let t11 = tasks.make_task_unwrapped("t11 # tag");
assert_eq!(tasks.viewed_tasks().len(), 1); assert_eq!(tasks.viewed_tasks().len(), 1);
assert_eq!(tasks.get_task_path(Some(t11)), "t1>t11"); assert_eq!(tasks.get_task_path(Some(t11)), "t1>t11");
assert_eq!(tasks.get_relative_path(t11), "t11"); assert_eq!(tasks.get_relative_path(t11), "t11");
let t12 = tasks.make_task("t12"); let t12 = tasks.make_task_unwrapped("t12");
assert_eq!(tasks.viewed_tasks().len(), 2); assert_eq!(tasks.viewed_tasks().len(), 2);
tasks.move_to(Some(t11)); tasks.move_to(Some(t11));
assert_position!(tasks, t11); assert_position!(tasks, t11);
assert_eq!(tasks.viewed_tasks().len(), 0); assert_eq!(tasks.viewed_tasks().len(), 0);
let t111 = tasks.make_task("t111"); let t111 = tasks.make_task_unwrapped("t111");
assert_tasks_view!(tasks, [t111]); assert_tasks_view!(tasks, [t111]);
assert_eq!(tasks.get_task_path(Some(t111)), "t1>t11>t111"); assert_eq!(tasks.get_task_path(Some(t111)), "t1>t11>t111");
assert_eq!(tasks.get_relative_path(t111), "t111"); assert_eq!(tasks.get_relative_path(t111), "t111");
@ -2195,13 +2216,21 @@ mod tasks_test {
fn test_empty_task_title_fallback_to_id() { fn test_empty_task_title_fallback_to_id() {
let mut tasks = stub_tasks(); let mut tasks = stub_tasks();
let empty = tasks.make_task(""); let empty = tasks.make_task_unchecked("", vec![]);
let empty_task = tasks.get_by_id(&empty).unwrap(); let empty_task = tasks.get_by_id(&empty).unwrap();
let empty_id = empty_task.event.id.to_string(); let empty_id = empty_task.event.id.to_string();
assert_eq!(empty_task.get_title(), empty_id); assert_eq!(empty_task.get_title(), empty_id);
assert_eq!(tasks.get_task_path(Some(empty)), empty_id); assert_eq!(tasks.get_task_path(Some(empty)), empty_id);
} }
#[test]
fn test_short_task() {
let mut tasks = stub_tasks();
let str = " # one";
assert_eq!(extract_tags(str, &tasks.users), ("".to_string(), vec![to_hashtag_tag("one")]));
assert_eq!(tasks.make_task(str), None);
}
#[test] #[test]
fn test_unknown_task() { fn test_unknown_task() {
let mut tasks = stub_tasks(); let mut tasks = stub_tasks();
@ -2209,7 +2238,7 @@ mod tasks_test {
let zero = EventId::all_zeros(); let zero = EventId::all_zeros();
assert_eq!(tasks.get_task_path(Some(zero)), zero.to_string()); assert_eq!(tasks.get_task_path(Some(zero)), zero.to_string());
tasks.move_to(Some(zero)); tasks.move_to(Some(zero));
let dangling = tasks.make_task("test"); let dangling = tasks.make_task_unwrapped("test");
assert_eq!( assert_eq!(
tasks.get_task_path(Some(dangling)), tasks.get_task_path(Some(dangling)),
"0000000000000000000000000000000000000000000000000000000000000000>test" "0000000000000000000000000000000000000000000000000000000000000000>test"