1use std::{
2 borrow::Cow,
3 path::{Path, PathBuf},
4 str::FromStr,
5};
6
7use crate::{exec::ExecutionConfig, felt::Felt, input::InputFile, linker::LinkLibrary};
8
9#[derive(Default, Debug)]
11#[cfg_attr(any(feature = "tui", feature = "repl"), derive(clap::Parser))]
12#[cfg_attr(any(feature = "tui", feature = "repl"), command(author, version, about = "The interactive Miden debugger", long_about = None))]
13pub struct DebuggerConfig {
14 #[cfg_attr(any(feature = "tui", feature = "repl"), arg(value_name = "FILE"))]
20 pub input: Option<InputFile>,
21 #[cfg_attr(any(feature = "tui", feature = "repl"), arg(long, value_name = "FILE"))]
27 pub inputs: Option<ExecutionConfig>,
28 #[cfg_attr(
38 any(feature = "tui", feature = "repl"),
39 arg(last(true), value_name = "ARGV")
40 )]
41 pub args: Vec<Felt>,
42 #[cfg_attr(
46 feature = "tui",
47 arg(long, value_name = "DIR", help_heading = "Execution")
48 )]
49 pub working_dir: Option<PathBuf>,
50 #[cfg_attr(
54 feature = "tui",
55 arg(
56 long,
57 value_name = "DIR",
58 env = "MIDEN_SYSROOT",
59 help_heading = "Linker"
60 )
61 )]
62 pub sysroot: Option<PathBuf>,
63 #[cfg_attr(any(feature = "tui", feature = "repl"), arg(
65 long,
66 value_enum,
67 default_value_t = ColorChoice::Auto,
68 default_missing_value = "auto",
69 num_args(0..=1),
70 help_heading = "Output"
71 ))]
72 pub color: ColorChoice,
73 #[cfg_attr(
76 any(feature = "tui", feature = "repl"),
77 arg(long, help_heading = "Execution")
78 )]
79 pub entrypoint: Option<String>,
80 #[cfg(feature = "dap")]
85 #[cfg_attr(
86 feature = "tui",
87 arg(long, value_name = "ADDR", help_heading = "Execution")
88 )]
89 pub dap_connect: Option<String>,
90 #[cfg_attr(
92 feature = "tui",
93 arg(
94 long = "search-path",
95 short = 'L',
96 value_name = "PATH",
97 help_heading = "Linker"
98 )
99 )]
100 pub search_path: Vec<PathBuf>,
101 #[cfg_attr(
112 feature = "tui",
113 arg(
114 long = "link-library",
115 short = 'l',
116 value_name = "[KIND=]NAME",
117 value_delimiter = ',',
118 next_line_help(true),
119 help_heading = "Linker"
120 )
121 )]
122 pub link_libraries: Vec<LinkLibrary>,
123 #[cfg_attr(
125 any(feature = "tui", feature = "repl"),
126 arg(long, help_heading = "Output")
127 )]
128 pub repl: bool,
129}
130
131#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
140#[cfg_attr(any(feature = "tui", feature = "repl"), derive(clap::ValueEnum))]
141pub enum ColorChoice {
142 Always,
145 AlwaysAnsi,
148 #[default]
152 Auto,
153 Never,
155}
156
157#[derive(Debug, thiserror::Error)]
158#[error("invalid color choice: {0}")]
159pub struct ColorChoiceParseError(std::borrow::Cow<'static, str>);
160
161impl FromStr for ColorChoice {
162 type Err = ColorChoiceParseError;
163
164 fn from_str(s: &str) -> Result<Self, Self::Err> {
165 match s.to_lowercase().as_str() {
166 "always" => Ok(ColorChoice::Always),
167 "always-ansi" => Ok(ColorChoice::AlwaysAnsi),
168 "never" => Ok(ColorChoice::Never),
169 "auto" => Ok(ColorChoice::Auto),
170 unknown => Err(ColorChoiceParseError(unknown.to_string().into())),
171 }
172 }
173}
174
175impl ColorChoice {
176 pub fn should_attempt_color(&self) -> bool {
178 match *self {
179 ColorChoice::Always => true,
180 ColorChoice::AlwaysAnsi => true,
181 ColorChoice::Never => false,
182 #[cfg(feature = "std")]
183 ColorChoice::Auto => self.env_allows_color(),
184 #[cfg(not(feature = "std"))]
185 ColorChoice::Auto => false,
186 }
187 }
188
189 #[cfg(not(windows))]
190 pub fn env_allows_color(&self) -> bool {
191 match std::env::var_os("TERM") {
192 None => return false,
195 Some(k) => {
196 if k == "dumb" {
197 return false;
198 }
199 }
200 }
201 if std::env::var_os("NO_COLOR").is_some() {
204 return false;
205 }
206 true
207 }
208
209 #[cfg(windows)]
210 pub fn env_allows_color(&self) -> bool {
211 if let Some(k) = std::env::var_os("TERM") {
215 if k == "dumb" {
216 return false;
217 }
218 }
219 if std::env::var_os("NO_COLOR").is_some() {
222 return false;
223 }
224 true
225 }
226
227 #[cfg(all(feature = "tui", windows))]
232 pub fn should_ansi(&self) -> bool {
233 match *self {
234 ColorChoice::Always => false,
235 ColorChoice::AlwaysAnsi => true,
236 ColorChoice::Never => false,
237 ColorChoice::Auto => {
238 match std::env::var("TERM") {
239 Err(_) => false,
240 Ok(k) => k != "dumb" && k != "cygwin",
244 }
245 }
246 }
247 }
248
249 #[cfg(not(feature = "tui"))]
254 pub fn should_ansi(&self) -> bool {
255 match *self {
256 ColorChoice::Always => false,
257 ColorChoice::AlwaysAnsi => true,
258 ColorChoice::Never => false,
259 ColorChoice::Auto => false,
260 }
261 }
262}
263
264impl DebuggerConfig {
265 pub fn working_dir(&self) -> Cow<'_, Path> {
266 match self.working_dir.as_deref() {
267 Some(path) => Cow::Borrowed(path),
268 None => std::env::current_dir()
269 .map(Cow::Owned)
270 .unwrap_or(Cow::Borrowed(Path::new("./"))),
271 }
272 }
273
274 pub fn toolchain_dir(&self) -> Option<PathBuf> {
275 let sysroot = if let Some(sysroot) = self.sysroot.as_deref() {
276 Cow::Borrowed(sysroot)
277 } else if let Some((midenup_home, midenup_channel)) =
278 midenup_home().and_then(|home| midenup_channel().map(|channel| (home, channel)))
279 {
280 Cow::Owned(midenup_home.join("toolchains").join(midenup_channel))
281 } else {
282 return None;
283 };
284
285 if sysroot.try_exists().ok().is_some_and(|exists| exists) {
286 Some(sysroot.into_owned())
287 } else {
288 None
289 }
290 }
291}
292
293fn midenup_home() -> Option<PathBuf> {
294 use std::process::Command;
295
296 let mut cmd = Command::new("midenup");
297 let mut output = cmd.args(["show", "home"]).output().ok()?;
298 if !output.status.success() {
299 return None;
300 }
301 let output = String::from_utf8(core::mem::take(&mut output.stdout)).ok()?;
302 let trimmed = output.trim_ascii();
303 if trimmed.is_empty() {
304 return None;
305 }
306 PathBuf::from_str(trimmed).ok()
307}
308
309fn midenup_channel() -> Option<String> {
310 use std::process::Command;
311
312 let mut cmd = Command::new("midenup");
313 let mut output = cmd.args(["show", "active-toolchain"]).output().ok()?;
314 if !output.status.success() {
315 return None;
316 }
317 let output = String::from_utf8(core::mem::take(&mut output.stdout)).ok()?;
318 let trimmed = output.trim_ascii();
319 if trimmed.is_empty() {
320 return None;
321 }
322 if output.len() == trimmed.len() {
323 Some(output)
324 } else {
325 Some(trimmed.to_string())
326 }
327}