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(feature = "tui", derive(clap::Parser))]
12#[cfg_attr(feature = "tui", command(author, version, about = "The interactive Miden debugger", long_about = None))]
13pub struct DebuggerConfig {
14 #[cfg_attr(feature = "tui", arg(value_name = "FILE"))]
20 pub input: Option<InputFile>,
21 #[cfg_attr(feature = "tui", arg(long, value_name = "FILE"))]
27 pub inputs: Option<ExecutionConfig>,
28 #[cfg_attr(feature = "tui", arg(last(true), value_name = "ARGV"))]
38 pub args: Vec<Felt>,
39 #[cfg_attr(
43 feature = "tui",
44 arg(long, value_name = "DIR", help_heading = "Execution")
45 )]
46 pub working_dir: Option<PathBuf>,
47 #[cfg_attr(
51 feature = "tui",
52 arg(
53 long,
54 value_name = "DIR",
55 env = "MIDEN_SYSROOT",
56 help_heading = "Linker"
57 )
58 )]
59 pub sysroot: Option<PathBuf>,
60 #[cfg_attr(feature = "tui", arg(
62 long,
63 value_enum,
64 default_value_t = ColorChoice::Auto,
65 default_missing_value = "auto",
66 num_args(0..=1),
67 help_heading = "Output"
68 ))]
69 pub color: ColorChoice,
70 #[cfg_attr(feature = "tui", arg(long, help_heading = "Execution"))]
73 pub entrypoint: Option<String>,
74 #[cfg(feature = "dap")]
79 #[cfg_attr(
80 feature = "tui",
81 arg(long, value_name = "ADDR", help_heading = "Execution")
82 )]
83 pub dap_connect: Option<String>,
84 #[cfg_attr(
86 feature = "tui",
87 arg(
88 long = "search-path",
89 short = 'L',
90 value_name = "PATH",
91 help_heading = "Linker"
92 )
93 )]
94 pub search_path: Vec<PathBuf>,
95 #[cfg_attr(
106 feature = "tui",
107 arg(
108 long = "link-library",
109 short = 'l',
110 value_name = "[KIND=]NAME",
111 value_delimiter = ',',
112 next_line_help(true),
113 help_heading = "Linker"
114 )
115 )]
116 pub link_libraries: Vec<LinkLibrary>,
117}
118
119#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
128#[cfg_attr(feature = "tui", derive(clap::ValueEnum))]
129pub enum ColorChoice {
130 Always,
133 AlwaysAnsi,
136 #[default]
140 Auto,
141 Never,
143}
144
145#[derive(Debug, thiserror::Error)]
146#[error("invalid color choice: {0}")]
147pub struct ColorChoiceParseError(std::borrow::Cow<'static, str>);
148
149impl FromStr for ColorChoice {
150 type Err = ColorChoiceParseError;
151
152 fn from_str(s: &str) -> Result<Self, Self::Err> {
153 match s.to_lowercase().as_str() {
154 "always" => Ok(ColorChoice::Always),
155 "always-ansi" => Ok(ColorChoice::AlwaysAnsi),
156 "never" => Ok(ColorChoice::Never),
157 "auto" => Ok(ColorChoice::Auto),
158 unknown => Err(ColorChoiceParseError(unknown.to_string().into())),
159 }
160 }
161}
162
163impl ColorChoice {
164 pub fn should_attempt_color(&self) -> bool {
166 match *self {
167 ColorChoice::Always => true,
168 ColorChoice::AlwaysAnsi => true,
169 ColorChoice::Never => false,
170 #[cfg(feature = "std")]
171 ColorChoice::Auto => self.env_allows_color(),
172 #[cfg(not(feature = "std"))]
173 ColorChoice::Auto => false,
174 }
175 }
176
177 #[cfg(not(windows))]
178 pub fn env_allows_color(&self) -> bool {
179 match std::env::var_os("TERM") {
180 None => return false,
183 Some(k) => {
184 if k == "dumb" {
185 return false;
186 }
187 }
188 }
189 if std::env::var_os("NO_COLOR").is_some() {
192 return false;
193 }
194 true
195 }
196
197 #[cfg(windows)]
198 pub fn env_allows_color(&self) -> bool {
199 if let Some(k) = std::env::var_os("TERM") {
203 if k == "dumb" {
204 return false;
205 }
206 }
207 if std::env::var_os("NO_COLOR").is_some() {
210 return false;
211 }
212 true
213 }
214
215 #[cfg(all(feature = "tui", windows))]
220 pub fn should_ansi(&self) -> bool {
221 match *self {
222 ColorChoice::Always => false,
223 ColorChoice::AlwaysAnsi => true,
224 ColorChoice::Never => false,
225 ColorChoice::Auto => {
226 match std::env::var("TERM") {
227 Err(_) => false,
228 Ok(k) => k != "dumb" && k != "cygwin",
232 }
233 }
234 }
235 }
236
237 #[cfg(not(feature = "tui"))]
242 pub fn should_ansi(&self) -> bool {
243 match *self {
244 ColorChoice::Always => false,
245 ColorChoice::AlwaysAnsi => true,
246 ColorChoice::Never => false,
247 ColorChoice::Auto => false,
248 }
249 }
250}
251
252impl DebuggerConfig {
253 pub fn working_dir(&self) -> Cow<'_, Path> {
254 match self.working_dir.as_deref() {
255 Some(path) => Cow::Borrowed(path),
256 None => std::env::current_dir()
257 .map(Cow::Owned)
258 .unwrap_or(Cow::Borrowed(Path::new("./"))),
259 }
260 }
261
262 pub fn toolchain_dir(&self) -> Option<PathBuf> {
263 let sysroot = if let Some(sysroot) = self.sysroot.as_deref() {
264 Cow::Borrowed(sysroot)
265 } else if let Some((midenup_home, midenup_channel)) =
266 midenup_home().and_then(|home| midenup_channel().map(|channel| (home, channel)))
267 {
268 Cow::Owned(midenup_home.join("toolchains").join(midenup_channel))
269 } else {
270 return None;
271 };
272
273 if sysroot.try_exists().ok().is_some_and(|exists| exists) {
274 Some(sysroot.into_owned())
275 } else {
276 None
277 }
278 }
279}
280
281fn midenup_home() -> Option<PathBuf> {
282 use std::process::Command;
283
284 let mut cmd = Command::new("midenup");
285 let mut output = cmd.args(["show", "home"]).output().ok()?;
286 if !output.status.success() {
287 return None;
288 }
289 let output = String::from_utf8(core::mem::take(&mut output.stdout)).ok()?;
290 let trimmed = output.trim_ascii();
291 if trimmed.is_empty() {
292 return None;
293 }
294 PathBuf::from_str(trimmed).ok()
295}
296
297fn midenup_channel() -> Option<String> {
298 use std::process::Command;
299
300 let mut cmd = Command::new("midenup");
301 let mut output = cmd.args(["show", "active-toolchain"]).output().ok()?;
302 if !output.status.success() {
303 return None;
304 }
305 let output = String::from_utf8(core::mem::take(&mut output.stdout)).ok()?;
306 let trimmed = output.trim_ascii();
307 if trimmed.is_empty() {
308 return None;
309 }
310 if output.len() == trimmed.len() {
311 Some(output)
312 } else {
313 Some(trimmed.to_string())
314 }
315}