python.rs

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