forc_debug/cli/
mod.rs

1mod commands;
2mod state;
3
4pub use commands::parse_int;
5
6use crate::{
7    error::{Error, Result},
8    FuelClient,
9};
10use rustyline::{CompletionType, Config, EditMode, Editor};
11use state::{DebuggerHelper, State};
12use std::path::PathBuf;
13
14/// Start the CLI debug interface
15pub async fn start_cli(api_url: &str) -> Result<()> {
16    let mut cli = Cli::new()?;
17    cli.run(api_url).await
18}
19
20pub struct Cli {
21    editor: Editor<DebuggerHelper, rustyline::history::FileHistory>,
22    history_path: PathBuf,
23}
24
25impl Drop for Cli {
26    fn drop(&mut self) {
27        // Save the terminal history
28        let _ = self.editor.save_history(&self.history_path);
29    }
30}
31
32impl Cli {
33    pub fn new() -> Result<Self> {
34        // Initialize editor with config
35        let config = Config::builder()
36            .auto_add_history(true)
37            .history_ignore_space(true)
38            .completion_type(CompletionType::Circular)
39            .edit_mode(EditMode::Vi)
40            .max_history_size(100)?
41            .build();
42
43        let mut editor = Editor::with_config(config)?;
44        let helper = DebuggerHelper::new();
45        editor.set_helper(Some(helper));
46
47        // Load history from .forc/.debug/history
48        let history_path = get_history_file_path()?;
49        let _ = editor.load_history(&history_path);
50
51        Ok(Self {
52            editor,
53            history_path,
54        })
55    }
56
57    pub async fn run(&mut self, api_url: &str) -> Result<()> {
58        let client = FuelClient::new(api_url).map_err(|e| Error::FuelClientError(e.to_string()))?;
59        let mut state = State::new(client);
60
61        // Start session
62        state.session_id = state
63            .client
64            .start_session()
65            .await
66            .map_err(|e| Error::FuelClientError(e.to_string()))?;
67
68        println!("Welcome to the Sway Debugger! Type \"help\" for a list of commands.");
69
70        // Main REPL loop
71        loop {
72            let readline = self.editor.readline(">> ");
73            match readline {
74                Ok(line) => {
75                    let args: Vec<String> = line.split_whitespace().map(String::from).collect();
76
77                    if args.is_empty() {
78                        continue;
79                    }
80
81                    if let Some(helper) = self.editor.helper() {
82                        match args[0].as_str() {
83                            cmd if helper.commands.is_help_command(cmd) => {
84                                if let Err(e) = commands::cmd_help(helper, &args).await {
85                                    println!("Error: {}", e);
86                                }
87                            }
88                            cmd if helper.commands.is_tx_command(cmd) => {
89                                if let Err(e) = commands::cmd_start_tx(&mut state, args).await {
90                                    println!("Error: {}", e);
91                                }
92                            }
93                            cmd if helper.commands.is_register_command(cmd) => {
94                                if let Err(e) = commands::cmd_registers(&mut state, args).await {
95                                    println!("Error: {}", e);
96                                }
97                            }
98                            cmd if helper.commands.is_breakpoint_command(cmd) => {
99                                if let Err(e) = commands::cmd_breakpoint(&mut state, args).await {
100                                    println!("Error: {}", e);
101                                }
102                            }
103                            cmd if helper.commands.is_memory_command(cmd) => {
104                                if let Err(e) = commands::cmd_memory(&mut state, args).await {
105                                    println!("Error: {}", e);
106                                }
107                            }
108                            cmd if helper.commands.is_quit_command(cmd) => {
109                                break Ok(());
110                            }
111                            cmd if helper.commands.is_reset_command(cmd) => {
112                                if let Err(e) = commands::cmd_reset(&mut state, args).await {
113                                    println!("Error: {}", e);
114                                }
115                            }
116                            cmd if helper.commands.is_continue_command(cmd) => {
117                                if let Err(e) = commands::cmd_continue(&mut state, args).await {
118                                    println!("Error: {}", e);
119                                }
120                            }
121                            cmd if helper.commands.is_step_command(cmd) => {
122                                if let Err(e) = commands::cmd_step(&mut state, args).await {
123                                    println!("Error: {}", e);
124                                }
125                            }
126                            unknown_cmd => {
127                                if let Some(suggestion) = helper.commands.find_closest(unknown_cmd)
128                                {
129                                    println!(
130                                        "Unknown command: '{}'. Did you mean '{}'?",
131                                        unknown_cmd, suggestion.name
132                                    );
133                                } else {
134                                    println!("Unknown command: '{}'", unknown_cmd);
135                                }
136                            }
137                        }
138                    }
139                }
140                Err(rustyline::error::ReadlineError::Interrupted) => {
141                    println!("CTRL-C");
142                    break Ok(());
143                }
144                Err(rustyline::error::ReadlineError::Eof) => {
145                    println!("CTRL-D");
146                    break Ok(());
147                }
148                Err(err) => {
149                    println!("Error: {}", err);
150                    break Ok(());
151                }
152            }
153        }
154    }
155}
156
157fn get_history_file_path() -> Result<PathBuf> {
158    let home = dirs::home_dir().ok_or_else(|| {
159        Error::IoError(std::io::Error::new(
160            std::io::ErrorKind::NotFound,
161            "Could not find home directory",
162        ))
163    })?;
164    let debug_dir = home.join(".forc").join(".debug");
165    std::fs::create_dir_all(&debug_dir).map_err(Error::IoError)?;
166    Ok(debug_dir.join("history"))
167}