ty: Add support for using venv-installed binary (#47029)

Joseph T. Lyons created

This aligns ty's behavior with ruff by checking for binaries in the
following order:

1. venv (from the Python toolchain's bin directory)
2. PATH
3. Zed-installed

Release Notes:

- ty: Added support for using venv-installed binary

Change summary

crates/languages/src/python.rs | 31 ++++++++++++++++++++++---------
1 file changed, 22 insertions(+), 9 deletions(-)

Detailed changes

crates/languages/src/python.rs 🔗

@@ -300,18 +300,31 @@ impl LspInstaller for TyLspAdapter {
     async fn check_if_user_installed(
         &self,
         delegate: &dyn LspAdapterDelegate,
-        _: Option<Toolchain>,
+        toolchain: Option<Toolchain>,
         _: &AsyncApp,
     ) -> Option<LanguageServerBinary> {
-        let Some(ty_bin) = delegate.which(Self::SERVER_NAME.as_ref()).await else {
-            return None;
+        let ty_in_venv = if let Some(toolchain) = toolchain
+            && toolchain.language_name.as_ref() == "Python"
+        {
+            Path::new(toolchain.path.as_str())
+                .parent()
+                .map(|path| path.join("ty"))
+        } else {
+            None
         };
-        let env = delegate.shell_env().await;
-        Some(LanguageServerBinary {
-            path: ty_bin,
-            env: Some(env),
-            arguments: vec!["server".into()],
-        })
+
+        for path in ty_in_venv.into_iter().chain(["ty".into()]) {
+            if let Some(ty_bin) = delegate.which(path.as_os_str()).await {
+                let env = delegate.shell_env().await;
+                return Some(LanguageServerBinary {
+                    path: ty_bin,
+                    env: Some(env),
+                    arguments: vec!["server".into()],
+                });
+            }
+        }
+
+        None
     }
 
     async fn fetch_server_binary(