···11+DO THE THING
22+33+44+remember to do part1
55+66+and part2
77+88+and part3
+3-3
.tsk/index
···11-tsk-30 Add flag to only print IDs in list command 1735007126
11+tsk-30 Add flag to only print IDs in list command 1763257109
22tsk-28 Add tool to clean up old tasks not in index 1735006519
33tsk-10 foreign workspaces 1732594198
44tsk-21 Add command to setup git stuff 1732594198
55-tsk-8 IMAP4-based sync 1732594198
55+tsk-8 IMAP4-based sync 1767469318
66tsk-17 Add reopen command 1732594198
77-tsk-16 Add ability to search archived tasks with find command 1732594198
77+tsk-16 Add ability to search archived tasks with find command 1767466011
88tsk-15 Add link identification to tasks 1732594198
99tsk-9 fix timestamp storage and parsing 1732594198
1010tsk-7 allow for creating tasks that don't go to top of stack 1732594198
···184184A quick overview of the format:
185185186186- \!Bolded\! text is surrounded by exclamation marks (!)
187187-- \*Italicized\* text is surrouneded by single asterists (*)
187187+- \*Italicized\* text is surrounded by single asterisks (*)
188188- \_Underlined\_ text is surrounded by underscores (_)
189189-- \~Strikenthrough\~ text is surrounded by tildes (~)
189189+- \~Strikethrough\~ text is surrounded by tildes (~)
190190+- \=Highlighted\= text is surrounded by equals signs (=)
191191+- \`Inline code\` is surrounded by backticks (`)
190192191193Links like in Markdown, along with the wiki-style links documented above.
194194+Raw links can also be written as \<https://example.com\>.
192195193196Misc
194197----
+1-1
src/attrs.rs
···11+use std::collections::BTreeMap;
12use std::collections::btree_map::Entry;
23use std::collections::btree_map::{IntoIter as BTreeIntoIter, Iter as BTreeMapIter};
33-use std::collections::BTreeMap;
44use std::iter::Chain;
5566type Map = BTreeMap<String, String>;
+14-6
src/fzf.rs
···11use crate::errors::{Error, Result};
22+use std::ffi::OsStr;
23use std::fmt::Display;
34use std::io::Write;
45use std::process::{Command, Stdio};
···6778/// Sends each item as a line to stdin to the `fzf` command and returns the selected item's string
89/// representation as output
99-pub fn select<I>(input: impl IntoIterator<Item = I>) -> Result<Option<I>>
1010+pub fn select<I, O, S>(
1111+ input: impl IntoIterator<Item = I>,
1212+ extra: impl IntoIterator<Item = S>,
1313+) -> Result<Option<O>>
1014where
1111- I: Display + FromStr,
1212- Error: From<<I as FromStr>::Err>,
1515+ O: FromStr,
1616+ I: Display,
1717+ Error: From<<O as FromStr>::Err>,
1818+ S: AsRef<OsStr>,
1319{
1414- let mut child = Command::new("fzf")
1515- .args(["-d", "\t"])
2020+ let mut command = Command::new("fzf");
2121+ let mut child = command
2222+ .args(extra)
2323+ .arg("--read0")
1624 .stderr(Stdio::inherit())
1725 .stdin(Stdio::piped())
1826 .stdout(Stdio::piped())
···2028 // unwrap: this can never fail
2129 let child_in = child.stdin.as_mut().unwrap();
2230 for item in input.into_iter() {
2323- writeln!(child_in, "{item}")?;
3131+ write!(child_in, "{item}\0")?;
2432 }
2533 let output = child.wait_with_output()?;
2634 if output.stdout.is_empty() {
+25-8
src/main.rs
···55mod task;
66mod util;
77mod workspace;
88-use clap_complete::{generate, Shell};
88+use clap_complete::{Shell, generate};
99use errors::Result;
1010use std::io::{self, Write};
1111use std::path::PathBuf;
···220220#[derive(Args)]
221221#[group(required = false, multiple = false)]
222222struct FindArgs {
223223- /// Include the contents of tasks in the search criteria.
223223+ /// Exclude the contents of tasks in the search criteria.
224224 #[arg(short = 'b', default_value_t = false)]
225225- search_body: bool,
225225+ exclude_body: bool,
226226 /* TODO: implement this
227227 /// Include archived tasks in the search criteria. Combine with `-b` to include archived
228228 /// bodies in the search criteria.
···237237 TaskIdentifier::Id(id)
238238 } else if value.find.find {
239239 TaskIdentifier::Find {
240240- search_body: value.find.args.search_body,
240240+ exclude_body: value.find.args.exclude_body,
241241 archived: false,
242242 }
243243 } else {
···291291 relative_id: 0,
292292 find: Find {
293293 find: false,
294294- args: FindArgs { search_body: false },
294294+ args: FindArgs { exclude_body: true },
295295 },
296296 }
297297}
···313313 } else {
314314 "".to_string()
315315 };
316316- let mut body = body.unwrap_or_default();
316316+ // If no body was explicitly provided and the title contains newlines,
317317+ // treat the first line as the title and the rest as the body (like git commit -m)
318318+ let mut body = if body.is_none() {
319319+ if let Some((first_line, rest)) = title.split_once('\n') {
320320+ let extracted_body = rest.to_string();
321321+ title = first_line.to_string();
322322+ extracted_body
323323+ } else {
324324+ String::new()
325325+ }
326326+ } else {
327327+ // Body was explicitly provided, so strip any newlines from title
328328+ title = title.replace(['\n', '\r'], " ");
329329+ body.unwrap_or_default()
330330+ };
317331 if body == "-" {
318332 // add newline so you can type directly in the shell
319333 //eprintln!("");
···327341 body = content.1.to_string();
328342 }
329343 }
344344+ // Ensure title never contains newlines (invariant for index file format)
345345+ title = title.replace(['\n', '\r'], " ");
330346 let task = workspace.new_task(title, body)?;
331347 workspace.handle_metadata(&task, None)?;
332348 Ok(task)
···380396 let pre_links = task::parse(&task.to_string()).map(|pt| pt.intenal_links());
381397 let new_content = open_editor(format!("{}\n\n{}", task.title.trim(), task.body.trim()))?;
382398 if let Some((title, body)) = new_content.split_once("\n") {
383383- task.title = title.to_string();
399399+ // Ensure title never contains newlines (invariant for index file format)
400400+ task.title = title.replace(['\n', '\r'], " ");
384401 task.body = body.to_string();
385402 workspace.handle_metadata(&task, pre_links)?;
386403 task.save()?;
···405422}
406423407424fn command_find(dir: PathBuf, short_id: bool, find_args: FindArgs) -> Result<()> {
408408- let id = Workspace::from_path(dir)?.search(None, find_args.search_body, false)?;
425425+ let id = Workspace::from_path(dir)?.search(None, !find_args.exclude_body, false)?;
409426 if let Some(id) = id {
410427 if short_id {
411428 // print as integer