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        for locator in locators.iter() {
417            locator.configure(&config);
418        }
419
420        let reporter = pet_reporter::collect::create_reporter();
421        pet::find::find_and_report_envs(&reporter, config, &locators, &environment, None);
422
423        let mut toolchains = reporter
424            .environments
425            .lock()
426            .ok()
427            .map_or(Vec::new(), |mut guard| std::mem::take(&mut guard));
428        toolchains.sort_by(|lhs, rhs| {
429            env_priority(lhs.kind)
430                .cmp(&env_priority(rhs.kind))
431                .then_with(|| lhs.executable.cmp(&rhs.executable))
432        });
433        let mut toolchains: Vec<_> = toolchains
434            .into_iter()
435            .filter_map(|toolchain| {
436                let name = if let Some(version) = &toolchain.version {
437                    format!("Python {version} ({:?})", toolchain.kind?)
438                } else {
439                    format!("{:?}", toolchain.kind?)
440                }
441                .into();
442                Some(Toolchain {
443                    name,
444                    path: toolchain.executable?.to_str()?.to_owned().into(),
445                    language_name: LanguageName::new("Python"),
446                })
447            })
448            .collect();
449        toolchains.dedup();
450        ToolchainList {
451            toolchains,
452            default: None,
453            groups: Default::default(),
454        }
455    }
456}
457
458pub struct EnvironmentApi<'a> {
459    global_search_locations: Arc<Mutex<Vec<PathBuf>>>,
460    project_env: &'a HashMap<String, String>,
461    pet_env: pet_core::os_environment::EnvironmentApi,
462}
463
464impl<'a> EnvironmentApi<'a> {
465    pub fn from_env(project_env: &'a HashMap<String, String>) -> Self {
466        let paths = project_env
467            .get("PATH")
468            .map(|p| std::env::split_paths(p).collect())
469            .unwrap_or_default();
470
471        EnvironmentApi {
472            global_search_locations: Arc::new(Mutex::new(paths)),
473            project_env,
474            pet_env: pet_core::os_environment::EnvironmentApi::new(),
475        }
476    }
477
478    fn user_home(&self) -> Option<PathBuf> {
479        self.project_env
480            .get("HOME")
481            .or_else(|| self.project_env.get("USERPROFILE"))
482            .map(|home| pet_fs::path::norm_case(PathBuf::from(home)))
483            .or_else(|| self.pet_env.get_user_home())
484    }
485}
486
487impl<'a> pet_core::os_environment::Environment for EnvironmentApi<'a> {
488    fn get_user_home(&self) -> Option<PathBuf> {
489        self.user_home()
490    }
491
492    fn get_root(&self) -> Option<PathBuf> {
493        None
494    }
495
496    fn get_env_var(&self, key: String) -> Option<String> {
497        self.project_env
498            .get(&key)
499            .cloned()
500            .or_else(|| self.pet_env.get_env_var(key))
501    }
502
503    fn get_know_global_search_locations(&self) -> Vec<PathBuf> {
504        if self.global_search_locations.lock().unwrap().is_empty() {
505            let mut paths =
506                std::env::split_paths(&self.get_env_var("PATH".to_string()).unwrap_or_default())
507                    .collect::<Vec<PathBuf>>();
508
509            log::trace!("Env PATH: {:?}", paths);
510            for p in self.pet_env.get_know_global_search_locations() {
511                if !paths.contains(&p) {
512                    paths.push(p);
513                }
514            }
515
516            let mut paths = paths
517                .into_iter()
518                .filter(|p| p.exists())
519                .collect::<Vec<PathBuf>>();
520
521            self.global_search_locations
522                .lock()
523                .unwrap()
524                .append(&mut paths);
525        }
526        self.global_search_locations.lock().unwrap().clone()
527    }
528}
529
530pub(crate) struct PyLspAdapter {
531    python_venv_base: OnceCell<Result<Arc<Path>, String>>,
532}
533impl PyLspAdapter {
534    const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pylsp");
535    pub(crate) fn new() -> Self {
536        Self {
537            python_venv_base: OnceCell::new(),
538        }
539    }
540    async fn ensure_venv(delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>> {
541        let python_path = Self::find_base_python(delegate)
542            .await
543            .ok_or_else(|| anyhow!("Could not find Python installation for PyLSP"))?;
544        let work_dir = delegate
545            .language_server_download_dir(&Self::SERVER_NAME)
546            .await
547            .ok_or_else(|| anyhow!("Could not get working directory for PyLSP"))?;
548        let mut path = PathBuf::from(work_dir.as_ref());
549        path.push("pylsp-venv");
550        if !path.exists() {
551            Command::new(python_path)
552                .arg("-m")
553                .arg("venv")
554                .arg("pylsp-venv")
555                .current_dir(work_dir)
556                .spawn()?
557                .output()
558                .await?;
559        }
560
561        Ok(path.into())
562    }
563    // Find "baseline", user python version from which we'll create our own venv.
564    async fn find_base_python(delegate: &dyn LspAdapterDelegate) -> Option<PathBuf> {
565        for path in ["python3", "python"] {
566            if let Some(path) = delegate.which(path.as_ref()).await {
567                return Some(path);
568            }
569        }
570        None
571    }
572
573    async fn base_venv(&self, delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>, String> {
574        self.python_venv_base
575            .get_or_init(move || async move {
576                Self::ensure_venv(delegate)
577                    .await
578                    .map_err(|e| format!("{e}"))
579            })
580            .await
581            .clone()
582    }
583}
584
585#[async_trait(?Send)]
586impl LspAdapter for PyLspAdapter {
587    fn name(&self) -> LanguageServerName {
588        Self::SERVER_NAME.clone()
589    }
590
591    async fn check_if_user_installed(
592        &self,
593        _: &dyn LspAdapterDelegate,
594        _: &AsyncAppContext,
595    ) -> Option<LanguageServerBinary> {
596        // We don't support user-provided pylsp, as global packages are discouraged in Python ecosystem.
597        None
598    }
599
600    async fn fetch_latest_server_version(
601        &self,
602        _: &dyn LspAdapterDelegate,
603    ) -> Result<Box<dyn 'static + Any + Send>> {
604        // let uri = "https://pypi.org/pypi/python-lsp-server/json";
605        // let mut root_manifest = delegate
606        //     .http_client()
607        //     .get(&uri, Default::default(), true)
608        //     .await?;
609        // let mut body = Vec::new();
610        // root_manifest.body_mut().read_to_end(&mut body).await?;
611        // let as_str = String::from_utf8(body)?;
612        // let json = serde_json::Value::from_str(&as_str)?;
613        // let latest_version = json
614        //     .get("info")
615        //     .and_then(|info| info.get("version"))
616        //     .and_then(|version| version.as_str().map(ToOwned::to_owned))
617        //     .ok_or_else(|| {
618        //         anyhow!("PyPI response did not contain version info for python-language-server")
619        //     })?;
620        Ok(Box::new(()) as Box<_>)
621    }
622
623    async fn fetch_server_binary(
624        &self,
625        _: Box<dyn 'static + Send + Any>,
626        _: PathBuf,
627        delegate: &dyn LspAdapterDelegate,
628    ) -> Result<LanguageServerBinary> {
629        let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?;
630        let pip_path = venv.join("bin").join("pip3");
631        ensure!(
632            Command::new(pip_path.as_path())
633                .arg("install")
634                .arg("python-lsp-server")
635                .output()
636                .await?
637                .status
638                .success(),
639            "python-lsp-server installation failed"
640        );
641        ensure!(
642            Command::new(pip_path.as_path())
643                .arg("install")
644                .arg("python-lsp-server[all]")
645                .output()
646                .await?
647                .status
648                .success(),
649            "python-lsp-server[all] installation failed"
650        );
651        ensure!(
652            Command::new(pip_path)
653                .arg("install")
654                .arg("pylsp-mypy")
655                .output()
656                .await?
657                .status
658                .success(),
659            "pylsp-mypy installation failed"
660        );
661        let pylsp = venv.join("bin").join("pylsp");
662        Ok(LanguageServerBinary {
663            path: pylsp,
664            env: None,
665            arguments: vec![],
666        })
667    }
668
669    async fn cached_server_binary(
670        &self,
671        _: PathBuf,
672        delegate: &dyn LspAdapterDelegate,
673    ) -> Option<LanguageServerBinary> {
674        let venv = self.base_venv(delegate).await.ok()?;
675        let pylsp = venv.join("bin").join("pylsp");
676        Some(LanguageServerBinary {
677            path: pylsp,
678            env: None,
679            arguments: vec![],
680        })
681    }
682
683    async fn process_completions(&self, _items: &mut [lsp::CompletionItem]) {}
684
685    async fn label_for_completion(
686        &self,
687        item: &lsp::CompletionItem,
688        language: &Arc<language::Language>,
689    ) -> Option<language::CodeLabel> {
690        let label = &item.label;
691        let grammar = language.grammar()?;
692        let highlight_id = match item.kind? {
693            lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?,
694            lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?,
695            lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?,
696            lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?,
697            _ => return None,
698        };
699        Some(language::CodeLabel {
700            text: label.clone(),
701            runs: vec![(0..label.len(), highlight_id)],
702            filter_range: 0..label.len(),
703        })
704    }
705
706    async fn label_for_symbol(
707        &self,
708        name: &str,
709        kind: lsp::SymbolKind,
710        language: &Arc<language::Language>,
711    ) -> Option<language::CodeLabel> {
712        let (text, filter_range, display_range) = match kind {
713            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
714                let text = format!("def {}():\n", name);
715                let filter_range = 4..4 + name.len();
716                let display_range = 0..filter_range.end;
717                (text, filter_range, display_range)
718            }
719            lsp::SymbolKind::CLASS => {
720                let text = format!("class {}:", name);
721                let filter_range = 6..6 + name.len();
722                let display_range = 0..filter_range.end;
723                (text, filter_range, display_range)
724            }
725            lsp::SymbolKind::CONSTANT => {
726                let text = format!("{} = 0", name);
727                let filter_range = 0..name.len();
728                let display_range = 0..filter_range.end;
729                (text, filter_range, display_range)
730            }
731            _ => return None,
732        };
733
734        Some(language::CodeLabel {
735            runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
736            text: text[display_range].to_string(),
737            filter_range,
738        })
739    }
740
741    async fn workspace_configuration(
742        self: Arc<Self>,
743        adapter: &Arc<dyn LspAdapterDelegate>,
744        toolchains: Arc<dyn LanguageToolchainStore>,
745        cx: &mut AsyncAppContext,
746    ) -> Result<Value> {
747        let toolchain = toolchains
748            .active_toolchain(adapter.worktree_id(), LanguageName::new("Python"), cx)
749            .await;
750        cx.update(move |cx| {
751            let mut user_settings =
752                language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
753                    .and_then(|s| s.settings.clone())
754                    .unwrap_or_else(|| {
755                        json!({
756                            "plugins": {
757                                "rope_autoimport": {"enabled": true},
758                                "mypy": {"enabled": true}
759                            }
760                        })
761                    });
762
763            // If python.pythonPath is not set in user config, do so using our toolchain picker.
764            if let Some(toolchain) = toolchain {
765                if user_settings.is_null() {
766                    user_settings = Value::Object(serde_json::Map::default());
767                }
768                let object = user_settings.as_object_mut().unwrap();
769                if let Some(python) = object
770                    .entry("plugins")
771                    .or_insert(Value::Object(serde_json::Map::default()))
772                    .as_object_mut()
773                {
774                    if let Some(jedi) = python
775                        .entry("jedi")
776                        .or_insert(Value::Object(serde_json::Map::default()))
777                        .as_object_mut()
778                    {
779                        jedi.insert(
780                            "environment".to_string(),
781                            Value::String(toolchain.path.clone().into()),
782                        );
783                    }
784                    if let Some(pylint) = python
785                        .entry("mypy")
786                        .or_insert(Value::Object(serde_json::Map::default()))
787                        .as_object_mut()
788                    {
789                        pylint.insert(
790                            "overrides".to_string(),
791                            Value::Array(vec![
792                                Value::String("--python-executable".into()),
793                                Value::String(toolchain.path.into()),
794                            ]),
795                        );
796                    }
797                }
798            }
799            user_settings = Value::Object(serde_json::Map::from_iter([(
800                "pylsp".to_string(),
801                user_settings,
802            )]));
803
804            user_settings
805        })
806    }
807}
808
809#[cfg(test)]
810mod tests {
811    use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext};
812    use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
813    use settings::SettingsStore;
814    use std::num::NonZeroU32;
815
816    #[gpui::test]
817    async fn test_python_autoindent(cx: &mut TestAppContext) {
818        cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
819        let language = crate::language("python", tree_sitter_python::LANGUAGE.into());
820        cx.update(|cx| {
821            let test_settings = SettingsStore::test(cx);
822            cx.set_global(test_settings);
823            language::init(cx);
824            cx.update_global::<SettingsStore, _>(|store, cx| {
825                store.update_user_settings::<AllLanguageSettings>(cx, |s| {
826                    s.defaults.tab_size = NonZeroU32::new(2);
827                });
828            });
829        });
830
831        cx.new_model(|cx| {
832            let mut buffer = Buffer::local("", cx).with_language(language, cx);
833            let append = |buffer: &mut Buffer, text: &str, cx: &mut ModelContext<Buffer>| {
834                let ix = buffer.len();
835                buffer.edit([(ix..ix, text)], Some(AutoindentMode::EachLine), cx);
836            };
837
838            // indent after "def():"
839            append(&mut buffer, "def a():\n", cx);
840            assert_eq!(buffer.text(), "def a():\n  ");
841
842            // preserve indent after blank line
843            append(&mut buffer, "\n  ", cx);
844            assert_eq!(buffer.text(), "def a():\n  \n  ");
845
846            // indent after "if"
847            append(&mut buffer, "if a:\n  ", cx);
848            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    ");
849
850            // preserve indent after statement
851            append(&mut buffer, "b()\n", cx);
852            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    b()\n    ");
853
854            // preserve indent after statement
855            append(&mut buffer, "else", cx);
856            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    b()\n    else");
857
858            // dedent "else""
859            append(&mut buffer, ":", cx);
860            assert_eq!(buffer.text(), "def a():\n  \n  if a:\n    b()\n  else:");
861
862            // indent lines after else
863            append(&mut buffer, "\n", cx);
864            assert_eq!(
865                buffer.text(),
866                "def a():\n  \n  if a:\n    b()\n  else:\n    "
867            );
868
869            // indent after an open paren. the closing  paren is not indented
870            // because there is another token before it on the same line.
871            append(&mut buffer, "foo(\n1)", cx);
872            assert_eq!(
873                buffer.text(),
874                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n      1)"
875            );
876
877            // dedent the closing paren if it is shifted to the beginning of the line
878            let argument_ix = buffer.text().find('1').unwrap();
879            buffer.edit(
880                [(argument_ix..argument_ix + 1, "")],
881                Some(AutoindentMode::EachLine),
882                cx,
883            );
884            assert_eq!(
885                buffer.text(),
886                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )"
887            );
888
889            // preserve indent after the close paren
890            append(&mut buffer, "\n", cx);
891            assert_eq!(
892                buffer.text(),
893                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )\n    "
894            );
895
896            // manually outdent the last line
897            let end_whitespace_ix = buffer.len() - 4;
898            buffer.edit(
899                [(end_whitespace_ix..buffer.len(), "")],
900                Some(AutoindentMode::EachLine),
901                cx,
902            );
903            assert_eq!(
904                buffer.text(),
905                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )\n"
906            );
907
908            // preserve the newly reduced indentation on the next newline
909            append(&mut buffer, "\n", cx);
910            assert_eq!(
911                buffer.text(),
912                "def a():\n  \n  if a:\n    b()\n  else:\n    foo(\n    )\n\n"
913            );
914
915            // reset to a simple if statement
916            buffer.edit([(0..buffer.len(), "if a:\n  b(\n  )")], None, cx);
917
918            // dedent "else" on the line after a closing paren
919            append(&mut buffer, "\n  else:\n", cx);
920            assert_eq!(buffer.text(), "if a:\n  b(\n  )\nelse:\n  ");
921
922            buffer
923        });
924    }
925}