python.rs

  1use crate::*;
  2use dap::{
  3    DebugRequest, StartDebuggingRequestArguments, adapters::DebugTaskDefinition,
  4    adapters::InlineValueProvider,
  5};
  6use gpui::AsyncApp;
  7use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
  8
  9#[derive(Default)]
 10pub(crate) struct PythonDebugAdapter;
 11
 12impl PythonDebugAdapter {
 13    const ADAPTER_NAME: &'static str = "Debugpy";
 14    const ADAPTER_PACKAGE_NAME: &'static str = "debugpy";
 15    const ADAPTER_PATH: &'static str = "src/debugpy/adapter";
 16    const LANGUAGE_NAME: &'static str = "Python";
 17
 18    fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
 19        let mut args = json!({
 20            "request": match config.request {
 21                DebugRequest::Launch(_) => "launch",
 22                DebugRequest::Attach(_) => "attach",
 23            },
 24            "subProcess": true,
 25            "redirectOutput": true,
 26        });
 27        let map = args.as_object_mut().unwrap();
 28        match &config.request {
 29            DebugRequest::Attach(attach) => {
 30                map.insert("processId".into(), attach.process_id.into());
 31            }
 32            DebugRequest::Launch(launch) => {
 33                map.insert("program".into(), launch.program.clone().into());
 34                map.insert("args".into(), launch.args.clone().into());
 35
 36                if let Some(stop_on_entry) = config.stop_on_entry {
 37                    map.insert("stopOnEntry".into(), stop_on_entry.into());
 38                }
 39                if let Some(cwd) = launch.cwd.as_ref() {
 40                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
 41                }
 42            }
 43        }
 44        StartDebuggingRequestArguments {
 45            configuration: args,
 46            request: config.request.to_dap(),
 47        }
 48    }
 49}
 50
 51#[async_trait(?Send)]
 52impl DebugAdapter for PythonDebugAdapter {
 53    fn name(&self) -> DebugAdapterName {
 54        DebugAdapterName(Self::ADAPTER_NAME.into())
 55    }
 56
 57    async fn fetch_latest_adapter_version(
 58        &self,
 59        delegate: &dyn DapDelegate,
 60    ) -> Result<AdapterVersion> {
 61        let github_repo = GithubRepo {
 62            repo_name: Self::ADAPTER_PACKAGE_NAME.into(),
 63            repo_owner: "microsoft".into(),
 64        };
 65
 66        adapters::fetch_latest_adapter_version_from_github(github_repo, delegate).await
 67    }
 68
 69    async fn install_binary(
 70        &self,
 71        version: AdapterVersion,
 72        delegate: &dyn DapDelegate,
 73    ) -> Result<()> {
 74        let version_path = adapters::download_adapter_from_github(
 75            self.name(),
 76            version,
 77            adapters::DownloadedFileType::Zip,
 78            delegate,
 79        )
 80        .await?;
 81
 82        // only needed when you install the latest version for the first time
 83        if let Some(debugpy_dir) =
 84            util::fs::find_file_name_in_dir(version_path.as_path(), |file_name| {
 85                file_name.starts_with("microsoft-debugpy-")
 86            })
 87            .await
 88        {
 89            // TODO Debugger: Rename folder instead of moving all files to another folder
 90            // We're doing unnecessary IO work right now
 91            util::fs::move_folder_files_to_folder(debugpy_dir.as_path(), version_path.as_path())
 92                .await?;
 93        }
 94
 95        Ok(())
 96    }
 97
 98    async fn get_installed_binary(
 99        &self,
100        delegate: &dyn DapDelegate,
101        config: &DebugTaskDefinition,
102        user_installed_path: Option<PathBuf>,
103        cx: &mut AsyncApp,
104    ) -> Result<DebugAdapterBinary> {
105        const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"];
106        let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
107        let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
108
109        let debugpy_dir = if let Some(user_installed_path) = user_installed_path {
110            user_installed_path
111        } else {
112            let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
113            let file_name_prefix = format!("{}_", Self::ADAPTER_NAME);
114
115            util::fs::find_file_name_in_dir(adapter_path.as_path(), |file_name| {
116                file_name.starts_with(&file_name_prefix)
117            })
118            .await
119            .ok_or_else(|| anyhow!("Debugpy directory not found"))?
120        };
121
122        let toolchain = delegate
123            .toolchain_store()
124            .active_toolchain(
125                delegate.worktree_id(),
126                Arc::from("".as_ref()),
127                language::LanguageName::new(Self::LANGUAGE_NAME),
128                cx,
129            )
130            .await;
131
132        let python_path = if let Some(toolchain) = toolchain {
133            Some(toolchain.path.to_string())
134        } else {
135            BINARY_NAMES
136                .iter()
137                .filter_map(|cmd| {
138                    delegate
139                        .which(OsStr::new(cmd))
140                        .map(|path| path.to_string_lossy().to_string())
141                })
142                .find(|_| true)
143        };
144
145        Ok(DebugAdapterBinary {
146            command: python_path.ok_or(anyhow!("failed to find binary path for python"))?,
147            arguments: vec![
148                debugpy_dir
149                    .join(Self::ADAPTER_PATH)
150                    .to_string_lossy()
151                    .to_string(),
152                format!("--port={}", port),
153                format!("--host={}", host),
154            ],
155            connection: Some(adapters::TcpArguments {
156                host,
157                port,
158                timeout,
159            }),
160            cwd: None,
161            envs: HashMap::default(),
162            request_args: self.request_args(config),
163        })
164    }
165
166    fn inline_value_provider(&self) -> Option<Box<dyn InlineValueProvider>> {
167        Some(Box::new(PythonInlineValueProvider))
168    }
169}
170
171struct PythonInlineValueProvider;
172
173impl InlineValueProvider for PythonInlineValueProvider {
174    fn provide(&self, variables: Vec<(String, lsp_types::Range)>) -> Vec<lsp_types::InlineValue> {
175        variables
176            .into_iter()
177            .map(|(variable, range)| {
178                if variable.contains(".") || variable.contains("[") {
179                    lsp_types::InlineValue::EvaluatableExpression(
180                        lsp_types::InlineValueEvaluatableExpression {
181                            range,
182                            expression: Some(variable),
183                        },
184                    )
185                } else {
186                    lsp_types::InlineValue::VariableLookup(lsp_types::InlineValueVariableLookup {
187                        range,
188                        variable_name: Some(variable),
189                        case_sensitive_lookup: true,
190                    })
191                }
192            })
193            .collect()
194    }
195}