use crate::*;use anyhow::{Context as _, bail};use collections::HashMap;use dap::{DebugRequest, StartDebuggingRequestArguments, adapters::DebugTaskDefinition};use fs::RemoveOptions;use futures::{StreamExt, TryStreamExt};use gpui::http_client::AsyncBody;use gpui::{AsyncApp, SharedString};use json_dotpath::DotPaths;use language::{LanguageName, Toolchain};use paths::debug_adapters_dir;use serde_json::Value;use smol::fs::File;use smol::io::AsyncReadExt;use smol::lock::OnceCell;use std::ffi::OsString;use std::net::Ipv4Addr;use std::str::FromStr;use std::{ ffi::OsStr, path::{Path, PathBuf},};use util::command::new_smol_command;use util::{ResultExt, paths::PathStyle, rel_path::RelPath}; enum DebugpyLaunchMode<'a> { Normal, AttachWithConnect { host: Option<&'a str> },} #[derive(Default)]pub(crate) struct PythonDebugAdapter { base_venv_path: OnceCell<Result<Arc<Path>, String>>, debugpy_whl_base_path: OnceCell<Result<Arc<Path>, String>>,} impl PythonDebugAdapter { const ADAPTER_NAME: &'static str = "Debugpy"; const DEBUG_ADAPTER_NAME: DebugAdapterName = DebugAdapterName(SharedString::new_static(Self::ADAPTER_NAME)); const LANGUAGE_NAME: &'static str = "Python"; async fn generate_debugpy_arguments<'a>( host: &'a Ipv4Addr, port: u16, launch_mode: DebugpyLaunchMode<'a>, user_installed_path: Option<&'a Path>, user_args: Option<Vec<String>>, ) -> Result<Vec<String>> { let mut args = if let Some(user_installed_path) = user_installed_path { log::debug!( "Using user-installed debugpy adapter from: {}", user_installed_path.display() ); vec![user_installed_path.to_string_lossy().into_owned()] } else { let adapter_path = paths::debug_adapters_dir().join(Self::DEBUG_ADAPTER_NAME.as_ref()); let path = adapter_path .join("debugpy") .join("adapter") .to_string_lossy() .into_owned(); log::debug!("Using pip debugpy adapter from: {path}"); vec![path] }; args.extend(if let Some(args) = user_args { args } else { match launch_mode { DebugpyLaunchMode::Normal => { vec![format!("--host={}", host), format!("--port={}", port)] } DebugpyLaunchMode::AttachWithConnect { host } => { let mut args = vec!["connect".to_string()]; if let Some(host) = host { args.push(format!("{host}:")); } args.push(format!("{port}")); args } } }); Ok(args) } async fn request_args( &self, delegate: &Arc<dyn DapDelegate>, task_definition: &DebugTaskDefinition, ) -> Result<StartDebuggingRequestArguments> { let request = self.request_kind(&task_definition.config).await?; let mut configuration = task_definition.config.clone(); if let Ok(console) = configuration.dot_get_mut("console") { // Use built-in Zed terminal if user did not explicitly provide a setting for console. if console.is_null() { *console = Value::String("integratedTerminal".into()); } } if let Some(obj) = configuration.as_object_mut() { obj.entry("cwd") .or_insert(delegate.worktree_root_path().to_string_lossy().into()); } Ok(StartDebuggingRequestArguments { configuration, request, }) } async fn fetch_wheel( &self, toolchain: Option<Toolchain>, delegate: &Arc<dyn DapDelegate>, ) -> Result<Arc<Path>> { let download_dir = debug_adapters_dir().join(Self::ADAPTER_NAME).join("wheels"); std::fs::create_dir_all(&download_dir)?; let venv_python = self.base_venv_path(toolchain, delegate).await?; let installation_succeeded = util::command::new_smol_command(venv_python.as_ref()) .args([ "-m", "pip", "download", "debugpy", "--only-binary=:all:", "-d", download_dir.to_string_lossy().as_ref(), ]) .output() .await .context("spawn system python")? .status .success(); if !installation_succeeded { bail!("debugpy installation failed (could not fetch Debugpy's wheel)"); } let wheel_path = std::fs::read_dir(&download_dir)? .find_map(|entry| { entry.ok().filter(|e| { e.file_type().is_ok_and(|typ| typ.is_file()) && Path::new(&e.file_name()).extension() == Some("whl".as_ref()) }) }) .with_context(|| format!("Did not find a .whl in {download_dir:?}"))?; util::archive::extract_zip( &debug_adapters_dir().join(Self::ADAPTER_NAME), File::open(&wheel_path.path()).await?, ) .await?; Ok(Arc::from(wheel_path.path())) } async fn maybe_fetch_new_wheel( &self, toolchain: Option<Toolchain>, delegate: &Arc<dyn DapDelegate>, ) -> Result<()> { let latest_release = delegate .http_client() .get( "https://pypi.org/pypi/debugpy/json", AsyncBody::empty(), false, ) .await .log_err(); let response = latest_release .filter(|response| response.status().is_success()) .context("getting latest release")?; let download_dir = debug_adapters_dir().join(Self::ADAPTER_NAME); std::fs::create_dir_all(&download_dir)?; let mut output = String::new(); response.into_body().read_to_string(&mut output).await?; let as_json = serde_json::Value::from_str(&output)?; let latest_version = as_json .get("info") .and_then(|info| { info.get("version") .and_then(|version| version.as_str()) .map(ToOwned::to_owned) }) .context("parsing latest release information")?; let dist_info_dirname: OsString = format!("debugpy-{latest_version}.dist-info").into(); let is_up_to_date = delegate .fs() .read_dir(&debug_adapters_dir().join(Self::ADAPTER_NAME)) .await? .into_stream() .any(async |entry| { entry.is_ok_and(|e| e.file_name().is_some_and(|name| name == dist_info_dirname)) }) .await; if !is_up_to_date { delegate .fs() .remove_dir( &debug_adapters_dir().join(Self::ADAPTER_NAME), RemoveOptions { recursive: true, ignore_if_not_exists: true, }, ) .await?; self.fetch_wheel(toolchain, delegate).await?; } anyhow::Ok(()) } async fn fetch_debugpy_whl( &self, toolchain: Option<Toolchain>, delegate: &Arc<dyn DapDelegate>, ) -> Result<Arc<Path>, String> { self.debugpy_whl_base_path .get_or_init(|| async move { self.maybe_fetch_new_wheel(toolchain, delegate) .await .map_err(|e| format!("{e}"))?; Ok(Arc::from( debug_adapters_dir() .join(Self::ADAPTER_NAME) .join("debugpy") .join("adapter") .as_ref(), )) }) .await .clone() } async fn base_venv_path( &self, toolchain: Option<Toolchain>, delegate: &Arc<dyn DapDelegate>, ) -> Result<Arc<Path>> { let result = self.base_venv_path .get_or_init(|| async { let base_python = if let Some(toolchain) = toolchain { toolchain.path.to_string() } else { Self::system_python_name(delegate).await.ok_or_else(|| { let mut message = "Could not find a Python installation".to_owned(); if cfg!(windows){ message.push_str(". Install Python from the Microsoft Store, or manually from https://www.python.org/downloads/windows.") } message })? }; let debug_adapter_path = paths::debug_adapters_dir().join(Self::DEBUG_ADAPTER_NAME.as_ref()); let output = util::command::new_smol_command(&base_python) .args(["-m", "venv", "zed_base_venv"]) .current_dir( &debug_adapter_path, ) .spawn() .map_err(|e| format!("{e:#?}"))? .output() .await .map_err(|e| format!("{e:#?}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let debug_adapter_path = debug_adapter_path.display(); return Err(format!("Failed to create base virtual environment with {base_python} in:\n{debug_adapter_path}\nstderr:\n{stderr}\nstdout:\n{stdout}\n")); } const PYTHON_PATH: &str = if cfg!(target_os = "windows") { "Scripts/python.exe" } else { "bin/python3" }; Ok(Arc::from( paths::debug_adapters_dir() .join(Self::DEBUG_ADAPTER_NAME.as_ref()) .join("zed_base_venv") .join(PYTHON_PATH) .as_ref(), )) }) .await .clone(); match result { Ok(path) => Ok(path), Err(e) => Err(anyhow::anyhow!("{e}")), } } async fn system_python_name(delegate: &Arc<dyn DapDelegate>) -> Option<String> { const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"]; let mut name = None; for cmd in BINARY_NAMES { let Some(path) = delegate.which(OsStr::new(cmd)).await else { continue; }; // Try to detect situations where `python3` exists but is not a real Python interpreter. // Notably, on fresh Windows installs, `python3` is a shim that opens the Microsoft Store app // when run with no arguments, and just fails otherwise. let Some(output) = new_smol_command(&path) .args(["-c", "print(1 + 2)"]) .output() .await .ok() else { continue; }; if output.stdout.trim_ascii() != b"3" { continue; } name = Some(path.to_string_lossy().into_owned()); break; } name } async fn get_installed_binary( &self, delegate: &Arc<dyn DapDelegate>, config: &DebugTaskDefinition, user_installed_path: Option<PathBuf>, user_args: Option<Vec<String>>, user_env: Option<HashMap<String, String>>, python_from_toolchain: Option<String>, ) -> Result<DebugAdapterBinary> { let mut tcp_connection = config.tcp_connection.clone().unwrap_or_default(); let (config_port, config_host) = config .config .get("connect") .map(|value| { ( value .get("port") .and_then(|val| val.as_u64().map(|p| p as u16)), value.get("host").and_then(|val| val.as_str()), ) }) .unwrap_or_else(|| { ( config .config .get("port") .and_then(|port| port.as_u64().map(|p| p as u16)), config.config.get("host").and_then(|host| host.as_str()), ) }); let is_attach_with_connect = if config .config .get("request") .is_some_and(|val| val.as_str().is_some_and(|request| request == "attach")) { if tcp_connection.host.is_some() && config_host.is_some() { bail!("Cannot have two different hosts in debug configuration") } else if tcp_connection.port.is_some() && config_port.is_some() { bail!("Cannot have two different ports in debug configuration") } tcp_connection.port = config_port; DebugpyLaunchMode::AttachWithConnect { host: config_host } } else { DebugpyLaunchMode::Normal }; let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; let python_path = if let Some(toolchain) = python_from_toolchain { Some(toolchain) } else { Self::system_python_name(delegate).await }; let python_command = python_path.context("failed to find binary path for Python")?; log::debug!("Using Python executable: {}", python_command); let arguments = Self::generate_debugpy_arguments( &host, port, is_attach_with_connect, user_installed_path.as_deref(), user_args, ) .await?; log::debug!( "Starting debugpy adapter with command: {} {}", python_command, arguments.join(" ") ); Ok(DebugAdapterBinary { command: Some(python_command), arguments, connection: Some(adapters::TcpArguments { host, port, timeout, }), cwd: Some(delegate.worktree_root_path().to_path_buf()), envs: user_env.unwrap_or_default(), request_args: self.request_args(delegate, config).await?, }) }} #[async_trait(?Send)]impl DebugAdapter for PythonDebugAdapter { fn name(&self) -> DebugAdapterName { Self::DEBUG_ADAPTER_NAME } fn adapter_language_name(&self) -> Option<LanguageName> { Some(SharedString::new_static("Python").into()) } async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> { let mut args = json!({ "request": match zed_scenario.request { DebugRequest::Launch(_) => "launch", DebugRequest::Attach(_) => "attach", }, "subProcess": true, "redirectOutput": true, }); let map = args.as_object_mut().unwrap(); match &zed_scenario.request { DebugRequest::Attach(attach) => { map.insert("processId".into(), attach.process_id.into()); } DebugRequest::Launch(launch) => { map.insert("program".into(), launch.program.clone().into()); map.insert("args".into(), launch.args.clone().into()); if !launch.env.is_empty() { map.insert("env".into(), launch.env_json()); } if let Some(stop_on_entry) = zed_scenario.stop_on_entry { map.insert("stopOnEntry".into(), stop_on_entry.into()); } if let Some(cwd) = launch.cwd.as_ref() { map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into()); } } } Ok(DebugScenario { adapter: zed_scenario.adapter, label: zed_scenario.label, config: args, build: None, tcp_connection: None, }) } fn dap_schema(&self) -> serde_json::Value { json!({ "properties": { "request": { "type": "string", "enum": ["attach", "launch"], "description": "Debug adapter request type" }, "autoReload": { "default": {}, "description": "Configures automatic reload of code on edit.", "properties": { "enable": { "default": false, "description": "Automatically reload code on edit.", "type": "boolean" }, "exclude": { "default": [ "**/.git/**", "**/.metadata/**", "**/__pycache__/**", "**/node_modules/**", "**/site-packages/**" ], "description": "Glob patterns of paths to exclude from auto reload.", "items": { "type": "string" }, "type": "array" }, "include": { "default": [Lines 1–500 of 1122. Load more
use crate::*; use anyhow::{Context as _, bail}; use collections::HashMap; use dap::{DebugRequest, StartDebuggingRequestArguments, adapters::DebugTaskDefinition}; use fs::RemoveOptions; use futures::{StreamExt, TryStreamExt}; use gpui::http_client::AsyncBody; use gpui::{AsyncApp, SharedString}; use json_dotpath::DotPaths; use language::{LanguageName, Toolchain}; use paths::debug_adapters_dir; use serde_json::Value; use smol::fs::File; use smol::io::AsyncReadExt; use smol::lock::OnceCell; use std::ffi::OsString; use std::net::Ipv4Addr; use std::str::FromStr; use std::{ ffi::OsStr, path::{Path, PathBuf}, }; use util::command::new_smol_command; use util::{ResultExt, paths::PathStyle, rel_path::RelPath}; enum DebugpyLaunchMode<'a> { Normal, AttachWithConnect { host: Option<&'a str> }, } #[derive(Default)] pub(crate) struct PythonDebugAdapter { base_venv_path: OnceCell<Result<Arc<Path>, String>>, debugpy_whl_base_path: OnceCell<Result<Arc<Path>, String>>, } impl PythonDebugAdapter { const ADAPTER_NAME: &'static str = "Debugpy"; const DEBUG_ADAPTER_NAME: DebugAdapterName = DebugAdapterName(SharedString::new_static(Self::ADAPTER_NAME)); const LANGUAGE_NAME: &'static str = "Python"; async fn generate_debugpy_arguments<'a>( host: &'a Ipv4Addr, port: u16, launch_mode: DebugpyLaunchMode<'a>, user_installed_path: Option<&'a Path>, user_args: Option<Vec<String>>, ) -> Result<Vec<String>> { let mut args = if let Some(user_installed_path) = user_installed_path { log::debug!( "Using user-installed debugpy adapter from: {}", user_installed_path.display() ); vec![user_installed_path.to_string_lossy().into_owned()] } else { let adapter_path = paths::debug_adapters_dir().join(Self::DEBUG_ADAPTER_NAME.as_ref()); let path = adapter_path .join("debugpy") .join("adapter") .to_string_lossy() .into_owned(); log::debug!("Using pip debugpy adapter from: {path}"); vec![path] }; args.extend(if let Some(args) = user_args { args } else { match launch_mode { DebugpyLaunchMode::Normal => { vec![format!("--host={}", host), format!("--port={}", port)] } DebugpyLaunchMode::AttachWithConnect { host } => { let mut args = vec!["connect".to_string()]; if let Some(host) = host { args.push(format!("{host}:")); } args.push(format!("{port}")); args } } }); Ok(args) } async fn request_args( &self, delegate: &Arc<dyn DapDelegate>, task_definition: &DebugTaskDefinition, ) -> Result<StartDebuggingRequestArguments> { let request = self.request_kind(&task_definition.config).await?; let mut configuration = task_definition.config.clone(); if let Ok(console) = configuration.dot_get_mut("console") { // Use built-in Zed terminal if user did not explicitly provide a setting for console. if console.is_null() { *console = Value::String("integratedTerminal".into()); } } if let Some(obj) = configuration.as_object_mut() { obj.entry("cwd") .or_insert(delegate.worktree_root_path().to_string_lossy().into()); } Ok(StartDebuggingRequestArguments { configuration, request, }) } async fn fetch_wheel( &self, toolchain: Option<Toolchain>, delegate: &Arc<dyn DapDelegate>, ) -> Result<Arc<Path>> { let download_dir = debug_adapters_dir().join(Self::ADAPTER_NAME).join("wheels"); std::fs::create_dir_all(&download_dir)?; let venv_python = self.base_venv_path(toolchain, delegate).await?; let installation_succeeded = util::command::new_smol_command(venv_python.as_ref()) .args([ "-m", "pip", "download", "debugpy", "--only-binary=:all:", "-d", download_dir.to_string_lossy().as_ref(), ]) .output() .await .context("spawn system python")? .status .success(); if !installation_succeeded { bail!("debugpy installation failed (could not fetch Debugpy's wheel)"); } let wheel_path = std::fs::read_dir(&download_dir)? .find_map(|entry| { entry.ok().filter(|e| { e.file_type().is_ok_and(|typ| typ.is_file()) && Path::new(&e.file_name()).extension() == Some("whl".as_ref()) }) }) .with_context(|| format!("Did not find a .whl in {download_dir:?}"))?; util::archive::extract_zip( &debug_adapters_dir().join(Self::ADAPTER_NAME), File::open(&wheel_path.path()).await?, ) .await?; Ok(Arc::from(wheel_path.path())) } async fn maybe_fetch_new_wheel( &self, toolchain: Option<Toolchain>, delegate: &Arc<dyn DapDelegate>, ) -> Result<()> { let latest_release = delegate .http_client() .get( "https://pypi.org/pypi/debugpy/json", AsyncBody::empty(), false, ) .await .log_err(); let response = latest_release .filter(|response| response.status().is_success()) .context("getting latest release")?; let download_dir = debug_adapters_dir().join(Self::ADAPTER_NAME); std::fs::create_dir_all(&download_dir)?; let mut output = String::new(); response.into_body().read_to_string(&mut output).await?; let as_json = serde_json::Value::from_str(&output)?; let latest_version = as_json .get("info") .and_then(|info| { info.get("version") .and_then(|version| version.as_str()) .map(ToOwned::to_owned) }) .context("parsing latest release information")?; let dist_info_dirname: OsString = format!("debugpy-{latest_version}.dist-info").into(); let is_up_to_date = delegate .fs() .read_dir(&debug_adapters_dir().join(Self::ADAPTER_NAME)) .await? .into_stream() .any(async |entry| { entry.is_ok_and(|e| e.file_name().is_some_and(|name| name == dist_info_dirname)) }) .await; if !is_up_to_date { delegate .fs() .remove_dir( &debug_adapters_dir().join(Self::ADAPTER_NAME), RemoveOptions { recursive: true, ignore_if_not_exists: true, }, ) .await?; self.fetch_wheel(toolchain, delegate).await?; } anyhow::Ok(()) } async fn fetch_debugpy_whl( &self, toolchain: Option<Toolchain>, delegate: &Arc<dyn DapDelegate>, ) -> Result<Arc<Path>, String> { self.debugpy_whl_base_path .get_or_init(|| async move { self.maybe_fetch_new_wheel(toolchain, delegate) .await .map_err(|e| format!("{e}"))?; Ok(Arc::from( debug_adapters_dir() .join(Self::ADAPTER_NAME) .join("debugpy") .join("adapter") .as_ref(), )) }) .await .clone() } async fn base_venv_path( &self, toolchain: Option<Toolchain>, delegate: &Arc<dyn DapDelegate>, ) -> Result<Arc<Path>> { let result = self.base_venv_path .get_or_init(|| async { let base_python = if let Some(toolchain) = toolchain { toolchain.path.to_string() } else { Self::system_python_name(delegate).await.ok_or_else(|| { let mut message = "Could not find a Python installation".to_owned(); if cfg!(windows){ message.push_str(". Install Python from the Microsoft Store, or manually from https://www.python.org/downloads/windows.") } message })? }; let debug_adapter_path = paths::debug_adapters_dir().join(Self::DEBUG_ADAPTER_NAME.as_ref()); let output = util::command::new_smol_command(&base_python) .args(["-m", "venv", "zed_base_venv"]) .current_dir( &debug_adapter_path, ) .spawn() .map_err(|e| format!("{e:#?}"))? .output() .await .map_err(|e| format!("{e:#?}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let debug_adapter_path = debug_adapter_path.display(); return Err(format!("Failed to create base virtual environment with {base_python} in:\n{debug_adapter_path}\nstderr:\n{stderr}\nstdout:\n{stdout}\n")); } const PYTHON_PATH: &str = if cfg!(target_os = "windows") { "Scripts/python.exe" } else { "bin/python3" }; Ok(Arc::from( paths::debug_adapters_dir() .join(Self::DEBUG_ADAPTER_NAME.as_ref()) .join("zed_base_venv") .join(PYTHON_PATH) .as_ref(), )) }) .await .clone(); match result { Ok(path) => Ok(path), Err(e) => Err(anyhow::anyhow!("{e}")), } } async fn system_python_name(delegate: &Arc<dyn DapDelegate>) -> Option<String> { const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"]; let mut name = None; for cmd in BINARY_NAMES { let Some(path) = delegate.which(OsStr::new(cmd)).await else { continue; }; // Try to detect situations where `python3` exists but is not a real Python interpreter. // Notably, on fresh Windows installs, `python3` is a shim that opens the Microsoft Store app // when run with no arguments, and just fails otherwise. let Some(output) = new_smol_command(&path) .args(["-c", "print(1 + 2)"]) .output() .await .ok() else { continue; }; if output.stdout.trim_ascii() != b"3" { continue; } name = Some(path.to_string_lossy().into_owned()); break; } name } async fn get_installed_binary( &self, delegate: &Arc<dyn DapDelegate>, config: &DebugTaskDefinition, user_installed_path: Option<PathBuf>, user_args: Option<Vec<String>>, user_env: Option<HashMap<String, String>>, python_from_toolchain: Option<String>, ) -> Result<DebugAdapterBinary> { let mut tcp_connection = config.tcp_connection.clone().unwrap_or_default(); let (config_port, config_host) = config .config .get("connect") .map(|value| { ( value .get("port") .and_then(|val| val.as_u64().map(|p| p as u16)), value.get("host").and_then(|val| val.as_str()), ) }) .unwrap_or_else(|| { ( config .config .get("port") .and_then(|port| port.as_u64().map(|p| p as u16)), config.config.get("host").and_then(|host| host.as_str()), ) }); let is_attach_with_connect = if config .config .get("request") .is_some_and(|val| val.as_str().is_some_and(|request| request == "attach")) { if tcp_connection.host.is_some() && config_host.is_some() { bail!("Cannot have two different hosts in debug configuration") } else if tcp_connection.port.is_some() && config_port.is_some() { bail!("Cannot have two different ports in debug configuration") } tcp_connection.port = config_port; DebugpyLaunchMode::AttachWithConnect { host: config_host } } else { DebugpyLaunchMode::Normal }; let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; let python_path = if let Some(toolchain) = python_from_toolchain { Some(toolchain) } else { Self::system_python_name(delegate).await }; let python_command = python_path.context("failed to find binary path for Python")?; log::debug!("Using Python executable: {}", python_command); let arguments = Self::generate_debugpy_arguments( &host, port, is_attach_with_connect, user_installed_path.as_deref(), user_args, ) .await?; log::debug!( "Starting debugpy adapter with command: {} {}", python_command, arguments.join(" ") ); Ok(DebugAdapterBinary { command: Some(python_command), arguments, connection: Some(adapters::TcpArguments { host, port, timeout, }), cwd: Some(delegate.worktree_root_path().to_path_buf()), envs: user_env.unwrap_or_default(), request_args: self.request_args(delegate, config).await?, }) } } #[async_trait(?Send)] impl DebugAdapter for PythonDebugAdapter { fn name(&self) -> DebugAdapterName { Self::DEBUG_ADAPTER_NAME } fn adapter_language_name(&self) -> Option<LanguageName> { Some(SharedString::new_static("Python").into()) } async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> { let mut args = json!({ "request": match zed_scenario.request { DebugRequest::Launch(_) => "launch", DebugRequest::Attach(_) => "attach", }, "subProcess": true, "redirectOutput": true, }); let map = args.as_object_mut().unwrap(); match &zed_scenario.request { DebugRequest::Attach(attach) => { map.insert("processId".into(), attach.process_id.into()); } DebugRequest::Launch(launch) => { map.insert("program".into(), launch.program.clone().into()); map.insert("args".into(), launch.args.clone().into()); if !launch.env.is_empty() { map.insert("env".into(), launch.env_json()); } if let Some(stop_on_entry) = zed_scenario.stop_on_entry { map.insert("stopOnEntry".into(), stop_on_entry.into()); } if let Some(cwd) = launch.cwd.as_ref() { map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into()); } } } Ok(DebugScenario { adapter: zed_scenario.adapter, label: zed_scenario.label, config: args, build: None, tcp_connection: None, }) } fn dap_schema(&self) -> serde_json::Value { json!({ "properties": { "request": { "type": "string", "enum": ["attach", "launch"], "description": "Debug adapter request type" }, "autoReload": { "default": {}, "description": "Configures automatic reload of code on edit.", "properties": { "enable": { "default": false, "description": "Automatically reload code on edit.", "type": "boolean" }, "exclude": { "default": [ "**/.git/**", "**/.metadata/**", "**/__pycache__/**", "**/node_modules/**", "**/site-packages/**" ], "description": "Glob patterns of paths to exclude from auto reload.", "items": { "type": "string" }, "type": "array" }, "include": { "default": [