forc_debug/cli/
mod.rs

1mod commands;
2mod state;
3
4pub use commands::parse_int;
5
6use crate::{
7    debugger::Debugger,
8    error::{ArgumentError, Error, Result},
9};
10use rustyline::{CompletionType, Config, EditMode, Editor};
11use state::DebuggerHelper;
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    let mut debugger = Debugger::new(api_url).await?;
18    cli.run(&mut debugger, None).await
19}
20
21pub struct Cli {
22    editor: Editor<DebuggerHelper, rustyline::history::FileHistory>,
23    history_path: PathBuf,
24}
25
26impl Drop for Cli {
27    fn drop(&mut self) {
28        // Save the terminal history
29        let _ = self.editor.save_history(&self.history_path);
30    }
31}
32
33impl Cli {
34    pub fn new() -> Result<Self> {
35        // Initialize editor with config
36        let config = Config::builder()
37            .auto_add_history(true)
38            .history_ignore_space(true)
39            .completion_type(CompletionType::Circular)
40            .edit_mode(EditMode::Vi)
41            .max_history_size(100)?
42            .build();
43
44        let mut editor = Editor::with_config(config)?;
45        let helper = DebuggerHelper::new();
46        editor.set_helper(Some(helper));
47
48        // Load history from .forc/.debug/history
49        let history_path = get_history_file_path()?;
50        let _ = editor.load_history(&history_path);
51
52        Ok(Self {
53            editor,
54            history_path,
55        })
56    }
57
58    /// Main CLI entry point with optional initial input
59    pub async fn run(
60        &mut self,
61        debugger: &mut Debugger,
62        initial_input: Option<String>,
63    ) -> Result<()> {
64        println!("Welcome to the Sway Debugger! Type \"help\" for a list of commands.");
65
66        let mut prefill_next = initial_input;
67
68        // Main REPL loop
69        loop {
70            let readline = if let Some(prefill) = prefill_next.take() {
71                self.editor.readline_with_initial(">> ", (&prefill, ""))
72            } else {
73                self.editor.readline(">> ")
74            };
75
76            match readline {
77                Ok(line) => {
78                    let args: Vec<String> = line.split_whitespace().map(String::from).collect();
79                    if args.is_empty() {
80                        continue;
81                    }
82
83                    if let Some(helper) = self.editor.helper() {
84                        match args[0].as_str() {
85                            cmd if helper.commands.is_help_command(cmd) => {
86                                if let Err(e) = commands::cmd_help(helper, &args).await {
87                                    println!("Error: {e}");
88                                }
89                            }
90                            cmd if helper.commands.is_quit_command(cmd) => {
91                                break Ok(());
92                            }
93                            _ => {
94                                // Execute the command using debugger
95                                if let Err(e) = debugger
96                                    .execute_from_args(args.clone(), &mut std::io::stdout())
97                                    .await
98                                {
99                                    if let Error::ArgumentError(ArgumentError::UnknownCommand(
100                                        cmd,
101                                    )) = &e
102                                    {
103                                        // Check if this is an unknown command error and provide suggestions
104                                        if let Some(suggestion) = helper.commands.find_closest(cmd)
105                                        {
106                                            println!(
107                                                "Unknown command: '{}'. Did you mean '{}'?",
108                                                cmd, suggestion.name
109                                            );
110                                        } else {
111                                            println!("Error: {e}");
112                                        }
113                                    } else {
114                                        println!("Error: {e}");
115                                    }
116                                }
117                            }
118                        }
119                    }
120                }
121                Err(rustyline::error::ReadlineError::Interrupted) => {
122                    println!("CTRL-C");
123                    break Ok(());
124                }
125                Err(rustyline::error::ReadlineError::Eof) => {
126                    println!("CTRL-D");
127                    break Ok(());
128                }
129                Err(err) => {
130                    println!("Error: {err}");
131                    break Ok(());
132                }
133            }
134        }
135    }
136}
137
138fn get_history_file_path() -> Result<PathBuf> {
139    let home = dirs::home_dir().ok_or_else(|| {
140        Error::IoError(std::io::Error::new(
141            std::io::ErrorKind::NotFound,
142            "Could not find home directory",
143        ))
144    })?;
145    let debug_dir = home.join(".forc").join(".debug");
146    std::fs::create_dir_all(&debug_dir).map_err(Error::IoError)?;
147    Ok(debug_dir.join("history"))
148}