1use clap::Args;
2use serde_json::Value;
3use std::fs;
4use std::path::Path;
5
6#[derive(Args, Debug)]
8pub struct JumpArgs {
9 #[arg(short, long)]
10 pub past: Option<usize>,
11
12 #[arg(short, long)]
13 pub child: Option<usize>,
14
15 #[arg(short, long)]
16 pub id: Option<String>,
17}
18
19pub fn run_jump(args: JumpArgs) -> Result<(), Box<dyn std::error::Error>> {
20 let index_path = Path::new(".fur/index.json");
21 let index_data = fs::read_to_string(index_path).expect("❌ Couldn't read index.json");
22 let mut index: Value = serde_json::from_str(&index_data).unwrap();
23
24 let conversation_id = index["active_thread"].as_str().unwrap();
25 let convo_path = Path::new(".fur/threads").join(format!("{}.json", conversation_id));
26 let conversation_data =
27 fs::read_to_string(convo_path).expect("❌ Couldn't read conversation file");
28 let conversation: Value = serde_json::from_str(&conversation_data).unwrap();
29
30 let current_id = index["current_message"].as_str().unwrap_or_default();
31
32 let messages = conversation["messages"].as_array().unwrap();
33
34 let current_msg_id = current_id;
36 let current_msg = messages.iter().find_map(|id| {
37 if id.as_str()? == current_msg_id {
38 let msg_path = Path::new(".fur/messages").join(format!("{}.json", current_msg_id));
39 let msg_data = fs::read_to_string(msg_path).ok()?;
40 serde_json::from_str::<Value>(&msg_data).ok()
41 } else {
42 None
43 }
44 });
45
46 if current_msg.is_none() {
47 eprintln!("❌ Current message not found in conversation.");
48 return Ok(());
49 }
50 let current = current_msg.unwrap();
51
52 if let Some(n) = args.past {
54 let mut current_id = current["id"].as_str().unwrap_or_default().to_string();
55 let mut jumped = 0;
56
57 while jumped < n {
58 let msg_path = Path::new(".fur/messages").join(format!("{}.json", current_id));
61 let msg_data = fs::read_to_string(&msg_path);
62 if msg_data.is_err() {
63 eprintln!("❌ Failed to load message: {}", current_id);
64 return Ok(());
65 }
66
67 let msg_json: Value = serde_json::from_str(&msg_data.unwrap()).unwrap();
68
69 match msg_json["parent"].as_str() {
73 Some(pid) if !pid.is_empty() => {
74 let in_conversation = conversation["messages"]
75 .as_array()
76 .unwrap_or(&vec![])
77 .iter()
78 .any(|val| val.as_str() == Some(pid));
79
80 if !in_conversation {
81 println!("\x1b[91m📜 You've reached the origin of this conversation. No earlier messages exist.\x1b[0m");
82 println!("\x1b[93m🌱 To start a new conversation, run:\n fur new \"Title of your new conversation\"\x1b[0m");
83 return Ok(());
84 }
85
86 current_id = pid.to_string();
88 jumped += 1;
89 }
90 _ => {
91 println!("\x1b[91m📜 You've reached the origin of this conversation. No earlier messages exist.\x1b[0m");
92 println!("\x1b[93m🌱 To start a new conversation, run:\n fur new \"Title of your new conversation\"\x1b[0m");
93 return Ok(());
94 }
95 }
96 }
97
98 index["current_message"] = Value::String(current_id.clone());
99 fs::write(index_path, serde_json::to_string_pretty(&index).unwrap()).unwrap();
100 println!(
101 "⏪ Jumped back {} message{} to {}",
102 jumped,
103 if jumped == 1 { "" } else { "s" },
104 current_id
105 );
106 return Ok(());
107 }
108
109 if let Some(n) = args.child {
111 let current_id = current["id"].as_str().unwrap_or_default();
112
113 let children: Vec<String> = messages
114 .iter()
115 .filter_map(|msg_id| {
116 let msg_path =
117 Path::new(".fur/messages").join(format!("{}.json", msg_id.as_str()?));
118 let msg_data = fs::read_to_string(msg_path).ok()?;
119 let msg_json: Value = serde_json::from_str(&msg_data).ok()?;
120 if msg_json["parent"].as_str()? == current_id {
121 Some(msg_json["id"].as_str()?.to_string())
122 } else {
123 None
124 }
125 })
126 .collect();
127
128 if let Some(child_id) = children.get(n) {
129 index["current_message"] = Value::String(child_id.to_string());
130 fs::write(index_path, serde_json::to_string_pretty(&index).unwrap()).unwrap();
131 println!("⏩ Jumped to child [{}]: {}", n, child_id);
132 return Ok(());
133 } else {
134 eprintln!("❌ No such child at index {}", n);
135 return Ok(());
136 }
137 }
138
139 if let Some(ref target_id) = args.id {
141 if messages
142 .iter()
143 .any(|m| m["id"].as_str() == Some(&target_id))
144 {
145 index["current_message"] = Value::String(target_id.to_string());
146 fs::write(index_path, serde_json::to_string_pretty(&index).unwrap()).unwrap();
147 println!("🎯 Jumped directly to message ID {}", target_id);
148 return Ok(());
149 } else {
150 eprintln!("❌ Message ID not found: {}", target_id);
151 return Ok(());
152 }
153 }
154
155 eprintln!("❗ No jump argument provided. Use --past, --child, or --id.");
156 Ok(())
157}