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
14pub 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 let _ = self.editor.save_history(&self.history_path);
30 }
31}
32
33impl Cli {
34 pub fn new() -> Result<Self> {
35 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 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 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 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 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 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}