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(
12 any(feature = "tui", feature = "repl", feature = "flamegraph"),
13 derive(clap::Args)
14)]
15pub struct DebuggerConfig {
16 #[cfg_attr(
22 any(feature = "tui", feature = "repl", feature = "flamegraph"),
23 arg(value_name = "FILE")
24 )]
25 pub input: Option<InputFile>,
26 #[cfg_attr(
32 any(feature = "tui", feature = "repl", feature = "flamegraph"),
33 arg(long, value_name = "FILE")
34 )]
35 pub inputs: Option<ExecutionConfig>,
36 #[cfg_attr(
46 any(feature = "tui", feature = "repl", feature = "flamegraph"),
47 arg(last(true), value_name = "ARGV")
48 )]
49 pub args: Vec<Felt>,
50 #[cfg_attr(
54 any(feature = "tui", feature = "flamegraph"),
55 arg(long, value_name = "DIR", help_heading = "Execution")
56 )]
57 pub working_dir: Option<PathBuf>,
58 #[cfg_attr(
62 any(feature = "tui", feature = "flamegraph"),
63 arg(
64 long,
65 value_name = "DIR",
66 env = "MIDEN_SYSROOT",
67 help_heading = "Linker"
68 )
69 )]
70 pub sysroot: Option<PathBuf>,
71 #[cfg_attr(any(feature = "tui", feature = "repl", feature = "flamegraph"), arg(
73 long,
74 value_enum,
75 default_value_t = ColorChoice::Auto,
76 default_missing_value = "auto",
77 num_args(0..=1),
78 help_heading = "Output"
79 ))]
80 pub color: ColorChoice,
81 #[cfg_attr(
84 any(feature = "tui", feature = "repl", feature = "flamegraph"),
85 arg(long, help_heading = "Execution")
86 )]
87 pub entrypoint: Option<String>,
88 #[cfg(feature = "dap")]
93 #[cfg_attr(
94 any(feature = "tui", feature = "flamegraph"),
95 arg(long, value_name = "ADDR", help_heading = "Execution")
96 )]
97 pub dap_connect: Option<String>,
98 #[cfg(feature = "dap")]
102 #[cfg_attr(
103 feature = "tui",
104 arg(long, value_name = "ADDR", help_heading = "Execution")
105 )]
106 pub start_debug_adapter: Option<String>,
107 #[cfg(feature = "dap")]
113 #[cfg_attr(
114 feature = "tui",
115 arg(
116 long = "source-path-prefix",
117 alias = "trim-path-prefix",
118 value_name = "PATH",
119 help_heading = "Debugging"
120 )
121 )]
122 pub source_path_prefixes: Vec<PathBuf>,
123 #[cfg_attr(
125 any(feature = "tui", feature = "flamegraph"),
126 arg(
127 long = "search-path",
128 short = 'L',
129 value_name = "PATH",
130 help_heading = "Linker"
131 )
132 )]
133 pub search_path: Vec<PathBuf>,
134 #[cfg_attr(
145 any(feature = "tui", feature = "flamegraph"),
146 arg(
147 long = "link-library",
148 short = 'l',
149 value_name = "[KIND=]NAME",
150 value_delimiter = ',',
151 next_line_help(true),
152 help_heading = "Linker"
153 )
154 )]
155 pub link_libraries: Vec<LinkLibrary>,
156 #[cfg_attr(
158 any(feature = "tui", feature = "repl", feature = "flamegraph"),
159 arg(long, help_heading = "Output")
160 )]
161 pub repl: bool,
162}
163
164#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
173#[cfg_attr(
174 any(feature = "tui", feature = "repl", feature = "flamegraph"),
175 derive(clap::ValueEnum)
176)]
177pub enum ColorChoice {
178 Always,
181 AlwaysAnsi,
184 #[default]
188 Auto,
189 Never,
191}
192
193#[derive(Debug, thiserror::Error)]
194#[error("invalid color choice: {0}")]
195pub struct ColorChoiceParseError(std::borrow::Cow<'static, str>);
196
197impl FromStr for ColorChoice {
198 type Err = ColorChoiceParseError;
199
200 fn from_str(s: &str) -> Result<Self, Self::Err> {
201 match s.to_lowercase().as_str() {
202 "always" => Ok(ColorChoice::Always),
203 "always-ansi" => Ok(ColorChoice::AlwaysAnsi),
204 "never" => Ok(ColorChoice::Never),
205 "auto" => Ok(ColorChoice::Auto),
206 unknown => Err(ColorChoiceParseError(unknown.to_string().into())),
207 }
208 }
209}
210
211impl ColorChoice {
212 pub fn should_attempt_color(&self) -> bool {
214 match *self {
215 ColorChoice::Always => true,
216 ColorChoice::AlwaysAnsi => true,
217 ColorChoice::Never => false,
218 #[cfg(feature = "std")]
219 ColorChoice::Auto => self.env_allows_color(),
220 #[cfg(not(feature = "std"))]
221 ColorChoice::Auto => false,
222 }
223 }
224
225 #[cfg(not(windows))]
226 pub fn env_allows_color(&self) -> bool {
227 match std::env::var_os("TERM") {
228 None => return false,
231 Some(k) => {
232 if k == "dumb" {
233 return false;
234 }
235 }
236 }
237 if std::env::var_os("NO_COLOR").is_some() {
240 return false;
241 }
242 true
243 }
244
245 #[cfg(windows)]
246 pub fn env_allows_color(&self) -> bool {
247 if let Some(k) = std::env::var_os("TERM") {
251 if k == "dumb" {
252 return false;
253 }
254 }
255 if std::env::var_os("NO_COLOR").is_some() {
258 return false;
259 }
260 true
261 }
262
263 #[cfg(all(feature = "tui", windows))]
268 pub fn should_ansi(&self) -> bool {
269 match *self {
270 ColorChoice::Always => false,
271 ColorChoice::AlwaysAnsi => true,
272 ColorChoice::Never => false,
273 ColorChoice::Auto => {
274 match std::env::var("TERM") {
275 Err(_) => false,
276 Ok(k) => k != "dumb" && k != "cygwin",
280 }
281 }
282 }
283 }
284
285 #[cfg(not(feature = "tui"))]
290 pub fn should_ansi(&self) -> bool {
291 match *self {
292 ColorChoice::Always => false,
293 ColorChoice::AlwaysAnsi => true,
294 ColorChoice::Never => false,
295 ColorChoice::Auto => false,
296 }
297 }
298}
299
300impl DebuggerConfig {
301 pub fn working_dir(&self) -> Cow<'_, Path> {
302 match self.working_dir.as_deref() {
303 Some(path) => Cow::Borrowed(path),
304 None => std::env::current_dir()
305 .map(Cow::Owned)
306 .unwrap_or(Cow::Borrowed(Path::new("./"))),
307 }
308 }
309
310 pub fn toolchain_dir(&self) -> Option<PathBuf> {
311 let sysroot = if let Some(sysroot) = self.sysroot.as_deref() {
312 Cow::Borrowed(sysroot)
313 } else if let Some((midenup_home, midenup_channel)) =
314 midenup_home().and_then(|home| midenup_channel().map(|channel| (home, channel)))
315 {
316 Cow::Owned(midenup_home.join("toolchains").join(midenup_channel))
317 } else {
318 return None;
319 };
320
321 if sysroot.try_exists().ok().is_some_and(|exists| exists) {
322 Some(sysroot.into_owned())
323 } else {
324 None
325 }
326 }
327}
328
329fn midenup_home() -> Option<PathBuf> {
330 use std::process::Command;
331
332 let mut cmd = Command::new("midenup");
333 let mut output = cmd.args(["show", "home"]).output().ok()?;
334 if !output.status.success() {
335 return None;
336 }
337 let output = String::from_utf8(core::mem::take(&mut output.stdout)).ok()?;
338 let trimmed = output.trim_ascii();
339 if trimmed.is_empty() {
340 return None;
341 }
342 PathBuf::from_str(trimmed).ok()
343}
344
345fn midenup_channel() -> Option<String> {
346 use std::process::Command;
347
348 let mut cmd = Command::new("midenup");
349 let mut output = cmd.args(["show", "active-toolchain"]).output().ok()?;
350 if !output.status.success() {
351 return None;
352 }
353 let output = String::from_utf8(core::mem::take(&mut output.stdout)).ok()?;
354 let trimmed = output.trim_ascii();
355 if trimmed.is_empty() {
356 return None;
357 }
358 if output.len() == trimmed.len() {
359 Some(output)
360 } else {
361 Some(trimmed.to_string())
362 }
363}