1use anyhow::{anyhow, Result};
2use clap::Args;
3use gn_core::NotesEngine;
4
5#[derive(Args)]
6pub struct ShowArgs {
7 pub id: Option<String>,
9
10 #[arg(short, long)]
12 pub thread: bool,
13
14 #[arg(long)]
16 pub json: bool,
17}
18
19pub fn run(args: &ShowArgs) -> Result<()> {
20 let engine = NotesEngine::new(".");
21 let namespaces = vec!["comments", "review", "todos"];
22
23 let mut all_notes = Vec::new();
24
25 for ns in &namespaces {
26 let namespace_enum = gn_core::Namespace::Custom(ns.to_string());
27 if let Ok(notes) = engine.read_notes(&namespace_enum) {
28 all_notes.extend(notes);
29 }
30 }
31
32 if all_notes.is_empty() {
33 println!("No notes found in repository.");
34 return Ok(());
35 }
36
37 let found_note = match &args.id {
39 None => {
40 if args.json {
41 all_notes.last().cloned()
42 } else {
43 match super::picker::pick_note("Select note to view:", &all_notes)? {
44 Some(n) => Some(n.clone()),
45 None => {
46 println!("Cancelled.");
47 return Ok(());
48 }
49 }
50 }
51 }
52 Some(raw_id) => {
53 let target = raw_id.trim().trim_start_matches('#');
54 if target.eq_ignore_ascii_case("latest") || target == "^" {
55 all_notes.last().cloned()
56 } else if let Ok(idx) = target.parse::<usize>() {
57 if idx >= 1 && idx <= all_notes.len() {
58 Some(all_notes[idx - 1].clone())
59 } else {
60 None
61 }
62 } else {
63 all_notes
64 .iter()
65 .find(|n| n.id.to_string().starts_with(target))
66 .cloned()
67 }
68 }
69 };
70
71 let note = found_note.ok_or_else(|| anyhow!("Note not found"))?;
72
73 if args.json {
74 if args.thread {
75 let mut thread = vec![note.clone()];
76 for n in &all_notes {
77 if n.thread_id == Some(note.id) {
78 thread.push(n.clone());
79 }
80 }
81 println!("{}", serde_json::to_string_pretty(&thread)?);
82 } else {
83 println!("{}", serde_json::to_string_pretty(¬e)?);
84 }
85 } else {
86 println!("\x1b[1;36mNote {}\x1b[0m", note.id);
87 println!("{}", "─".repeat(60));
88 println!("Commit: {}", note.commit);
89 if let Some(f) = ¬e.file {
90 let l = note.line_start.unwrap_or(0);
91 println!("File: {}:{}", f, l);
92 }
93 println!("Author: {}", note.author);
94 println!("Date: {}", note.timestamp);
95 println!("Namespace: {:?}", note.namespace);
96 println!("Status: {:?}", note.status);
97 println!("\nMessage:\n{}", note.body);
98
99 if args.thread {
100 let replies: Vec<&gn_core::note::Note> = all_notes
101 .iter()
102 .filter(|n| n.thread_id == Some(note.id))
103 .collect();
104
105 if !replies.is_empty() {
106 println!("\n\x1b[1mThread Replies ({}):\x1b[0m", replies.len());
107 println!("{}", "─".repeat(60));
108 for r in replies {
109 println!("\x1b[36m↳ [{}]\x1b[0m {}:", &r.id.to_string()[..8], r.author);
110 println!(" {}\n", r.body.trim());
111 }
112 }
113 }
114 }
115
116 Ok(())
117}