Skip to main content

fur_cli/commands/
jump.rs

1use std::fs;
2use std::path::Path;
3use clap::Args;
4use serde_json::Value;
5
6/// JumpArgs allow specifying relative or absolute jumps
7#[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 = fs::read_to_string(convo_path).expect("❌ Couldn't read conversation file");
27    let conversation: Value = serde_json::from_str(&conversation_data).unwrap();
28
29    let current_id = index["current_message"].as_str().unwrap_or_default();
30
31    let messages = conversation["messages"].as_array().unwrap();
32
33    // Locate current message
34    let current_msg_id = current_id;
35    let current_msg = messages
36        .iter()
37        .find_map(|id| {
38            if id.as_str()? == current_msg_id {
39                let msg_path = Path::new(".fur/messages").join(format!("{}.json", current_msg_id));
40                let msg_data = fs::read_to_string(msg_path).ok()?;
41                serde_json::from_str::<Value>(&msg_data).ok()
42            } else {
43                None
44            }
45        });
46
47    if current_msg.is_none() {
48        eprintln!("❌ Current message not found in conversation.");
49        return Ok(());
50    }
51    let current = current_msg.unwrap();
52
53    // Handle jump --past
54    if let Some(n) = args.past {
55        let mut current_id = current["id"].as_str().unwrap_or_default().to_string();
56        let mut jumped = 0;
57
58        while jumped < n {
59            // println!("🔍 Loop iteration {} — current_id: {}", jumped, current_id);
60
61            let msg_path = Path::new(".fur/messages").join(format!("{}.json", current_id));
62            let msg_data = fs::read_to_string(&msg_path);
63            if msg_data.is_err() {
64                eprintln!("❌ Failed to load message: {}", current_id);
65                return Ok(());
66            }
67
68            let msg_json: Value = serde_json::from_str(&msg_data.unwrap()).unwrap();
69
70            // let parent_raw = &msg_json["parent"];
71            // println!("   ↪️ parent field raw: {}", parent_raw);
72
73            match msg_json["parent"].as_str() {
74                Some(pid) if !pid.is_empty() => {
75                    let in_conversation = conversation["messages"]
76                        .as_array()
77                        .unwrap_or(&vec![])
78                        .iter()
79                        .any(|val| val.as_str() == Some(pid));
80
81                    if !in_conversation {
82                        println!("\x1b[91m📜 You've reached the origin of this conversation. No earlier messages exist.\x1b[0m");
83                        println!("\x1b[93m🌱 To start a new conversation, run:\n    fur new \"Title of your new conversation\"\x1b[0m");
84                        return Ok(());
85                    }
86
87                    // println!("   🧬 Jumping to parent_id: {}", pid);
88                    current_id = pid.to_string();
89                    jumped += 1;
90                }
91                _ => {
92                    println!("\x1b[91m📜 You've reached the origin of this conversation. No earlier messages exist.\x1b[0m");
93                    println!("\x1b[93m🌱 To start a new conversation, run:\n    fur new \"Title of your new conversation\"\x1b[0m");
94                    return Ok(());
95                }
96            }
97        }
98
99        index["current_message"] = Value::String(current_id.clone());
100        fs::write(index_path, serde_json::to_string_pretty(&index).unwrap()).unwrap();
101        println!(
102            "⏪ Jumped back {} message{} to {}",
103            jumped,
104            if jumped == 1 { "" } else { "s" },
105            current_id
106        );
107        return Ok(());
108    }
109
110
111    // Handle jump --child
112    if let Some(n) = args.child {
113        let current_id = current["id"].as_str().unwrap_or_default();
114
115        let children: Vec<String> = messages.iter().filter_map(|msg_id| {
116            let msg_path = Path::new(".fur/messages").join(format!("{}.json", msg_id.as_str()?));
117            let msg_data = fs::read_to_string(msg_path).ok()?;
118            let msg_json: Value = serde_json::from_str(&msg_data).ok()?;
119            if msg_json["parent"].as_str()? == current_id {
120                Some(msg_json["id"].as_str()?.to_string())
121            } else {
122                None
123            }
124        }).collect();
125
126        if let Some(child_id) = children.get(n) {
127            index["current_message"] = Value::String(child_id.to_string());
128            fs::write(index_path, serde_json::to_string_pretty(&index).unwrap()).unwrap();
129            println!("⏩ Jumped to child [{}]: {}", n, child_id);
130            return Ok(());
131        } else {
132            eprintln!("❌ No such child at index {}", n);
133            return Ok(());
134        }
135
136    }
137
138    // Handle jump --id
139    if let Some(ref target_id) = args.id {
140        if messages.iter().any(|m| m["id"].as_str() == Some(&target_id)) {
141            index["current_message"] = Value::String(target_id.to_string());
142            fs::write(index_path, serde_json::to_string_pretty(&index).unwrap()).unwrap();
143            println!("🎯 Jumped directly to message ID {}", target_id);
144            return Ok(());
145        } else {
146            eprintln!("❌ Message ID not found: {}", target_id);
147            return Ok(());
148        }
149    }
150
151    eprintln!("❗ No jump argument provided. Use --past, --child, or --id.");
152    Ok(())
153
154
155}