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}