python.rs

  1use anyhow::ensure;
  2use anyhow::{anyhow, Result};
  3use async_trait::async_trait;
  4use collections::HashMap;
  5use gpui::AsyncAppContext;
  6use gpui::{AppContext, Task};
  7use language::LanguageName;
  8use language::LanguageToolchainStore;
  9use language::Toolchain;
 10use language::ToolchainList;
 11use language::ToolchainLister;
 12use language::{ContextProvider, LspAdapter, LspAdapterDelegate};
 13use lsp::LanguageServerBinary;
 14use lsp::LanguageServerName;
 15use node_runtime::NodeRuntime;
 16use pet_core::os_environment::Environment;
 17use pet_core::python_environment::PythonEnvironmentKind;
 18use pet_core::Configuration;
 19use project::lsp_store::language_server_settings;
 20use serde_json::{json, Value};
 21use smol::{lock::OnceCell, process::Command};
 22use std::cmp::Ordering;
 23
 24use std::sync::Mutex;
 25use std::{
 26    any::Any,
 27    borrow::Cow,
 28    ffi::OsString,
 29    path::{Path, PathBuf},
 30    sync::Arc,
 31};
 32use task::{TaskTemplate, TaskTemplates, VariableName};
 33use util::ResultExt;
 34
 35const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js";
 36const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js";
 37
 38fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 39    vec![server_path.into(), "--stdio".into()]
 40}
 41
 42pub struct PythonLspAdapter {
 43    node: NodeRuntime,
 44}
 45
 46impl PythonLspAdapter {
 47    const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pyright");
 48
 49    pub fn new(node: NodeRuntime) -> Self {
 50        PythonLspAdapter { node }
 51    }
 52}
 53
 54#[async_trait(?Send)]
 55impl LspAdapter for PythonLspAdapter {
 56    fn name(&self) -> LanguageServerName {
 57        Self::SERVER_NAME.clone()
 58    }
 59
 60    async fn check_if_user_installed(
 61        &self,
 62        delegate: &dyn LspAdapterDelegate,
 63        _: &AsyncAppContext,
 64    ) -> Option<LanguageServerBinary> {
 65        let node = delegate.which("node".as_ref()).await?;
 66        let (node_modules_path, _) = delegate
 67            .npm_package_installed_version(Self::SERVER_NAME.as_ref())
 68            .await
 69            .log_err()??;
 70
 71        let path = node_modules_path.join(NODE_MODULE_RELATIVE_SERVER_PATH);
 72
 73        Some(LanguageServerBinary {
 74            path: node,
 75            env: None,
 76            arguments: server_binary_arguments(&path),
 77        })
 78    }
 79
 80    async fn fetch_latest_server_version(
 81        &self,
 82        _: &dyn LspAdapterDelegate,
 83    ) -> Result<Box<dyn 'static + Any + Send>> {
 84        Ok(Box::new(
 85            self.node
 86                .npm_package_latest_version(Self::SERVER_NAME.as_ref())
 87                .await?,
 88        ) as Box<_>)
 89    }
 90
 91    async fn fetch_server_binary(
 92        &self,
 93        latest_version: Box<dyn 'static + Send + Any>,
 94        container_dir: PathBuf,
 95        _: &dyn LspAdapterDelegate,
 96    ) -> Result<LanguageServerBinary> {
 97        let latest_version = latest_version.downcast::<String>().unwrap();
 98        let server_path = container_dir.join(SERVER_PATH);
 99
100        let should_install_language_server = self
101            .node
102            .should_install_npm_package(
103                Self::SERVER_NAME.as_ref(),
104                &server_path,
105                &container_dir,
106                &latest_version,
107            )
108            .await;
109
110        if should_install_language_server {
111            self.node
112                .npm_install_packages(
113                    &container_dir,
114                    &[(Self::SERVER_NAME.as_ref(), latest_version.as_str())],
115                )
116                .await?;
117        }
118
119        Ok(LanguageServerBinary {
120            path: self.node.binary_path().await?,
121            env: None,
122            arguments: server_binary_arguments(&server_path),
123        })
124    }
125
126    async fn cached_server_binary(
127        &self,
128        container_dir: PathBuf,
129        _: &dyn LspAdapterDelegate,
130    ) -> Option<LanguageServerBinary> {
131        get_cached_server_binary(container_dir, &self.node).await
132    }
133
134    async fn process_completions(&self, items: &mut [lsp::CompletionItem]) {
135        // Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`.
136        // Where `XX` is the sorting category, `YYYY` is based on most recent usage,
137        // and `name` is the symbol name itself.
138        //
139        // Because the symbol name is included, there generally are not ties when
140        // sorting by the `sortText`, so the symbol's fuzzy match score is not taken
141        // into account. Here, we remove the symbol name from the sortText in order
142        // to allow our own fuzzy score to be used to break ties.
143        //
144        // see https://github.com/microsoft/pyright/blob/95ef4e103b9b2f129c9320427e51b73ea7cf78bd/packages/pyright-internal/src/languageService/completionProvider.ts#LL2873
145        for item in items {
146            let Some(sort_text) = &mut item.sort_text else {
147                continue;
148            };
149            let mut parts = sort_text.split('.');
150            let Some(first) = parts.next() else { continue };
151            let Some(second) = parts.next() else { continue };
152            let Some(_) = parts.next() else { continue };
153            sort_text.replace_range(first.len() + second.len() + 1.., "");
154        }
155    }
156
157    async fn label_for_completion(
158        &self,
159        item: &lsp::CompletionItem,
160        language: &Arc<language::Language>,
161    ) -> Option<language::CodeLabel> {
162        let label = &item.label;
163        let grammar = language.grammar()?;
164        let highlight_id = match item.kind? {
165            lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?,
166            lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?,
167            lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?,
168            lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?,
169            _ => return None,
170        };
171        Some(language::CodeLabel {
172            text: label.clone(),
173            runs: vec![(0..label.len(), highlight_id)],
174            filter_range: 0..label.len(),
175        })
176    }
177
178    async fn label_for_symbol(
179        &self,
180        name: &str,
181        kind: lsp::SymbolKind,
182        language: &Arc<language::Language>,
183    ) -> Option<language::CodeLabel> {
184        let (text, filter_range, display_range) = match kind {
185            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
186                let text = format!("def {}():\n", name);
187                let filter_range = 4..4 + name.len();
188                let display_range = 0..filter_range.end;
189                (text, filter_range, display_range)
190            }
191            lsp::SymbolKind::CLASS => {
192                let text = format!("class {}:", name);
193                let filter_range = 6..6 + name.len();
194                let display_range = 0..filter_range.end;
195                (text, filter_range, display_range)
196            }
197            lsp::SymbolKind::CONSTANT => {
198                let text = format!("{} = 0", name);
199                let filter_range = 0..name.len();
200                let display_range = 0..filter_range.end;
201                (text, filter_range, display_range)
202            }
203            _ => return None,
204        };
205
206        Some(language::CodeLabel {
207            runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
208            text: text[display_range].to_string(),
209            filter_range,
210        })
211    }
212
213    async fn workspace_configuration(
214        self: Arc<Self>,
215        adapter: &Arc<dyn LspAdapterDelegate>,
216        toolchains: Arc<dyn LanguageToolchainStore>,
217        cx: &mut AsyncAppContext,
218    ) -> Result<Value> {
219        let toolchain = toolchains
220            .active_toolchain(adapter.worktree_id(), LanguageName::new("Python"), cx)
221            .await;
222        cx.update(move |cx| {
223            let mut user_settings =
224                language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
225                    .and_then(|s| s.settings.clone())
226                    .unwrap_or_default();
227
228            // If python.pythonPath is not set in user config, do so using our toolchain picker.
229            if let Some(toolchain) = toolchain {
230                if user_settings.is_null() {
231                    user_settings = Value::Object(serde_json::Map::default());
232                }
233                let object = user_settings.as_object_mut().unwrap();
234                if let Some(python) = object
235                    .entry("python")
236                    .or_insert(Value::Object(serde_json::Map::default()))
237                    .as_object_mut()
238                {
239                    python
240                        .entry("pythonPath")
241                        .or_insert(Value::String(toolchain.path.into()));
242                }
243            }
244            user_settings
245        })
246    }
247}
248
249async fn get_cached_server_binary(
250    container_dir: PathBuf,
251    node: &NodeRuntime,
252) -> Option<LanguageServerBinary> {
253    let server_path = container_dir.join(SERVER_PATH);
254    if server_path.exists() {
255        Some(LanguageServerBinary {
256            path: node.binary_path().await.log_err()?,
257            env: None,
258            arguments: server_binary_arguments(&server_path),
259        })
260    } else {
261        log::error!("missing executable in directory {:?}", server_path);
262        None
263    }
264}
265
266pub(crate) struct PythonContextProvider;
267
268const PYTHON_UNITTEST_TARGET_TASK_VARIABLE: VariableName =
269    VariableName::Custom(Cow::Borrowed("PYTHON_UNITTEST_TARGET"));
270
271const PYTHON_ACTIVE_TOOLCHAIN_PATH: VariableName =
272    VariableName::Custom(Cow::Borrowed("PYTHON_ACTIVE_ZED_TOOLCHAIN"));
273impl ContextProvider for PythonContextProvider {
274    fn build_context(
275        &self,
276        variables: &task::TaskVariables,
277        location: &project::Location,
278        _: Option<HashMap<String, String>>,
279        toolchains: Arc<dyn LanguageToolchainStore>,
280        cx: &mut gpui::AppContext,
281    ) -> Task<Result<task::TaskVariables>> {
282        let python_module_name = python_module_name_from_relative_path(
283            variables.get(&VariableName::RelativeFile).unwrap_or(""),
284        );
285        let unittest_class_name =
286            variables.get(&VariableName::Custom(Cow::Borrowed("_unittest_class_name")));
287        let unittest_method_name = variables.get(&VariableName::Custom(Cow::Borrowed(
288            "_unittest_method_name",
289        )));
290
291        let unittest_target_str = match (unittest_class_name, unittest_method_name) {
292            (Some(class_name), Some(method_name)) => {
293                format!("{}.{}.{}", python_module_name, class_name, method_name)
294            }
295            (Some(class_name), None) => format!("{}.{}", python_module_name, class_name),
296            (None, None) => python_module_name,
297            (None, Some(_)) => return Task::ready(Ok(task::TaskVariables::default())), // should never happen, a TestCase class is the unit of testing
298        };
299
300        let unittest_target = (
301            PYTHON_UNITTEST_TARGET_TASK_VARIABLE.clone(),
302            unittest_target_str,
303        );
304        let worktree_id = location.buffer.read(cx).file().map(|f| f.worktree_id(cx));
305        cx.spawn(move |mut cx| async move {
306            let active_toolchain = if let Some(worktree_id) = worktree_id {
307                toolchains
308                    .active_toolchain(worktree_id, "Python".into(), &mut cx)
309                    .await
310                    .map_or_else(|| "python3".to_owned(), |toolchain| toolchain.path.into())
311            } else {
312                String::from("python3")
313            };
314            let toolchain = (PYTHON_ACTIVE_TOOLCHAIN_PATH, active_toolchain);
315            Ok(task::TaskVariables::from_iter([unittest_target, toolchain]))
316        })
317    }
318
319    fn associated_tasks(
320        &self,
321        _: Option<Arc<dyn language::File>>,
322        _: &AppContext,
323    ) -> Option<TaskTemplates> {
324        Some(TaskTemplates(vec![
325            TaskTemplate {
326                label: "execute selection".to_owned(),
327                command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
328                args: vec!["-c".to_owned(), VariableName::SelectedText.template_value()],
329                ..TaskTemplate::default()
330            },
331            TaskTemplate {
332                label: format!("run '{}'", VariableName::File.template_value()),
333                command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
334                args: vec![VariableName::File.template_value()],
335                ..TaskTemplate::default()
336            },
337            TaskTemplate {
338                label: format!("unittest '{}'", VariableName::File.template_value()),
339                command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
340                args: vec![
341                    "-m".to_owned(),
342                    "unittest".to_owned(),
343                    VariableName::File.template_value(),
344                ],
345                ..TaskTemplate::default()
346            },
347            TaskTemplate {
348                label: "unittest $ZED_CUSTOM_PYTHON_UNITTEST_TARGET".to_owned(),
349                command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
350                args: vec![
351                    "-m".to_owned(),
352                    "unittest".to_owned(),
353                    "$ZED_CUSTOM_PYTHON_UNITTEST_TARGET".to_owned(),
354                ],
355                tags: vec![
356                    "python-unittest-class".to_owned(),
357                    "python-unittest-method".to_owned(),
358                ],
359                ..TaskTemplate::default()
360            },
361        ]))
362    }
363}
364
365fn python_module_name_from_relative_path(relative_path: &str) -> String {
366    let path_with_dots = relative_path.replace('/', ".");
367    path_with_dots
368        .strip_suffix(".py")
369        .unwrap_or(&path_with_dots)
370        .to_string()
371}
372
373#[derive(Default)]
374pub(crate) struct PythonToolchainProvider {}
375
376static ENV_PRIORITY_LIST: &'static [PythonEnvironmentKind] = &[
377    // Prioritize non-Conda environments.
378    PythonEnvironmentKind::Poetry,
379    PythonEnvironmentKind::Pipenv,
380    PythonEnvironmentKind::VirtualEnvWrapper,
381    PythonEnvironmentKind::Venv,
382    PythonEnvironmentKind::VirtualEnv,
383    PythonEnvironmentKind::Conda,
384    PythonEnvironmentKind::Pyenv,
385    PythonEnvironmentKind::GlobalPaths,
386    PythonEnvironmentKind::Homebrew,
387];
388
389fn env_priority(kind: Option<PythonEnvironmentKind>) -> usize {
390    if let Some(kind) = kind {
391        ENV_PRIORITY_LIST
392            .iter()
393            .position(|blessed_env| blessed_env == &kind)
394            .unwrap_or(ENV_PRIORITY_LIST.len())
395    } else {
396        // Unknown toolchains are less useful than non-blessed ones.
397        ENV_PRIORITY_LIST.len() + 1
398    }
399}
400
401#[async_trait(?Send)]
402impl ToolchainLister for PythonToolchainProvider {
403    async fn list(
404        &self,
405        worktree_root: PathBuf,
406        project_env: Option<HashMap<String, String>>,
407    ) -> ToolchainList {
408        let env = project_env.unwrap_or_default();
409        let environment = EnvironmentApi::from_env(&env);
410        let locators = pet::locators::create_locators(
411            Arc::new(pet_conda::Conda::from(&environment)),
412            Arc::new(pet_poetry::Poetry::from(&environment)),
413            &environment,
414        );
415        let mut config = Configuration::default();
416        config.workspace_directories = Some(vec![worktree_root]);
417        for locator in locators.iter() {
418            locator.configure(&config);
419        }
420
421        let reporter = pet_reporter::collect::create_reporter();
422        pet::find::find_and_report_envs(&reporter, config, &locators, &environment, None);
423
424        let mut toolchains = reporter
425            .environments
426            .lock()
427            .ok()
428            .map_or(Vec::new(), |mut guard| std::mem::take(&mut guard));
429
430        toolchains.sort_by(|lhs, rhs| {
431            env_priority(lhs.kind)
432                .cmp(&env_priority(rhs.kind))
433                .then_with(|| {
434                    if lhs.kind == Some(PythonEnvironmentKind::Conda) {
435                        environment
436                            .get_env_var("CONDA_PREFIX".to_string())
437                            .map(|conda_prefix| {
438                                let is_match = |exe: &Option<PathBuf>| {
439                                    exe.as_ref().map_or(false, |e| e.starts_with(&conda_prefix))
440                                };
441                                match (is_match(&lhs.executable), is_match(&rhs.executable)) {
442                                    (true, false) => Ordering::Less,
443                                    (false, true) => Ordering::Greater,
444                                    _ => Ordering::Equal,
445                                }
446                            })
447                            .unwrap_or(Ordering::Equal)
448                    } else {
449                        Ordering::Equal
450                    }
451                })
452                .then_with(|| lhs.executable.cmp(&rhs.executable))
453        });
454
455        let mut toolchains: Vec<_> = toolchains
456            .into_iter()
457            .filter_map(|toolchain| {
458                let name = if let Some(version) = &toolchain.version {
459                    format!("Python {version} ({:?})", toolchain.kind?)
460                } else {
461                    format!("{:?}", toolchain.kind?)
462                }
463                .into();
464                Some(Toolchain {
465                    name,
466                    path: toolchain.executable?.to_str()?.to_owned().into(),
467                    language_name: LanguageName::new("Python"),
468                })
469            })
470            .collect();
471        toolchains.dedup();
472        ToolchainList {
473            toolchains,
474            default: None,
475            groups: Default::default(),
476        }
477    }
478}
479
480pub struct EnvironmentApi<'a> {
481    global_search_locations: Arc<Mutex<Vec<PathBuf>>>,
482    project_env: &'a HashMap<String, String>,
483    pet_env: pet_core::os_environment::EnvironmentApi,
484}
485
486impl<'a> EnvironmentApi<'a> {
487    pub fn from_env(project_env: &'a HashMap<String, String>) -> Self {
488        let paths = project_env
489            .get("PATH")
490            .map(|p| std::env::split_paths(p).collect())
491            .unwrap_or_default();
492
493        EnvironmentApi {
494            global_search_locations: Arc::new(Mutex::new(paths)),
495            project_env,
496            pet_env: pet_core::os_environment::EnvironmentApi::new(),
497        }
498    }
499
500    fn user_home(&self) -> Option<PathBuf> {
501        self.project_env
502            .get("HOME")
503            .or_else(|| self.project_env.get("USERPROFILE"))
504            .map(|home| pet_fs::path::norm_case(PathBuf::from(home)))
505            .or_else(|| self.pet_env.get_user_home())
506    }
507}
508
509impl<'a> pet_core::os_environment::Environment for EnvironmentApi<'a> {
510    fn get_user_home(&self) -> Option<PathBuf> {
511        self.user_home()
512    }
513
514    fn get_root(&self) -> Option<PathBuf> {
515        None
516    }
517
518    fn get_env_var(&self, key: String) -> Option<String> {
519        self.project_env
520            .get(&key)
521            .cloned()
522            .or_else(|| self.pet_env.get_env_var(key))
523    }
524
525    fn get_know_global_search_locations(&self) -> Vec<PathBuf> {
526        if self.global_search_locations.lock().unwrap().is_empty() {
527            let mut paths =
528                std::env::split_paths(&self.get_env_var("PATH".to_string()).unwrap_or_default())
529                    .collect::<Vec<PathBuf>>();
530
531            log::trace!("Env PATH: {:?}", paths);
532            for p in self.pet_env.get_know_global_search_locations() {
533                if !paths.contains(&p) {
534                    paths.push(p);
535                }
536            }
537
538            let mut paths = paths
539                .into_iter()
540                .filter(|p| p.exists())
541                .collect::<Vec<PathBuf>>();
542
543            self.global_search_locations
544                .lock()
545                .unwrap()
546                .append(&mut paths);
547        }
548        self.global_search_locations.lock().unwrap().clone()
549    }
550}
551
552pub(crate) struct PyLspAdapter {
553    python_venv_base: OnceCell<Result<Arc<Path>, String>>,
554}
555impl PyLspAdapter {
556    const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pylsp");
557    pub(crate) fn new() -> Self {
558        Self {
559            python_venv_base: OnceCell::new(),
560        }
561    }
562    async fn ensure_venv(delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>> {
563        let python_path = Self::find_base_python(delegate)
564            .await
565            .ok_or_else(|| anyhow!("Could not find Python installation for PyLSP"))?;
566        let work_dir = delegate
567            .language_server_download_dir(&Self::SERVER_NAME)
568            .await
569            .ok_or_else(|| anyhow!("Could not get working directory for PyLSP"))?;
570        let mut path = PathBuf::from(work_dir.as_ref());
571        path.push("pylsp-venv");
572        if !path.exists() {
573            Command::new(python_path)
574                .arg("-m")
575                .arg("venv")
576                .arg("pylsp-venv")
577                .current_dir(work_dir)
578                .spawn()?
579                .output()
580                .await?;
581        }
582
583        Ok(path.into())
584    }
585    // Find "baseline", user python version from which we'll create our own venv.
586    async fn find_base_python(delegate: &dyn LspAdapterDelegate) -> Option<PathBuf> {
587        for path in ["python3", "python"] {
588            if let Some(path) = delegate.which(path.as_ref()).await {
589                return Some(path);
590            }
591        }
592        None
593    }
594
595    async fn base_venv(&self, delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>, String> {
596        self.python_venv_base
597            .get_or_init(move || async move {
598                Self::ensure_venv(delegate)
599                    .await
600                    .map_err(|e| format!("{e}"))
601            })
602            .await
603            .clone()
604    }
605}
606
607#[async_trait(?Send)]
608impl LspAdapter for PyLspAdapter {
609    fn name(&self) -> LanguageServerName {
610        Self::SERVER_NAME.clone()
611    }
612
613    async fn check_if_user_installed(
614        &self,
615        _: &dyn LspAdapterDelegate,
616        _: &AsyncAppContext,
617    ) -> Option<LanguageServerBinary> {
618        // We don't support user-provided pylsp, as global packages are discouraged in Python ecosystem.
619        None
620    }
621
622    async fn fetch_latest_server_version(
623        &self,
624        _: &dyn LspAdapterDelegate,
625    ) -> Result<Box<dyn 'static + Any + Send>> {
626        // let uri = "https://pypi.org/pypi/python-lsp-server/json";
627        // let mut root_manifest = delegate
628        //     .http_client()
629        //     .get(&uri, Default::default(), true)
630        //     .await?;
631        // let mut body = Vec::new();
632        // root_manifest.body_mut().read_to_end(&mut body).await?;
633        // let as_str = String::from_utf8(body)?;
634        // let json = serde_json::Value::from_str(&as_str)?;
635        // let latest_version = json
636        //     .get("info")
637        //     .and_then(|info| info.get("version"))
638        //     .and_then(|version| version.as_str().map(ToOwned::to_owned))
639        //     .ok_or_else(|| {
640        //         anyhow!("PyPI response did not contain version info for python-language-server")
641        //     })?;
642        Ok(Box::new(()) as Box<_>)
643    }
644
645    async fn fetch_server_binary(
646        &self,
647        _: Box<dyn 'static + Send + Any>,
648        _: PathBuf,
649        delegate: &dyn LspAdapterDelegate,
650    ) -> Result<LanguageServerBinary> {
651        let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?;
652        let pip_path = venv.join("bin").join("pip3");
653        ensure!(
654            Command::new(pip_path.as_path())
655                .arg("install")
656                .arg("python-lsp-server")
657                .output()
658                .await?
659                .status
660                .success(),
661            "python-lsp-server installation failed"
662        );
663        ensure!(
664            Command::new(pip_path.as_path())
665                .arg("install")
666                .arg("python-lsp-server[all]")
667                .output()
668                .await?
669                .status
670                .success(),
671            "python-lsp-server[all] installation failed"
672        );
673        ensure!(
674            Command::new(pip_path)
675                .arg("install")
676                .arg("pylsp-mypy")
677                .output()
678                .await?
679                .status
680                .success(),
681            "pylsp-mypy installation failed"
682        );
683        let pylsp = venv.join("bin").join("pylsp");
684        Ok(LanguageServerBinary {
685            path: pylsp,
686            env: None,
687            arguments: vec![],
688        })
689    }
690
691    async fn cached_server_binary(
692        &self,
693        _: PathBuf,
694        delegate: &dyn LspAdapterDelegate,
695    ) -> Option<LanguageServerBinary> {
696        let venv = self.base_venv(delegate).await.ok()?;
697        let pylsp = venv.join("bin").join("pylsp");
698        Some(LanguageServerBinary {
699            path: pylsp,
700            env: None,
701            arguments: vec![],
702        })
703    }
704
705    async fn process_completions(&self, _items: &mut [lsp::CompletionItem]) {}
706
707    async fn label_for_completion(
708        &self,
709        item: &lsp::CompletionItem,
710        language: &Arc<language::Language>,
711    ) -> Option<language::CodeLabel> {
712        let label = &item.label;
713        let grammar = language.grammar()?;
714        let highlight_id = match item.kind? {
715            lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?,
716            lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?,
717            lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?,
718            lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?,
719            _ => return None,
720        };
721        Some(language::CodeLabel {
722            text: label.clone(),
723            runs: vec![(0..label.len(), highlight_id)],
724            filter_range: 0..label.len(),
725        })
726    }
727
728    async fn label_for_symbol(
729        &self,
730        name: &str,
731        kind: lsp::SymbolKind,
732        language: &Arc<language::Language>,
733    ) -> Option<language::CodeLabel> {
734        let (text, filter_range, display_range) = match kind {
735            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
736                let text = format!("def {}():\n", name);
737                let filter_range = 4..4 + name.len();
738                let display_range = 0..filter_range.end;
739                (text, filter_range, display_range)
740            }
741            lsp::SymbolKind::CLASS => {
742                let text = format!("class {}:", name);
743                let filter_range = 6..6 + name.len();
744                let display_range = 0..filter_range.end;
745                (text, filter_range, display_range)
746            }
747            lsp::SymbolKind::CONSTANT => {
748                let text = format!("{} = 0", name);
749                let filter_range = 0..name.len();
750                let display_range = 0..filter_range.end;
751                (text, filter_range, display_range)
752            }
753            _ => return None,
754        };
755
756        Some(language::CodeLabel {
757            runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
758            text: text[display_range].to_string(),
759            filter_range,
760        })
761    }
762
763    async fn workspace_configuration(
764        self: Arc<Self>,
765        adapter: &Arc<dyn LspAdapterDelegate>,
766        toolchains: Arc<dyn LanguageToolchainStore>,
767        cx: &mut AsyncAppContext,
768    ) -> Result<Value> {
769        let toolchain = toolchains
770            .active_toolchain(adapter.worktree_id(), LanguageName::new("Python"), cx)
771            .await;
772        cx.update(move |cx| {
773            let mut user_settings =
774                language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
775                    .and_then(|s| s.settings.clone())
776                    .unwrap_or_else(|| {
777                        json!({
778                            "plugins": {
779                                "rope_autoimport": {"enabled": true},
780                                "mypy": {"enabled": true}
781                            }
782                        })
783                    });
784
785            // If python.pythonPath is not set in user config, do so using our toolchain picker.
786            if let Some(toolchain) = toolchain {
787                if user_settings.is_null() {
788                    user_settings = Value::Object(serde_json::Map::default());
789                }
790                let object = user_settings.as_object_mut().unwrap();
791                if let Some(python) = object
792                    .entry("plugins")
793                    .or_insert(Value::Object(serde_json::Map::default()))
794                    .as_object_mut()
795                {
796                    if let Some(jedi) = python
797                        .entry("jedi")
798                        .or_insert(Value::Object(serde_json::Map::default()))
799                        .as_object_mut()
800                    {
801                        jedi.insert(
802                            "environment".to_string(),
803                            Value::String(toolchain.path.clone().into()),
804                        );
805                    }
806                    if let Some(pylint) = python
807                        .entry("mypy")
808                        .or_insert(Value::Object(serde_json::Map::default()))
809                        .as_object_mut()
810                    {
811                        pylint.insert(
812                            "overrides".to_string(),
813                            Value::Array(vec![
814                                Value::String("--python-executable".into()),
815                                Value::String(toolchain.path.into()),
816                            ]),
817                        );
818                    }
819                }
820            }
821            user_settings = Value::Object(serde_json::Map::from_iter([(
822                "pylsp".to_string(),
823                user_settings,
824            )]));
825
826            user_settings
827        })
828    }
829}
830
831#[cfg(test)]
832mod tests {
833    use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext};
834    use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
835    use settings::SettingsStore;
836    use std::num::NonZeroU32;
837
838    #[gpui::test]
839    async fn test_python_autoindent(cx: &mut TestAppContext) {
840        cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
841        let language = crate::language("python", tree_sitter_python::LANGUAGE.into());
842        cx.update(|cx| {
843            let test_settings = SettingsStore::test(cx);
844            cx.set_global(test_settings);
845            language::init(cx);
846            cx.update_global::<SettingsStore, _>(|store, cx| {
847                store.update_user_settings::<AllLanguageSettings>(cx, |s| {
848                    s.defaults.tab_size = NonZeroU32::new(2);
849                });
850            });
851        });
852
853        cx.new_model(|cx| {
854            let mut buffer = Buffer::local("", cx).with_language(language, cx);
855            let append = |buffer: &mut Buffer, text: &str, cx: &mut ModelContext<Buffer>| {
856                let ix = buffer.len();
857                buffer.edit([(ix..ix, text)], Some(AutoindentMode::EachLine), cx);
858            };
859
860            // indent after "def():"
861            append(&mut buffer, "def a():\n", cx);
862            assert_eq!(buffer.text(), "def a():\n  ");
863
864            // preserve indent after blank line
865            append(&mut buffer, "\n  ", cx);
866            assert_eq!(buffer.text(), "def a():\n  \n  ");
867
868            // indent after "if"
869            append(&mut buffer, "if a:\n  ", cx);
870            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    ");
871
872            // preserve indent after statement
873            append(&mut buffer, "b()\n", cx);
874            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    b()\n    ");
875
876            // preserve indent after statement
877            append(&mut buffer, "else", cx);
878            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    b()\n    else");
879
880            // dedent "else""
881            append(&mut buffer, ":", cx);
882            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    b()\n  else:");
883
884            // indent lines after else
885            append(&mut buffer, "\n", cx);
886            assert_eq!(
887                buffer.text(),
888                "def a():\n  \n  if a:\n    b()\n  else:\n    "
889            );
890
891            // indent after an open paren. the closing  paren is not indented
892            // because there is another token before it on the same line.
893            append(&mut buffer, "foo(\n1)", cx);
894            assert_eq!(
895                buffer.text(),
896                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n      1)"
897            );
898
899            // dedent the closing paren if it is shifted to the beginning of the line
900            let argument_ix = buffer.text().find('1').unwrap();
901            buffer.edit(
902                [(argument_ix..argument_ix + 1, "")],
903                Some(AutoindentMode::EachLine),
904                cx,
905            );
906            assert_eq!(
907                buffer.text(),
908                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )"
909            );
910
911            // preserve indent after the close paren
912            append(&mut buffer, "\n", cx);
913            assert_eq!(
914                buffer.text(),
915                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )\n    "
916            );
917
918            // manually outdent the last line
919            let end_whitespace_ix = buffer.len() - 4;
920            buffer.edit(
921                [(end_whitespace_ix..buffer.len(), "")],
922                Some(AutoindentMode::EachLine),
923                cx,
924            );
925            assert_eq!(
926                buffer.text(),
927                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )\n"
928            );
929
930            // preserve the newly reduced indentation on the next newline
931            append(&mut buffer, "\n", cx);
932            assert_eq!(
933                buffer.text(),
934                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )\n\n"
935            );
936
937            // reset to a simple if statement
938            buffer.edit([(0..buffer.len(), "if a:\n  b(\n  )")], None, cx);
939
940            // dedent "else" on the line after a closing paren
941            append(&mut buffer, "\n  else:\n", cx);
942            assert_eq!(buffer.text(), "if a:\n  b(\n  )\nelse:\n  ");
943
944            buffer
945        });
946    }
947}