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};
 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
270const PYTHON_ACTIVE_TOOLCHAIN_PATH: VariableName =
271    VariableName::Custom(Cow::Borrowed("PYTHON_ACTIVE_ZED_TOOLCHAIN"));
272impl ContextProvider for PythonContextProvider {
273    fn build_context(
274        &self,
275        variables: &task::TaskVariables,
276        location: &project::Location,
277        _: Option<HashMap<String, String>>,
278        toolchains: Arc<dyn LanguageToolchainStore>,
279        cx: &mut gpui::AppContext,
280    ) -> Task<Result<task::TaskVariables>> {
281        let python_module_name = python_module_name_from_relative_path(
282            variables.get(&VariableName::RelativeFile).unwrap_or(""),
283        );
284        let unittest_class_name =
285            variables.get(&VariableName::Custom(Cow::Borrowed("_unittest_class_name")));
286        let unittest_method_name = variables.get(&VariableName::Custom(Cow::Borrowed(
287            "_unittest_method_name",
288        )));
289
290        let unittest_target_str = match (unittest_class_name, unittest_method_name) {
291            (Some(class_name), Some(method_name)) => {
292                format!("{}.{}.{}", python_module_name, class_name, method_name)
293            }
294            (Some(class_name), None) => format!("{}.{}", python_module_name, class_name),
295            (None, None) => python_module_name,
296            (None, Some(_)) => return Task::ready(Ok(task::TaskVariables::default())), // should never happen, a TestCase class is the unit of testing
297        };
298
299        let unittest_target = (
300            PYTHON_UNITTEST_TARGET_TASK_VARIABLE.clone(),
301            unittest_target_str,
302        );
303        let worktree_id = location.buffer.read(cx).file().map(|f| f.worktree_id(cx));
304        cx.spawn(move |mut cx| async move {
305            let active_toolchain = if let Some(worktree_id) = worktree_id {
306                toolchains
307                    .active_toolchain(worktree_id, "Python".into(), &mut cx)
308                    .await
309                    .map_or_else(|| "python3".to_owned(), |toolchain| toolchain.path.into())
310            } else {
311                String::from("python3")
312            };
313            let toolchain = (PYTHON_ACTIVE_TOOLCHAIN_PATH, active_toolchain);
314            Ok(task::TaskVariables::from_iter([unittest_target, toolchain]))
315        })
316    }
317
318    fn associated_tasks(
319        &self,
320        _: Option<Arc<dyn language::File>>,
321        _: &AppContext,
322    ) -> Option<TaskTemplates> {
323        Some(TaskTemplates(vec![
324            TaskTemplate {
325                label: "execute selection".to_owned(),
326                command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
327                args: vec!["-c".to_owned(), VariableName::SelectedText.template_value()],
328                ..TaskTemplate::default()
329            },
330            TaskTemplate {
331                label: format!("run '{}'", VariableName::File.template_value()),
332                command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
333                args: vec![VariableName::File.template_value()],
334                ..TaskTemplate::default()
335            },
336            TaskTemplate {
337                label: format!("unittest '{}'", VariableName::File.template_value()),
338                command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
339                args: vec![
340                    "-m".to_owned(),
341                    "unittest".to_owned(),
342                    VariableName::File.template_value(),
343                ],
344                ..TaskTemplate::default()
345            },
346            TaskTemplate {
347                label: "unittest $ZED_CUSTOM_PYTHON_UNITTEST_TARGET".to_owned(),
348                command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
349                args: vec![
350                    "-m".to_owned(),
351                    "unittest".to_owned(),
352                    "$ZED_CUSTOM_PYTHON_UNITTEST_TARGET".to_owned(),
353                ],
354                tags: vec![
355                    "python-unittest-class".to_owned(),
356                    "python-unittest-method".to_owned(),
357                ],
358                ..TaskTemplate::default()
359            },
360        ]))
361    }
362}
363
364fn python_module_name_from_relative_path(relative_path: &str) -> String {
365    let path_with_dots = relative_path.replace('/', ".");
366    path_with_dots
367        .strip_suffix(".py")
368        .unwrap_or(&path_with_dots)
369        .to_string()
370}
371
372#[derive(Default)]
373pub(crate) struct PythonToolchainProvider {}
374
375static ENV_PRIORITY_LIST: &'static [PythonEnvironmentKind] = &[
376    // Prioritize non-Conda environments.
377    PythonEnvironmentKind::Poetry,
378    PythonEnvironmentKind::Pipenv,
379    PythonEnvironmentKind::VirtualEnvWrapper,
380    PythonEnvironmentKind::Venv,
381    PythonEnvironmentKind::VirtualEnv,
382    PythonEnvironmentKind::Conda,
383    PythonEnvironmentKind::Pyenv,
384    PythonEnvironmentKind::GlobalPaths,
385    PythonEnvironmentKind::Homebrew,
386];
387
388fn env_priority(kind: Option<PythonEnvironmentKind>) -> usize {
389    if let Some(kind) = kind {
390        ENV_PRIORITY_LIST
391            .iter()
392            .position(|blessed_env| blessed_env == &kind)
393            .unwrap_or(ENV_PRIORITY_LIST.len())
394    } else {
395        // Unknown toolchains are less useful than non-blessed ones.
396        ENV_PRIORITY_LIST.len() + 1
397    }
398}
399
400#[async_trait(?Send)]
401impl ToolchainLister for PythonToolchainProvider {
402    async fn list(
403        &self,
404        worktree_root: PathBuf,
405        project_env: Option<HashMap<String, String>>,
406    ) -> ToolchainList {
407        let env = project_env.unwrap_or_default();
408        let environment = EnvironmentApi::from_env(&env);
409        let locators = pet::locators::create_locators(
410            Arc::new(pet_conda::Conda::from(&environment)),
411            Arc::new(pet_poetry::Poetry::from(&environment)),
412            &environment,
413        );
414        let mut config = Configuration::default();
415        config.workspace_directories = Some(vec![worktree_root]);
416        let reporter = pet_reporter::collect::create_reporter();
417        pet::find::find_and_report_envs(&reporter, config, &locators, &environment, None);
418
419        let mut toolchains = reporter
420            .environments
421            .lock()
422            .ok()
423            .map_or(Vec::new(), |mut guard| std::mem::take(&mut guard));
424        toolchains.sort_by(|lhs, rhs| {
425            env_priority(lhs.kind)
426                .cmp(&env_priority(rhs.kind))
427                .then_with(|| lhs.executable.cmp(&rhs.executable))
428        });
429        let mut toolchains: Vec<_> = toolchains
430            .into_iter()
431            .filter_map(|toolchain| {
432                let name = if let Some(version) = &toolchain.version {
433                    format!("Python {version} ({:?})", toolchain.kind?)
434                } else {
435                    format!("{:?}", toolchain.kind?)
436                }
437                .into();
438                Some(Toolchain {
439                    name,
440                    path: toolchain.executable?.to_str()?.to_owned().into(),
441                    language_name: LanguageName::new("Python"),
442                })
443            })
444            .collect();
445        toolchains.dedup();
446        ToolchainList {
447            toolchains,
448            default: None,
449            groups: Default::default(),
450        }
451    }
452}
453
454pub struct EnvironmentApi<'a> {
455    global_search_locations: Arc<Mutex<Vec<PathBuf>>>,
456    project_env: &'a HashMap<String, String>,
457    pet_env: pet_core::os_environment::EnvironmentApi,
458}
459
460impl<'a> EnvironmentApi<'a> {
461    pub fn from_env(project_env: &'a HashMap<String, String>) -> Self {
462        let paths = project_env
463            .get("PATH")
464            .map(|p| std::env::split_paths(p).collect())
465            .unwrap_or_default();
466
467        EnvironmentApi {
468            global_search_locations: Arc::new(Mutex::new(paths)),
469            project_env,
470            pet_env: pet_core::os_environment::EnvironmentApi::new(),
471        }
472    }
473
474    fn user_home(&self) -> Option<PathBuf> {
475        self.project_env
476            .get("HOME")
477            .or_else(|| self.project_env.get("USERPROFILE"))
478            .map(|home| pet_fs::path::norm_case(PathBuf::from(home)))
479            .or_else(|| self.pet_env.get_user_home())
480    }
481}
482
483impl<'a> pet_core::os_environment::Environment for EnvironmentApi<'a> {
484    fn get_user_home(&self) -> Option<PathBuf> {
485        self.user_home()
486    }
487
488    fn get_root(&self) -> Option<PathBuf> {
489        None
490    }
491
492    fn get_env_var(&self, key: String) -> Option<String> {
493        self.project_env
494            .get(&key)
495            .cloned()
496            .or_else(|| self.pet_env.get_env_var(key))
497    }
498
499    fn get_know_global_search_locations(&self) -> Vec<PathBuf> {
500        if self.global_search_locations.lock().unwrap().is_empty() {
501            let mut paths =
502                std::env::split_paths(&self.get_env_var("PATH".to_string()).unwrap_or_default())
503                    .collect::<Vec<PathBuf>>();
504
505            log::trace!("Env PATH: {:?}", paths);
506            for p in self.pet_env.get_know_global_search_locations() {
507                if !paths.contains(&p) {
508                    paths.push(p);
509                }
510            }
511
512            let mut paths = paths
513                .into_iter()
514                .filter(|p| p.exists())
515                .collect::<Vec<PathBuf>>();
516
517            self.global_search_locations
518                .lock()
519                .unwrap()
520                .append(&mut paths);
521        }
522        self.global_search_locations.lock().unwrap().clone()
523    }
524}
525
526pub(crate) struct PyLspAdapter {
527    python_venv_base: OnceCell<Result<Arc<Path>, String>>,
528}
529impl PyLspAdapter {
530    const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pylsp");
531    pub(crate) fn new() -> Self {
532        Self {
533            python_venv_base: OnceCell::new(),
534        }
535    }
536    async fn ensure_venv(delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>> {
537        let python_path = Self::find_base_python(delegate)
538            .await
539            .ok_or_else(|| anyhow!("Could not find Python installation for PyLSP"))?;
540        let work_dir = delegate
541            .language_server_download_dir(&Self::SERVER_NAME)
542            .await
543            .ok_or_else(|| anyhow!("Could not get working directory for PyLSP"))?;
544        let mut path = PathBuf::from(work_dir.as_ref());
545        path.push("pylsp-venv");
546        if !path.exists() {
547            Command::new(python_path)
548                .arg("-m")
549                .arg("venv")
550                .arg("pylsp-venv")
551                .current_dir(work_dir)
552                .spawn()?
553                .output()
554                .await?;
555        }
556
557        Ok(path.into())
558    }
559    // Find "baseline", user python version from which we'll create our own venv.
560    async fn find_base_python(delegate: &dyn LspAdapterDelegate) -> Option<PathBuf> {
561        for path in ["python3", "python"] {
562            if let Some(path) = delegate.which(path.as_ref()).await {
563                return Some(path);
564            }
565        }
566        None
567    }
568
569    async fn base_venv(&self, delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>, String> {
570        self.python_venv_base
571            .get_or_init(move || async move {
572                Self::ensure_venv(delegate)
573                    .await
574                    .map_err(|e| format!("{e}"))
575            })
576            .await
577            .clone()
578    }
579}
580
581#[async_trait(?Send)]
582impl LspAdapter for PyLspAdapter {
583    fn name(&self) -> LanguageServerName {
584        Self::SERVER_NAME.clone()
585    }
586
587    async fn check_if_user_installed(
588        &self,
589        _: &dyn LspAdapterDelegate,
590        _: &AsyncAppContext,
591    ) -> Option<LanguageServerBinary> {
592        // We don't support user-provided pylsp, as global packages are discouraged in Python ecosystem.
593        None
594    }
595
596    async fn fetch_latest_server_version(
597        &self,
598        _: &dyn LspAdapterDelegate,
599    ) -> Result<Box<dyn 'static + Any + Send>> {
600        // let uri = "https://pypi.org/pypi/python-lsp-server/json";
601        // let mut root_manifest = delegate
602        //     .http_client()
603        //     .get(&uri, Default::default(), true)
604        //     .await?;
605        // let mut body = Vec::new();
606        // root_manifest.body_mut().read_to_end(&mut body).await?;
607        // let as_str = String::from_utf8(body)?;
608        // let json = serde_json::Value::from_str(&as_str)?;
609        // let latest_version = json
610        //     .get("info")
611        //     .and_then(|info| info.get("version"))
612        //     .and_then(|version| version.as_str().map(ToOwned::to_owned))
613        //     .ok_or_else(|| {
614        //         anyhow!("PyPI response did not contain version info for python-language-server")
615        //     })?;
616        Ok(Box::new(()) as Box<_>)
617    }
618
619    async fn fetch_server_binary(
620        &self,
621        _: Box<dyn 'static + Send + Any>,
622        _: PathBuf,
623        delegate: &dyn LspAdapterDelegate,
624    ) -> Result<LanguageServerBinary> {
625        let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?;
626        let pip_path = venv.join("bin").join("pip3");
627        ensure!(
628            Command::new(pip_path.as_path())
629                .arg("install")
630                .arg("python-lsp-server")
631                .output()
632                .await?
633                .status
634                .success(),
635            "python-lsp-server installation failed"
636        );
637        ensure!(
638            Command::new(pip_path.as_path())
639                .arg("install")
640                .arg("python-lsp-server[all]")
641                .output()
642                .await?
643                .status
644                .success(),
645            "python-lsp-server[all] installation failed"
646        );
647        ensure!(
648            Command::new(pip_path)
649                .arg("install")
650                .arg("pylsp-mypy")
651                .output()
652                .await?
653                .status
654                .success(),
655            "pylsp-mypy installation failed"
656        );
657        let pylsp = venv.join("bin").join("pylsp");
658        Ok(LanguageServerBinary {
659            path: pylsp,
660            env: None,
661            arguments: vec![],
662        })
663    }
664
665    async fn cached_server_binary(
666        &self,
667        _: PathBuf,
668        delegate: &dyn LspAdapterDelegate,
669    ) -> Option<LanguageServerBinary> {
670        let venv = self.base_venv(delegate).await.ok()?;
671        let pylsp = venv.join("bin").join("pylsp");
672        Some(LanguageServerBinary {
673            path: pylsp,
674            env: None,
675            arguments: vec![],
676        })
677    }
678
679    async fn process_completions(&self, _items: &mut [lsp::CompletionItem]) {}
680
681    async fn label_for_completion(
682        &self,
683        item: &lsp::CompletionItem,
684        language: &Arc<language::Language>,
685    ) -> Option<language::CodeLabel> {
686        let label = &item.label;
687        let grammar = language.grammar()?;
688        let highlight_id = match item.kind? {
689            lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?,
690            lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?,
691            lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?,
692            lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?,
693            _ => return None,
694        };
695        Some(language::CodeLabel {
696            text: label.clone(),
697            runs: vec![(0..label.len(), highlight_id)],
698            filter_range: 0..label.len(),
699        })
700    }
701
702    async fn label_for_symbol(
703        &self,
704        name: &str,
705        kind: lsp::SymbolKind,
706        language: &Arc<language::Language>,
707    ) -> Option<language::CodeLabel> {
708        let (text, filter_range, display_range) = match kind {
709            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
710                let text = format!("def {}():\n", name);
711                let filter_range = 4..4 + name.len();
712                let display_range = 0..filter_range.end;
713                (text, filter_range, display_range)
714            }
715            lsp::SymbolKind::CLASS => {
716                let text = format!("class {}:", name);
717                let filter_range = 6..6 + name.len();
718                let display_range = 0..filter_range.end;
719                (text, filter_range, display_range)
720            }
721            lsp::SymbolKind::CONSTANT => {
722                let text = format!("{} = 0", name);
723                let filter_range = 0..name.len();
724                let display_range = 0..filter_range.end;
725                (text, filter_range, display_range)
726            }
727            _ => return None,
728        };
729
730        Some(language::CodeLabel {
731            runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
732            text: text[display_range].to_string(),
733            filter_range,
734        })
735    }
736
737    async fn workspace_configuration(
738        self: Arc<Self>,
739        adapter: &Arc<dyn LspAdapterDelegate>,
740        toolchains: Arc<dyn LanguageToolchainStore>,
741        cx: &mut AsyncAppContext,
742    ) -> Result<Value> {
743        let toolchain = toolchains
744            .active_toolchain(adapter.worktree_id(), LanguageName::new("Python"), cx)
745            .await;
746        cx.update(move |cx| {
747            let mut user_settings =
748                language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
749                    .and_then(|s| s.settings.clone())
750                    .unwrap_or_else(|| {
751                        json!({
752                            "plugins": {
753                                "rope_autoimport": {"enabled": true},
754                                "mypy": {"enabled": true}
755                            }
756                        })
757                    });
758
759            // If python.pythonPath is not set in user config, do so using our toolchain picker.
760            if let Some(toolchain) = toolchain {
761                if user_settings.is_null() {
762                    user_settings = Value::Object(serde_json::Map::default());
763                }
764                let object = user_settings.as_object_mut().unwrap();
765                if let Some(python) = object
766                    .entry("plugins")
767                    .or_insert(Value::Object(serde_json::Map::default()))
768                    .as_object_mut()
769                {
770                    if let Some(jedi) = python
771                        .entry("jedi")
772                        .or_insert(Value::Object(serde_json::Map::default()))
773                        .as_object_mut()
774                    {
775                        jedi.insert(
776                            "environment".to_string(),
777                            Value::String(toolchain.path.clone().into()),
778                        );
779                    }
780                    if let Some(pylint) = python
781                        .entry("mypy")
782                        .or_insert(Value::Object(serde_json::Map::default()))
783                        .as_object_mut()
784                    {
785                        pylint.insert(
786                            "overrides".to_string(),
787                            Value::Array(vec![
788                                Value::String("--python-executable".into()),
789                                Value::String(toolchain.path.into()),
790                            ]),
791                        );
792                    }
793                }
794            }
795            user_settings = Value::Object(serde_json::Map::from_iter([(
796                "pylsp".to_string(),
797                user_settings,
798            )]));
799
800            user_settings
801        })
802    }
803}
804
805#[cfg(test)]
806mod tests {
807    use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext};
808    use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
809    use settings::SettingsStore;
810    use std::num::NonZeroU32;
811
812    #[gpui::test]
813    async fn test_python_autoindent(cx: &mut TestAppContext) {
814        cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
815        let language = crate::language("python", tree_sitter_python::LANGUAGE.into());
816        cx.update(|cx| {
817            let test_settings = SettingsStore::test(cx);
818            cx.set_global(test_settings);
819            language::init(cx);
820            cx.update_global::<SettingsStore, _>(|store, cx| {
821                store.update_user_settings::<AllLanguageSettings>(cx, |s| {
822                    s.defaults.tab_size = NonZeroU32::new(2);
823                });
824            });
825        });
826
827        cx.new_model(|cx| {
828            let mut buffer = Buffer::local("", cx).with_language(language, cx);
829            let append = |buffer: &mut Buffer, text: &str, cx: &mut ModelContext<Buffer>| {
830                let ix = buffer.len();
831                buffer.edit([(ix..ix, text)], Some(AutoindentMode::EachLine), cx);
832            };
833
834            // indent after "def():"
835            append(&mut buffer, "def a():\n", cx);
836            assert_eq!(buffer.text(), "def a():\n  ");
837
838            // preserve indent after blank line
839            append(&mut buffer, "\n  ", cx);
840            assert_eq!(buffer.text(), "def a():\n  \n  ");
841
842            // indent after "if"
843            append(&mut buffer, "if a:\n  ", cx);
844            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    ");
845
846            // preserve indent after statement
847            append(&mut buffer, "b()\n", cx);
848            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    b()\n    ");
849
850            // preserve indent after statement
851            append(&mut buffer, "else", cx);
852            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    b()\n    else");
853
854            // dedent "else""
855            append(&mut buffer, ":", cx);
856            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    b()\n  else:");
857
858            // indent lines after else
859            append(&mut buffer, "\n", cx);
860            assert_eq!(
861                buffer.text(),
862                "def a():\n  \n  if a:\n    b()\n  else:\n    "
863            );
864
865            // indent after an open paren. the closing  paren is not indented
866            // because there is another token before it on the same line.
867            append(&mut buffer, "foo(\n1)", cx);
868            assert_eq!(
869                buffer.text(),
870                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n      1)"
871            );
872
873            // dedent the closing paren if it is shifted to the beginning of the line
874            let argument_ix = buffer.text().find('1').unwrap();
875            buffer.edit(
876                [(argument_ix..argument_ix + 1, "")],
877                Some(AutoindentMode::EachLine),
878                cx,
879            );
880            assert_eq!(
881                buffer.text(),
882                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )"
883            );
884
885            // preserve indent after the close paren
886            append(&mut buffer, "\n", cx);
887            assert_eq!(
888                buffer.text(),
889                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )\n    "
890            );
891
892            // manually outdent the last line
893            let end_whitespace_ix = buffer.len() - 4;
894            buffer.edit(
895                [(end_whitespace_ix..buffer.len(), "")],
896                Some(AutoindentMode::EachLine),
897                cx,
898            );
899            assert_eq!(
900                buffer.text(),
901                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )\n"
902            );
903
904            // preserve the newly reduced indentation on the next newline
905            append(&mut buffer, "\n", cx);
906            assert_eq!(
907                buffer.text(),
908                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )\n\n"
909            );
910
911            // reset to a simple if statement
912            buffer.edit([(0..buffer.len(), "if a:\n  b(\n  )")], None, cx);
913
914            // dedent "else" on the line after a closing paren
915            append(&mut buffer, "\n  else:\n", cx);
916            assert_eq!(buffer.text(), "if a:\n  b(\n  )\nelse:\n  ");
917
918            buffer
919        });
920    }
921}