1pub mod open;
2pub mod list;
3pub mod append;
4pub mod edit;
5pub mod delete;
6pub mod search;
7pub mod stats;
8pub mod tags;
9pub mod export;
10pub mod git;
11pub mod update;
12pub mod config_cmd;
13pub mod notify;
14pub mod daemon;
15pub mod meta_cmd;
16
17use colored::Colorize;
18use indexmap::IndexMap;
19use crate::elements::{Element, ElementKind};
20use crate::ref_resolver::RefResolver;
21
22pub fn type_badge(kind: &ElementKind) -> String {
25 match kind {
26 ElementKind::Task => "[task]".green().bold().to_string(),
27 ElementKind::Note => "[note]".cyan().bold().to_string(),
28 ElementKind::Log => "[log]".yellow().bold().to_string(),
29 ElementKind::Reminder => "[reminder]".magenta().bold().to_string(),
30 ElementKind::MpsGroup => "[@mps]".white().to_string(),
31 ElementKind::Character => "[character]".blue().bold().to_string(),
32 ElementKind::Unknown => "[unknown]".white().to_string(),
33 }
34}
35
36pub fn element_extra(el: &Element) -> String {
38 match el {
39 Element::Task { data, .. } => {
40 let s = data.status_str();
41 let colored = if data.is_done() {
42 s.green().to_string()
43 } else {
44 s.yellow().to_string()
45 };
46 format!("({}) ", colored)
47 }
48 Element::Log { data, .. } => {
49 data.duration_str()
50 .map(|d| format!("({}) ", d.yellow()))
51 .unwrap_or_default()
52 }
53 Element::Reminder { data, .. } => {
54 data.at.as_deref()
55 .map(|t| format!("({}) ", t.magenta()))
56 .unwrap_or_default()
57 }
58 Element::Character { data, .. } => {
59 data.name.as_deref()
60 .map(|n| format!("({}) ", n.bold()))
61 .unwrap_or_default()
62 }
63 _ => String::new(),
64 }
65}
66
67pub fn tags_str(tags: &[String]) -> String {
68 if tags.is_empty() {
69 String::new()
70 } else {
71 format!(" {}", format!("[{}]", tags.join(", ")).white())
72 }
73}
74
75pub struct DisplayOpts {
77 pub type_filter: Option<String>,
78 pub tag_filter: Option<String>,
79 pub status_filter: Option<String>,
80 pub name_filter: Option<String>,
81}
82
83pub fn visible(el: &Element, opts: &DisplayOpts) -> bool {
84 if el.is_unknown() { return false; }
85 if let Some(ref tf) = opts.type_filter {
86 if el.sign() != tf { return false; }
87 }
88 if let Some(ref tag) = opts.tag_filter {
89 if !el.tags().iter().any(|t| t == tag) { return false; }
90 }
91 if let Some(ref sf) = opts.status_filter {
92 match el {
94 Element::Task { data, .. } => {
95 if data.status_str() != sf { return false; }
96 }
97 _ => return false,
98 }
99 }
100 if let Some(ref nf) = opts.name_filter {
101 match el {
103 Element::Character { data, .. } => {
104 if data.name.as_deref().map(|n| n.to_lowercase()) != Some(nf.to_lowercase()) {
105 return false;
106 }
107 }
108 _ => return false,
109 }
110 }
111 true
112}
113
114pub fn print_element(el: &Element, depth: usize, ref_str: Option<&str>) {
116 let indent = " ".repeat(depth + 1);
117 let badge = type_badge(&el.kind());
118 let extra = element_extra(el);
119 let body_line = el.body_str().trim().lines().next().unwrap_or("").trim();
120 let tags = tags_str(el.tags());
121
122 if let Some(r) = ref_str {
123 print!("{}{} ", indent, format!("{:<12}", r).white());
124 } else {
125 print!("{}", indent);
126 }
127 println!("{} {}{}{}", badge, extra, body_line, tags);
128}
129
130pub fn print_tree(
138 elements: &IndexMap<String, Element>,
139 opts: &DisplayOpts,
140 resolver: Option<&RefResolver>,
141 show_refs: bool,
142) -> usize {
143 if elements.is_empty() { return 0; }
144
145 let mut sorted: Vec<(&String, &Element)> = elements.iter().collect();
146 sorted.sort_by(|(a, _), (b, _)| {
147 let a_parts: Vec<u64> = a.split('.').filter_map(|s| s.parse().ok()).collect();
148 let b_parts: Vec<u64> = b.split('.').filter_map(|s| s.parse().ok()).collect();
149 a_parts.cmp(&b_parts)
150 });
151
152 let root_depth = sorted.first().map(|(k, _)| k.split('.').count()).unwrap_or(1);
153 let mut shown = 0usize;
154
155 for (ref_key, el) in &sorted {
156 let depth = ref_key.split('.').count();
157 if depth <= root_depth { continue; }
158 let render_depth = depth - root_depth - 1;
159
160 let human_ref: String = resolver
162 .and_then(|r| r.to_human(ref_key))
163 .map(|s| s.to_string())
164 .unwrap_or_else(|| ref_key.to_string());
165
166 if el.is_mps_group() {
167 let prefix = format!("{}.", ref_key);
168 let any_visible = sorted.iter().any(|(k, v)| {
169 k.starts_with(&prefix) && !v.is_mps_group() && visible(v, opts)
170 });
171 if !any_visible { continue; }
172
173 let indent = " ".repeat(render_depth + 1);
174 if show_refs {
175 print!("{}{} ", indent, format!("{:<12}", &human_ref).white());
176 } else {
177 print!("{}", indent);
178 }
179 println!("{}", "[@mps]".white());
180 } else {
181 if !visible(el, opts) { continue; }
182 let ref_str = if show_refs { Some(human_ref.as_str()) } else { None };
183 print_element(el, render_depth, ref_str);
184 shown += 1;
185 }
186 }
187
188 shown
189}