1use anyhow::Result;
2use clap::Args;
3use gn_core::NotesEngine;
4
5#[derive(Args)]
6pub struct ListArgs {
7 #[arg(short, long)]
9 pub file: Option<String>,
10
11 #[arg(short, long)]
13 pub commit: Option<String>,
14
15 #[arg(short, long)]
17 pub status: Option<String>,
18
19 #[arg(short, long)]
21 pub author: Option<String>,
22
23 #[arg(long)]
25 pub json: bool,
26
27 #[arg(short, long)]
29 pub namespace: Option<String>,
30}
31
32pub fn run(args: &ListArgs) -> Result<()> {
33 let engine = NotesEngine::new(".");
34 let namespaces = match &args.namespace {
35 Some(ns) => vec![ns.clone()],
36 None => vec![
37 "comments".to_string(),
38 "review".to_string(),
39 "todos".to_string(),
40 ],
41 };
42
43 let mut all_notes = Vec::new();
44
45 for ns in namespaces {
46 let namespace_enum = gn_core::Namespace::Custom(ns.clone());
47 if let Ok(mut notes) = engine.read_notes(&namespace_enum) {
48 all_notes.append(&mut notes);
49 }
50 }
51
52 if let Some(file) = &args.file {
53 all_notes.retain(|n| n.file.as_deref() == Some(file));
54 }
55 if let Some(commit) = &args.commit {
56 all_notes.retain(|n| n.commit == *commit);
57 }
58 if let Some(status) = &args.status {
59 all_notes.retain(|n| format!("{:?}", n.status).to_lowercase() == status.to_lowercase());
60 }
61 if let Some(author) = &args.author {
62 all_notes.retain(|n| n.author.contains(author));
63 }
64
65 if args.json {
66 let json = serde_json::to_string_pretty(&all_notes)?;
67 println!("{}", json);
68 } else {
69 if all_notes.is_empty() {
70 println!("No notes found.");
71 return Ok(());
72 }
73
74 println!(
75 "{:<5} {:<10} {:<22} {:<18} {:<10} {}",
76 "#", "ID", "FILE:LINE", "AUTHOR", "STATUS", "BODY"
77 );
78 println!("{}", "─".repeat(88));
79
80 for (idx, note) in all_notes.iter().enumerate() {
81 let num = format!("[{}]", idx + 1);
82 let id_short = note.id.to_string().chars().take(8).collect::<String>();
83 let file = note.file.clone().unwrap_or_else(|| "".to_string());
84 let line = note.line_start.unwrap_or(0);
85 let file_line = if file.is_empty() {
86 "-".to_string()
87 } else {
88 format!("{}:{}", file, line)
89 };
90 let body_short = if note.body.len() > 50 {
91 format!("{}...", ¬e.body[..47].replace('\n', " "))
92 } else {
93 note.body.replace('\n', " ")
94 };
95 let author_short = note.author.split('<').next().unwrap_or(¬e.author).trim();
96 let status_str = match note.status {
97 gn_core::note::NoteStatus::Open => "\x1b[33mOpen\x1b[0m",
98 gn_core::note::NoteStatus::Approved => "\x1b[32mApproved\x1b[0m",
99 gn_core::note::NoteStatus::Rejected => "\x1b[31mRejected\x1b[0m",
100 gn_core::note::NoteStatus::Resolved => "\x1b[32mResolved\x1b[0m",
101 };
102
103 println!(
104 "\x1b[1;36m{:<5}\x1b[0m {:<10} {:<22} {:<18} {:<19} {}",
105 num, id_short, file_line, author_short, status_str, body_short
106 );
107 }
108 println!("\n\x1b[90mTip: Reply or resolve using numbers: gn r 1 -m \"...\" or gn ok 1\x1b[0m");
109 }
110
111 Ok(())
112}