use anyhow::{Context as _, ensure};use anyhow::{Result, anyhow};use async_trait::async_trait;use collections::HashMap;use gpui::{App, Task};use gpui::{AsyncApp, SharedString};use language::Toolchain;use language::ToolchainList;use language::ToolchainLister;use language::language_settings::language_settings;use language::{ContextLocation, LanguageToolchainStore};use language::{ContextProvider, LspAdapter, LspAdapterDelegate};use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery};use lsp::LanguageServerBinary;use lsp::LanguageServerName;use node_runtime::NodeRuntime;use pet_core::Configuration;use pet_core::os_environment::Environment;use pet_core::python_environment::PythonEnvironmentKind;use project::Fs;use project::lsp_store::language_server_settings;use serde_json::{Value, json};use smol::lock::OnceCell;use std::cmp::Ordering; use parking_lot::Mutex;use std::str::FromStr;use std::{ any::Any, borrow::Cow, ffi::OsString, fmt::Write, fs, io::{self, BufRead}, path::{Path, PathBuf}, sync::Arc,};use task::{TaskTemplate, TaskTemplates, VariableName};use util::ResultExt; pub(crate) struct PyprojectTomlManifestProvider; impl ManifestProvider for PyprojectTomlManifestProvider { fn name(&self) -> ManifestName { SharedString::new_static("pyproject.toml").into() } fn search( &self, ManifestQuery { path, depth, delegate, }: ManifestQuery, ) -> Option<Arc<Path>> { for path in path.ancestors().take(depth) { let p = path.join("pyproject.toml"); if delegate.exists(&p, Some(false)) { return Some(path.into()); } } None }} const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js";const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js"; enum TestRunner { UNITTEST, PYTEST,} impl FromStr for TestRunner { type Err = (); fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { match s { "unittest" => Ok(Self::UNITTEST), "pytest" => Ok(Self::PYTEST), _ => Err(()), } }} fn server_binary_arguments(server_path: &Path) -> Vec<OsString> { vec![server_path.into(), "--stdio".into()]} pub struct PythonLspAdapter { node: NodeRuntime,} impl PythonLspAdapter { const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pyright"); pub fn new(node: NodeRuntime) -> Self { PythonLspAdapter { node } }} #[async_trait(?Send)]impl LspAdapter for PythonLspAdapter { fn name(&self) -> LanguageServerName { Self::SERVER_NAME.clone() } async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, _: Arc<dyn LanguageToolchainStore>, _: &AsyncApp, ) -> Option<LanguageServerBinary> { if let Some(pyright_bin) = delegate.which("pyright-langserver".as_ref()).await { let env = delegate.shell_env().await; Some(LanguageServerBinary { path: pyright_bin, env: Some(env), arguments: vec!["--stdio".into()], }) } else { let node = delegate.which("node".as_ref()).await?; let (node_modules_path, _) = delegate .npm_package_installed_version(Self::SERVER_NAME.as_ref()) .await .log_err()??; let path = node_modules_path.join(NODE_MODULE_RELATIVE_SERVER_PATH); Some(LanguageServerBinary { path: node, env: None, arguments: server_binary_arguments(&path), }) } } async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, ) -> Result<Box<dyn 'static + Any + Send>> { Ok(Box::new( self.node .npm_package_latest_version(Self::SERVER_NAME.as_ref()) .await?, ) as Box<_>) } async fn fetch_server_binary( &self, latest_version: Box<dyn 'static + Send + Any>, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result<LanguageServerBinary> { let latest_version = latest_version.downcast::<String>().unwrap(); let server_path = container_dir.join(SERVER_PATH); self.node .npm_install_packages( &container_dir, &[(Self::SERVER_NAME.as_ref(), latest_version.as_str())], ) .await?; Ok(LanguageServerBinary { path: self.node.binary_path().await?, env: None, arguments: server_binary_arguments(&server_path), }) } async fn check_if_version_installed( &self, version: &(dyn 'static + Send + Any), container_dir: &PathBuf, _: &dyn LspAdapterDelegate, ) -> Option<LanguageServerBinary> { let version = version.downcast_ref::<String>().unwrap(); let server_path = container_dir.join(SERVER_PATH); let should_install_language_server = self .node .should_install_npm_package( Self::SERVER_NAME.as_ref(), &server_path, &container_dir, &version, ) .await; if should_install_language_server { None } else { Some(LanguageServerBinary { path: self.node.binary_path().await.ok()?, env: None, arguments: server_binary_arguments(&server_path), }) } } async fn cached_server_binary( &self, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option<LanguageServerBinary> { get_cached_server_binary(container_dir, &self.node).await } async fn process_completions(&self, items: &mut [lsp::CompletionItem]) { // Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`. // Where `XX` is the sorting category, `YYYY` is based on most recent usage, // and `name` is the symbol name itself. // // Because the symbol name is included, there generally are not ties when // sorting by the `sortText`, so the symbol's fuzzy match score is not taken // into account. Here, we remove the symbol name from the sortText in order // to allow our own fuzzy score to be used to break ties. // // see https://github.com/microsoft/pyright/blob/95ef4e103b9b2f129c9320427e51b73ea7cf78bd/packages/pyright-internal/src/languageService/completionProvider.ts#LL2873 for item in items { let Some(sort_text) = &mut item.sort_text else { continue; }; let mut parts = sort_text.split('.'); let Some(first) = parts.next() else { continue }; let Some(second) = parts.next() else { continue }; let Some(_) = parts.next() else { continue }; sort_text.replace_range(first.len() + second.len() + 1.., ""); } } async fn label_for_completion( &self, item: &lsp::CompletionItem, language: &Arc<language::Language>, ) -> Option<language::CodeLabel> { let label = &item.label; let grammar = language.grammar()?; let highlight_id = match item.kind? { lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?, lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?, lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?, lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?, _ => return None, }; Some(language::CodeLabel { text: label.clone(), runs: vec![(0..label.len(), highlight_id)], filter_range: 0..label.len(), }) } async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, language: &Arc<language::Language>, ) -> Option<language::CodeLabel> { let (text, filter_range, display_range) = match kind { lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => { let text = format!("def {}():\n", name); let filter_range = 4..4 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::CLASS => { let text = format!("class {}:", name); let filter_range = 6..6 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::CONSTANT => { let text = format!("{} = 0", name); let filter_range = 0..name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } _ => return None, }; Some(language::CodeLabel { runs: language.highlight_text(&text.as_str().into(), display_range.clone()), text: text[display_range].to_string(), filter_range, }) } async fn workspace_configuration( self: Arc<Self>, _: &dyn Fs, adapter: &Arc<dyn LspAdapterDelegate>, toolchains: Arc<dyn LanguageToolchainStore>, cx: &mut AsyncApp, ) -> Result<Value> { let toolchain = toolchains .active_toolchain( adapter.worktree_id(), Arc::from("".as_ref()), LanguageName::new("Python"), cx, ) .await; cx.update(move |cx| { let mut user_settings = language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx) .and_then(|s| s.settings.clone()) .unwrap_or_default(); // If python.pythonPath is not set in user config, do so using our toolchain picker. if let Some(toolchain) = toolchain { if user_settings.is_null() { user_settings = Value::Object(serde_json::Map::default()); } let object = user_settings.as_object_mut().unwrap(); if let Some(python) = object .entry("python") .or_insert(Value::Object(serde_json::Map::default())) .as_object_mut() { python .entry("pythonPath") .or_insert(Value::String(toolchain.path.into())); } } user_settings }) } fn manifest_name(&self) -> Option<ManifestName> { Some(SharedString::new_static("pyproject.toml").into()) }} async fn get_cached_server_binary( container_dir: PathBuf, node: &NodeRuntime,) -> Option<LanguageServerBinary> { let server_path = container_dir.join(SERVER_PATH); if server_path.exists() { Some(LanguageServerBinary { path: node.binary_path().await.log_err()?, env: None, arguments: server_binary_arguments(&server_path), }) } else { log::error!("missing executable in directory {:?}", server_path); None }} pub(crate) struct PythonContextProvider; const PYTHON_TEST_TARGET_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("PYTHON_TEST_TARGET")); const PYTHON_ACTIVE_TOOLCHAIN_PATH: VariableName = VariableName::Custom(Cow::Borrowed("PYTHON_ACTIVE_ZED_TOOLCHAIN")); const PYTHON_ACTIVE_TOOLCHAIN_PATH_RAW: VariableName = VariableName::Custom(Cow::Borrowed("PYTHON_ACTIVE_ZED_TOOLCHAIN_RAW")); const PYTHON_MODULE_NAME_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("PYTHON_MODULE_NAME")); impl ContextProvider for PythonContextProvider { fn build_context( &self, variables: &task::TaskVariables, location: ContextLocation<'_>, _: Option<HashMap<String, String>>, toolchains: Arc<dyn LanguageToolchainStore>, cx: &mut gpui::App, ) -> Task<Result<task::TaskVariables>> { let test_target = match selected_test_runner(location.file_location.buffer.read(cx).file(), cx) { TestRunner::UNITTEST => self.build_unittest_target(variables), TestRunner::PYTEST => self.build_pytest_target(variables), }; let module_target = self.build_module_target(variables); let worktree_id = location .file_location .buffer .read(cx) .file() .map(|f| f.worktree_id(cx)); cx.spawn(async move |cx| { let raw_toolchain = if let Some(worktree_id) = worktree_id { toolchains .active_toolchain(worktree_id, Arc::from("".as_ref()), "Python".into(), cx) .await .map_or_else( || String::from("python3"), |toolchain| toolchain.path.to_string(), ) } else { String::from("python3") }; let active_toolchain = format!("\"{raw_toolchain}\""); let toolchain = (PYTHON_ACTIVE_TOOLCHAIN_PATH, active_toolchain); let raw_toolchain = (PYTHON_ACTIVE_TOOLCHAIN_PATH_RAW, raw_toolchain); Ok(task::TaskVariables::from_iter( test_target .into_iter() .chain(module_target.into_iter()) .chain([toolchain, raw_toolchain]), )) }) } fn associated_tasks( &self, file: Option<Arc<dyn language::File>>, cx: &App, ) -> Option<TaskTemplates> { let test_runner = selected_test_runner(file.as_ref(), cx); let mut tasks = vec![ // Execute a selection TaskTemplate { label: "execute selection".to_owned(), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![ "-c".to_owned(), VariableName::SelectedText.template_value_with_whitespace(), ], cwd: Some("$ZED_WORKTREE_ROOT".into()), ..TaskTemplate::default() }, // Execute an entire file TaskTemplate { label: format!("run '{}'", VariableName::File.template_value()), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![VariableName::File.template_value_with_whitespace()], cwd: Some("$ZED_WORKTREE_ROOT".into()), ..TaskTemplate::default() }, // Execute a file as module TaskTemplate { label: format!("run module '{}'", VariableName::File.template_value()), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![ "-m".to_owned(), PYTHON_MODULE_NAME_TASK_VARIABLE.template_value(), ], cwd: Some("$ZED_WORKTREE_ROOT".into()), tags: vec!["python-module-main-method".to_owned()], ..TaskTemplate::default() }, ]; tasks.extend(match test_runner { TestRunner::UNITTEST => { [ // Run tests for an entire file TaskTemplate { label: format!("unittest '{}'", VariableName::File.template_value()), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![ "-m".to_owned(), "unittest".to_owned(), VariableName::File.template_value_with_whitespace(), ], cwd: Some("$ZED_WORKTREE_ROOT".into()), ..TaskTemplate::default() }, // Run test(s) for a specific target within a file TaskTemplate { label: "unittest $ZED_CUSTOM_PYTHON_TEST_TARGET".to_owned(), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![ "-m".to_owned(), "unittest".to_owned(), PYTHON_TEST_TARGET_TASK_VARIABLE.template_value_with_whitespace(), ], tags: vec![ "python-unittest-class".to_owned(), "python-unittest-method".to_owned(), ], cwd: Some("$ZED_WORKTREE_ROOT".into()), ..TaskTemplate::default() }, ] } TestRunner::PYTEST => { [ // Run tests for an entire file TaskTemplate { label: format!("pytest '{}'", VariableName::File.template_value()), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![ "-m".to_owned(), "pytest".to_owned(), VariableName::File.template_value_with_whitespace(), ], cwd: Some("$ZED_WORKTREE_ROOT".into()), ..TaskTemplate::default() },Lines 1–500 of 1312. Load more
use anyhow::{Context as _, ensure}; use anyhow::{Result, anyhow}; use async_trait::async_trait; use collections::HashMap; use gpui::{App, Task}; use gpui::{AsyncApp, SharedString}; use language::Toolchain; use language::ToolchainList; use language::ToolchainLister; use language::language_settings::language_settings; use language::{ContextLocation, LanguageToolchainStore}; use language::{ContextProvider, LspAdapter, LspAdapterDelegate}; use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery}; use lsp::LanguageServerBinary; use lsp::LanguageServerName; use node_runtime::NodeRuntime; use pet_core::Configuration; use pet_core::os_environment::Environment; use pet_core::python_environment::PythonEnvironmentKind; use project::Fs; use project::lsp_store::language_server_settings; use serde_json::{Value, json}; use smol::lock::OnceCell; use std::cmp::Ordering; use parking_lot::Mutex; use std::str::FromStr; use std::{ any::Any, borrow::Cow, ffi::OsString, fmt::Write, fs, io::{self, BufRead}, path::{Path, PathBuf}, sync::Arc, }; use task::{TaskTemplate, TaskTemplates, VariableName}; use util::ResultExt; pub(crate) struct PyprojectTomlManifestProvider; impl ManifestProvider for PyprojectTomlManifestProvider { fn name(&self) -> ManifestName { SharedString::new_static("pyproject.toml").into() } fn search( &self, ManifestQuery { path, depth, delegate, }: ManifestQuery, ) -> Option<Arc<Path>> { for path in path.ancestors().take(depth) { let p = path.join("pyproject.toml"); if delegate.exists(&p, Some(false)) { return Some(path.into()); } } None } } const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js"; const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js"; enum TestRunner { UNITTEST, PYTEST, } impl FromStr for TestRunner { type Err = (); fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { match s { "unittest" => Ok(Self::UNITTEST), "pytest" => Ok(Self::PYTEST), _ => Err(()), } } } fn server_binary_arguments(server_path: &Path) -> Vec<OsString> { vec![server_path.into(), "--stdio".into()] } pub struct PythonLspAdapter { node: NodeRuntime, } impl PythonLspAdapter { const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pyright"); pub fn new(node: NodeRuntime) -> Self { PythonLspAdapter { node } } } #[async_trait(?Send)] impl LspAdapter for PythonLspAdapter { fn name(&self) -> LanguageServerName { Self::SERVER_NAME.clone() } async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, _: Arc<dyn LanguageToolchainStore>, _: &AsyncApp, ) -> Option<LanguageServerBinary> { if let Some(pyright_bin) = delegate.which("pyright-langserver".as_ref()).await { let env = delegate.shell_env().await; Some(LanguageServerBinary { path: pyright_bin, env: Some(env), arguments: vec!["--stdio".into()], }) } else { let node = delegate.which("node".as_ref()).await?; let (node_modules_path, _) = delegate .npm_package_installed_version(Self::SERVER_NAME.as_ref()) .await .log_err()??; let path = node_modules_path.join(NODE_MODULE_RELATIVE_SERVER_PATH); Some(LanguageServerBinary { path: node, env: None, arguments: server_binary_arguments(&path), }) } } async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, ) -> Result<Box<dyn 'static + Any + Send>> { Ok(Box::new( self.node .npm_package_latest_version(Self::SERVER_NAME.as_ref()) .await?, ) as Box<_>) } async fn fetch_server_binary( &self, latest_version: Box<dyn 'static + Send + Any>, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result<LanguageServerBinary> { let latest_version = latest_version.downcast::<String>().unwrap(); let server_path = container_dir.join(SERVER_PATH); self.node .npm_install_packages( &container_dir, &[(Self::SERVER_NAME.as_ref(), latest_version.as_str())], ) .await?; Ok(LanguageServerBinary { path: self.node.binary_path().await?, env: None, arguments: server_binary_arguments(&server_path), }) } async fn check_if_version_installed( &self, version: &(dyn 'static + Send + Any), container_dir: &PathBuf, _: &dyn LspAdapterDelegate, ) -> Option<LanguageServerBinary> { let version = version.downcast_ref::<String>().unwrap(); let server_path = container_dir.join(SERVER_PATH); let should_install_language_server = self .node .should_install_npm_package( Self::SERVER_NAME.as_ref(), &server_path, &container_dir, &version, ) .await; if should_install_language_server { None } else { Some(LanguageServerBinary { path: self.node.binary_path().await.ok()?, env: None, arguments: server_binary_arguments(&server_path), }) } } async fn cached_server_binary( &self, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option<LanguageServerBinary> { get_cached_server_binary(container_dir, &self.node).await } async fn process_completions(&self, items: &mut [lsp::CompletionItem]) { // Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`. // Where `XX` is the sorting category, `YYYY` is based on most recent usage, // and `name` is the symbol name itself. // // Because the symbol name is included, there generally are not ties when // sorting by the `sortText`, so the symbol's fuzzy match score is not taken // into account. Here, we remove the symbol name from the sortText in order // to allow our own fuzzy score to be used to break ties. // // see https://github.com/microsoft/pyright/blob/95ef4e103b9b2f129c9320427e51b73ea7cf78bd/packages/pyright-internal/src/languageService/completionProvider.ts#LL2873 for item in items { let Some(sort_text) = &mut item.sort_text else { continue; }; let mut parts = sort_text.split('.'); let Some(first) = parts.next() else { continue }; let Some(second) = parts.next() else { continue }; let Some(_) = parts.next() else { continue }; sort_text.replace_range(first.len() + second.len() + 1.., ""); } } async fn label_for_completion( &self, item: &lsp::CompletionItem, language: &Arc<language::Language>, ) -> Option<language::CodeLabel> { let label = &item.label; let grammar = language.grammar()?; let highlight_id = match item.kind? { lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?, lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?, lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?, lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?, _ => return None, }; Some(language::CodeLabel { text: label.clone(), runs: vec![(0..label.len(), highlight_id)], filter_range: 0..label.len(), }) } async fn label_for_symbol( &self, name: &str, kind: lsp::SymbolKind, language: &Arc<language::Language>, ) -> Option<language::CodeLabel> { let (text, filter_range, display_range) = match kind { lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => { let text = format!("def {}():\n", name); let filter_range = 4..4 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::CLASS => { let text = format!("class {}:", name); let filter_range = 6..6 + name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } lsp::SymbolKind::CONSTANT => { let text = format!("{} = 0", name); let filter_range = 0..name.len(); let display_range = 0..filter_range.end; (text, filter_range, display_range) } _ => return None, }; Some(language::CodeLabel { runs: language.highlight_text(&text.as_str().into(), display_range.clone()), text: text[display_range].to_string(), filter_range, }) } async fn workspace_configuration( self: Arc<Self>, _: &dyn Fs, adapter: &Arc<dyn LspAdapterDelegate>, toolchains: Arc<dyn LanguageToolchainStore>, cx: &mut AsyncApp, ) -> Result<Value> { let toolchain = toolchains .active_toolchain( adapter.worktree_id(), Arc::from("".as_ref()), LanguageName::new("Python"), cx, ) .await; cx.update(move |cx| { let mut user_settings = language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx) .and_then(|s| s.settings.clone()) .unwrap_or_default(); // If python.pythonPath is not set in user config, do so using our toolchain picker. if let Some(toolchain) = toolchain { if user_settings.is_null() { user_settings = Value::Object(serde_json::Map::default()); } let object = user_settings.as_object_mut().unwrap(); if let Some(python) = object .entry("python") .or_insert(Value::Object(serde_json::Map::default())) .as_object_mut() { python .entry("pythonPath") .or_insert(Value::String(toolchain.path.into())); } } user_settings }) } fn manifest_name(&self) -> Option<ManifestName> { Some(SharedString::new_static("pyproject.toml").into()) } } async fn get_cached_server_binary( container_dir: PathBuf, node: &NodeRuntime, ) -> Option<LanguageServerBinary> { let server_path = container_dir.join(SERVER_PATH); if server_path.exists() { Some(LanguageServerBinary { path: node.binary_path().await.log_err()?, env: None, arguments: server_binary_arguments(&server_path), }) } else { log::error!("missing executable in directory {:?}", server_path); None } } pub(crate) struct PythonContextProvider; const PYTHON_TEST_TARGET_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("PYTHON_TEST_TARGET")); const PYTHON_ACTIVE_TOOLCHAIN_PATH: VariableName = VariableName::Custom(Cow::Borrowed("PYTHON_ACTIVE_ZED_TOOLCHAIN")); const PYTHON_ACTIVE_TOOLCHAIN_PATH_RAW: VariableName = VariableName::Custom(Cow::Borrowed("PYTHON_ACTIVE_ZED_TOOLCHAIN_RAW")); const PYTHON_MODULE_NAME_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("PYTHON_MODULE_NAME")); impl ContextProvider for PythonContextProvider { fn build_context( &self, variables: &task::TaskVariables, location: ContextLocation<'_>, _: Option<HashMap<String, String>>, toolchains: Arc<dyn LanguageToolchainStore>, cx: &mut gpui::App, ) -> Task<Result<task::TaskVariables>> { let test_target = match selected_test_runner(location.file_location.buffer.read(cx).file(), cx) { TestRunner::UNITTEST => self.build_unittest_target(variables), TestRunner::PYTEST => self.build_pytest_target(variables), }; let module_target = self.build_module_target(variables); let worktree_id = location .file_location .buffer .read(cx) .file() .map(|f| f.worktree_id(cx)); cx.spawn(async move |cx| { let raw_toolchain = if let Some(worktree_id) = worktree_id { toolchains .active_toolchain(worktree_id, Arc::from("".as_ref()), "Python".into(), cx) .await .map_or_else( || String::from("python3"), |toolchain| toolchain.path.to_string(), ) } else { String::from("python3") }; let active_toolchain = format!("\"{raw_toolchain}\""); let toolchain = (PYTHON_ACTIVE_TOOLCHAIN_PATH, active_toolchain); let raw_toolchain = (PYTHON_ACTIVE_TOOLCHAIN_PATH_RAW, raw_toolchain); Ok(task::TaskVariables::from_iter( test_target .into_iter() .chain(module_target.into_iter()) .chain([toolchain, raw_toolchain]), )) }) } fn associated_tasks( &self, file: Option<Arc<dyn language::File>>, cx: &App, ) -> Option<TaskTemplates> { let test_runner = selected_test_runner(file.as_ref(), cx); let mut tasks = vec![ // Execute a selection TaskTemplate { label: "execute selection".to_owned(), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![ "-c".to_owned(), VariableName::SelectedText.template_value_with_whitespace(), ], cwd: Some("$ZED_WORKTREE_ROOT".into()), ..TaskTemplate::default() }, // Execute an entire file TaskTemplate { label: format!("run '{}'", VariableName::File.template_value()), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![VariableName::File.template_value_with_whitespace()], cwd: Some("$ZED_WORKTREE_ROOT".into()), ..TaskTemplate::default() }, // Execute a file as module TaskTemplate { label: format!("run module '{}'", VariableName::File.template_value()), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![ "-m".to_owned(), PYTHON_MODULE_NAME_TASK_VARIABLE.template_value(), ], cwd: Some("$ZED_WORKTREE_ROOT".into()), tags: vec!["python-module-main-method".to_owned()], ..TaskTemplate::default() }, ]; tasks.extend(match test_runner { TestRunner::UNITTEST => { [ // Run tests for an entire file TaskTemplate { label: format!("unittest '{}'", VariableName::File.template_value()), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![ "-m".to_owned(), "unittest".to_owned(), VariableName::File.template_value_with_whitespace(), ], cwd: Some("$ZED_WORKTREE_ROOT".into()), ..TaskTemplate::default() }, // Run test(s) for a specific target within a file TaskTemplate { label: "unittest $ZED_CUSTOM_PYTHON_TEST_TARGET".to_owned(), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![ "-m".to_owned(), "unittest".to_owned(), PYTHON_TEST_TARGET_TASK_VARIABLE.template_value_with_whitespace(), ], tags: vec![ "python-unittest-class".to_owned(), "python-unittest-method".to_owned(), ], cwd: Some("$ZED_WORKTREE_ROOT".into()), ..TaskTemplate::default() }, ] } TestRunner::PYTEST => { [ // Run tests for an entire file TaskTemplate { label: format!("pytest '{}'", VariableName::File.template_value()), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![ "-m".to_owned(), "pytest".to_owned(), VariableName::File.template_value_with_whitespace(), ], cwd: Some("$ZED_WORKTREE_ROOT".into()), ..TaskTemplate::default() },