1pub mod append;
2pub mod config_cmd;
3pub mod daemon;
4pub mod delete;
5pub mod edit;
6pub mod export;
7pub mod git;
8pub mod list;
9pub mod meta_cmd;
10pub mod notify;
11pub mod open;
12pub mod search;
13pub mod stats;
14pub mod tags;
15pub mod update;
16
17use crate::elements::{Element, ElementKind};
18use crate::ref_resolver::RefResolver;
19use colored::Colorize;
20use indexmap::IndexMap;
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, .. } => data
49 .duration_str()
50 .map(|d| format!("({}) ", d.yellow()))
51 .unwrap_or_default(),
52 Element::Reminder { data, .. } => data
53 .at
54 .as_deref()
55 .map(|t| format!("({}) ", t.magenta()))
56 .unwrap_or_default(),
57 Element::Character { data, .. } => data
58 .name
59 .as_deref()
60 .map(|n| format!("({}) ", n.bold()))
61 .unwrap_or_default(),
62 _ => String::new(),
63 }
64}
65
66pub fn tags_str(tags: &[String]) -> String {
67 if tags.is_empty() {
68 String::new()
69 } else {
70 format!(" {}", format!("[{}]", tags.join(", ")).white())
71 }
72}
73
74pub struct DisplayOpts {
76 pub type_filter: Option<String>,
77 pub tag_filter: Option<String>,
78 pub status_filter: Option<String>,
79 pub name_filter: Option<String>,
80}
81
82pub fn visible(el: &Element, opts: &DisplayOpts) -> bool {
83 if el.is_unknown() {
84 return false;
85 }
86 if let Some(ref tf) = opts.type_filter {
87 if el.sign() != tf {
88 return false;
89 }
90 }
91 if let Some(ref tag) = opts.tag_filter {
92 if !el.tags().iter().any(|t| t == tag) {
93 return false;
94 }
95 }
96 if let Some(ref sf) = opts.status_filter {
97 match el {
99 Element::Task { data, .. } => {
100 if data.status_str() != sf {
101 return false;
102 }
103 }
104 _ => return false,
105 }
106 }
107 if let Some(ref nf) = opts.name_filter {
108 match el {
110 Element::Character { data, .. } => {
111 if data.name.as_deref().map(|n| n.to_lowercase()) != Some(nf.to_lowercase()) {
112 return false;
113 }
114 }
115 _ => return false,
116 }
117 }
118 true
119}
120
121pub fn print_element(el: &Element, depth: usize, ref_str: Option<&str>) {
123 let indent = " ".repeat(depth + 1);
124 let badge = type_badge(&el.kind());
125 let extra = element_extra(el);
126 let body_line = el.body_str().trim().lines().next().unwrap_or("").trim();
127 let tags = tags_str(el.tags());
128
129 if let Some(r) = ref_str {
130 print!("{}{} ", indent, format!("{:<12}", r).white());
131 } else {
132 print!("{}", indent);
133 }
134 println!("{} {}{}{}", badge, extra, body_line, tags);
135}
136
137pub fn print_tree(
145 elements: &IndexMap<String, Element>,
146 opts: &DisplayOpts,
147 resolver: Option<&RefResolver>,
148 show_refs: bool,
149) -> usize {
150 if elements.is_empty() {
151 return 0;
152 }
153
154 let mut sorted: Vec<(&String, &Element)> = elements.iter().collect();
155 sorted.sort_by(|(a, _), (b, _)| {
156 let a_parts: Vec<u64> = a.split('.').filter_map(|s| s.parse().ok()).collect();
157 let b_parts: Vec<u64> = b.split('.').filter_map(|s| s.parse().ok()).collect();
158 a_parts.cmp(&b_parts)
159 });
160
161 let root_depth = sorted
162 .first()
163 .map(|(k, _)| k.split('.').count())
164 .unwrap_or(1);
165 let mut shown = 0usize;
166
167 for (ref_key, el) in &sorted {
168 let depth = ref_key.split('.').count();
169 if depth <= root_depth {
170 continue;
171 }
172 let render_depth = depth - root_depth - 1;
173
174 let human_ref: String = resolver
176 .and_then(|r| r.to_human(ref_key))
177 .map(|s| s.to_string())
178 .unwrap_or_else(|| ref_key.to_string());
179
180 if el.is_mps_group() {
181 let prefix = format!("{}.", ref_key);
182 let any_visible = sorted
183 .iter()
184 .any(|(k, v)| k.starts_with(&prefix) && !v.is_mps_group() && visible(v, opts));
185 if !any_visible {
186 continue;
187 }
188
189 let indent = " ".repeat(render_depth + 1);
190 if show_refs {
191 print!("{}{} ", indent, format!("{:<12}", &human_ref).white());
192 } else {
193 print!("{}", indent);
194 }
195 println!("{}", "[@mps]".white());
196 } else {
197 if !visible(el, opts) {
198 continue;
199 }
200 let ref_str = if show_refs {
201 Some(human_ref.as_str())
202 } else {
203 None
204 };
205 print_element(el, render_depth, ref_str);
206 shown += 1;
207 }
208 }
209
210 shown
211}