A file-based task manager
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

tsk show: render footnote section resolving [[tsk-N]] links

The markup parser already produces ParsedLink entries with the right
classification (Internal / Namespaced / Foreign / External) and tags
each occurrence with a superscript footnote number in the inline
output. command_show now renders the matching footnote section after
the body:

- Internal(id) → look up in the active namespace; emit
`tsk-N: <title>` if bound, `tsk-N: <not bound in '<ns>'>` otherwise.
- Namespaced{ns, id} → echo `<ns>/tsk-N` (cross-namespace title
resolution + stable-id rewriting on share is the deferred half of
this task — left for when we actually do cross-namespace renders).
- Foreign{prefix, id} → `<prefix>-<id> (foreign)`.
- External(url) → the URL.

super_num() is now `pub(crate)` so command_show can produce matching
markers for the footnote section.

Smoke-tested with a body containing one resolvable link, one missing
id, and one namespaced link — all three rendered with the expected
suffix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+24 -4
+23 -3
src/lib.rs
··· 598 598 } 599 599 600 600 fn command_show(dir: PathBuf, task_id: TaskId, show_attrs: bool, raw: bool) -> Result<()> { 601 - let task = Workspace::from_path(dir)?.task(task_id.into())?; 601 + let ws = Workspace::from_path(dir)?; 602 + let task = ws.task(task_id.into())?; 602 603 if show_attrs && !task.attributes.is_empty() { 603 604 println!("---"); 604 605 for (k, vs) in &task.attributes { ··· 611 612 let plain = task.to_string(); 612 613 match (raw, task::parse(&plain)) { 613 614 (false, Some(parsed)) => { 614 - // Re-attach the title — the parser is fed the body-side text and 615 - // produces a styled body; the title is rendered as-is on top. 616 615 print!("{}", parsed.content); 616 + // Footnote section: resolve each [[...]] link against the active 617 + // namespace (or just echo for foreign / external links). 618 + if !parsed.links.is_empty() { 619 + println!(); 620 + for (i, link) in parsed.links.iter().enumerate() { 621 + println!("\n{} {}", task::super_num(i + 1), render_link(&ws, link)); 622 + } 623 + } 617 624 } 618 625 _ => print!("{plain}"), 619 626 } 620 627 println!(); 621 628 Ok(()) 629 + } 630 + 631 + fn render_link(ws: &Workspace, link: &task::ParsedLink) -> String { 632 + use task::ParsedLink::*; 633 + match link { 634 + Internal(id) => match ws.task((*id).into()) { 635 + Ok(t) => format!("{id}: {}", t.title), 636 + Err(_) => format!("{id}: <not bound in '{}'>", ws.namespace()), 637 + }, 638 + Namespaced { namespace, id } => format!("{namespace}/{id}"), 639 + Foreign { prefix, id } => format!("{prefix}-{id} (foreign)"), 640 + External(url) => url.to_string(), 641 + } 622 642 } 623 643 624 644 fn command_edit(dir: PathBuf, task_id: TaskId) -> Result<()> {
+1 -1
src/task.rs
··· 272 272 } 273 273 274 274 /// Converts a unsigned integer into a superscripted string 275 - fn super_num(num: usize) -> String { 275 + pub(crate) fn super_num(num: usize) -> String { 276 276 let num_str = num.to_string(); 277 277 let mut out = String::with_capacity(num_str.len()); 278 278 for char in num_str.chars() {