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}