Cargo.lock 🔗
@@ -9331,6 +9331,7 @@ dependencies = [
"tree-sitter-typescript",
"tree-sitter-yaml",
"unindent",
+ "url",
"util",
"workspace",
"workspace-hack",
Piotr Osiewicz , Lukas Wirth , Zsolt Dollenstein , and Cole Miller created
- **Rename PythonLSPAdapter to PyrightLspAdapter**
- **ah damn**
- **Ah damn x2**
Release Notes:
- Python: Added built-in support for [ty](https://docs.astral.sh/ty/)
language server (disabled by default).
---------
Co-authored-by: Lukas Wirth <lukas@zed.dev>
Co-authored-by: Zsolt Dollenstein <zsol.zsol@gmail.com>
Co-authored-by: Cole Miller <cole@zed.dev>
Cargo.lock | 1
crates/languages/Cargo.toml | 1
crates/languages/src/lib.rs | 5
crates/languages/src/python.rs | 242 ++++++++++++++++++++++++++++++++++-
4 files changed, 236 insertions(+), 13 deletions(-)
@@ -9331,6 +9331,7 @@ dependencies = [
"tree-sitter-typescript",
"tree-sitter-yaml",
"unindent",
+ "url",
"util",
"workspace",
"workspace-hack",
@@ -69,6 +69,7 @@ serde_json_lenient.workspace = true
settings.workspace = true
smol.workspace = true
snippet_provider.workspace = true
+url.workspace = true
task.workspace = true
tempfile.workspace = true
toml.workspace = true
@@ -91,8 +91,9 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
let json_lsp_adapter = Arc::new(json::JsonLspAdapter::new(node.clone(), languages.clone()));
let node_version_lsp_adapter = Arc::new(json::NodeVersionAdapter);
let py_lsp_adapter = Arc::new(python::PyLspAdapter::new());
+ let ty_lsp_adapter = Arc::new(python::TyLspAdapter::new(fs.clone()));
let python_context_provider = Arc::new(python::PythonContextProvider);
- let python_lsp_adapter = Arc::new(python::PythonLspAdapter::new(node.clone()));
+ let python_lsp_adapter = Arc::new(python::PyrightLspAdapter::new(node.clone()));
let basedpyright_lsp_adapter = Arc::new(BasedPyrightLspAdapter::new());
let ruff_lsp_adapter = Arc::new(RuffLspAdapter::new(fs.clone()));
let python_toolchain_provider = Arc::new(python::PythonToolchainProvider);
@@ -268,8 +269,10 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
LanguageServerName("typescript-language-server".into()),
typescript_lsp_adapter,
);
+
languages.register_available_lsp_adapter(python_lsp_adapter.name(), python_lsp_adapter);
languages.register_available_lsp_adapter(py_lsp_adapter.name(), py_lsp_adapter);
+ languages.register_available_lsp_adapter(ty_lsp_adapter.name(), ty_lsp_adapter);
// Register Tailwind for the existing languages that should have it by default.
//
// This can be driven by the `language_servers` setting once we have a way for
@@ -2,19 +2,14 @@ use anyhow::{Context as _, ensure};
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use collections::HashMap;
-use dap::adapters::latest_github_release;
use futures::{AsyncBufReadExt, StreamExt as _};
-use gpui::{App, Task};
-use gpui::{AsyncApp, SharedString};
-use http_client::github::AssetKind;
-use http_client::github::GitHubLspBinaryVersion;
-use language::ToolchainList;
-use language::ToolchainLister;
+use gpui::{App, AsyncApp, SharedString, Task};
+use http_client::github::{AssetKind, GitHubLspBinaryVersion, latest_github_release};
use language::language_settings::language_settings;
use language::{ContextLocation, LanguageToolchainStore};
use language::{ContextProvider, LspAdapter, LspAdapterDelegate};
use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery};
-use language::{Toolchain, ToolchainMetadata};
+use language::{Toolchain, ToolchainList, ToolchainLister, ToolchainMetadata};
use lsp::LanguageServerBinary;
use lsp::LanguageServerName;
use node_runtime::{NodeRuntime, VersionStrategy};
@@ -106,20 +101,243 @@ fn process_pyright_completions(items: &mut [lsp::CompletionItem]) {
item.sort_text.take();
}
}
-pub struct PythonLspAdapter {
+
+pub struct TyLspAdapter {
+ fs: Arc<dyn Fs>,
+}
+
+#[cfg(target_os = "macos")]
+impl TyLspAdapter {
+ const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
+ const ARCH_SERVER_NAME: &str = "apple-darwin";
+}
+
+#[cfg(target_os = "linux")]
+impl TyLspAdapter {
+ const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
+ const ARCH_SERVER_NAME: &str = "unknown-linux-gnu";
+}
+
+#[cfg(target_os = "freebsd")]
+impl TyLspAdapter {
+ const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
+ const ARCH_SERVER_NAME: &str = "unknown-freebsd";
+}
+
+#[cfg(target_os = "windows")]
+impl TyLspAdapter {
+ const GITHUB_ASSET_KIND: AssetKind = AssetKind::Zip;
+ const ARCH_SERVER_NAME: &str = "pc-windows-msvc";
+}
+
+impl TyLspAdapter {
+ const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("ty");
+
+ pub fn new(fs: Arc<dyn Fs>) -> TyLspAdapter {
+ TyLspAdapter { fs }
+ }
+
+ fn build_asset_name() -> Result<(String, String)> {
+ let arch = match consts::ARCH {
+ "x86" => "i686",
+ _ => consts::ARCH,
+ };
+ let os = Self::ARCH_SERVER_NAME;
+ let suffix = match consts::OS {
+ "windows" => "zip",
+ _ => "tar.gz",
+ };
+ let asset_name = format!("ty-{arch}-{os}.{suffix}");
+ let asset_stem = format!("ty-{arch}-{os}");
+ Ok((asset_stem, asset_name))
+ }
+}
+
+#[async_trait(?Send)]
+impl LspAdapter for TyLspAdapter {
+ fn name(&self) -> LanguageServerName {
+ Self::SERVER_NAME
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ delegate: &dyn LspAdapterDelegate,
+ _: &AsyncApp,
+ ) -> Result<Box<dyn 'static + Send + Any>> {
+ let release =
+ latest_github_release("astral-sh/ty", true, true, delegate.http_client()).await?;
+ let (_, asset_name) = Self::build_asset_name()?;
+ let asset = release
+ .assets
+ .into_iter()
+ .find(|asset| asset.name == asset_name)
+ .with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
+ Ok(Box::new(GitHubLspBinaryVersion {
+ name: release.tag_name,
+ url: asset.browser_download_url,
+ digest: asset.digest,
+ }))
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ latest_version: Box<dyn 'static + Send + Any>,
+ container_dir: PathBuf,
+ delegate: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ let GitHubLspBinaryVersion {
+ name,
+ url,
+ digest: expected_digest,
+ } = *latest_version.downcast::<GitHubLspBinaryVersion>().unwrap();
+ let destination_path = container_dir.join(format!("ty-{name}"));
+ let server_path = match Self::GITHUB_ASSET_KIND {
+ AssetKind::TarGz | AssetKind::Gz => destination_path.clone(), // Tar and gzip extract in place.
+ AssetKind::Zip => destination_path.clone().join("ty.exe"), // zip contains a .exe
+ };
+
+ let binary = LanguageServerBinary {
+ path: server_path.clone(),
+ env: None,
+ arguments: Default::default(),
+ };
+
+ let metadata_path = destination_path.with_extension("metadata");
+ let metadata = GithubBinaryMetadata::read_from_file(&metadata_path)
+ .await
+ .ok();
+ if let Some(metadata) = metadata {
+ let validity_check = async || {
+ delegate
+ .try_exec(LanguageServerBinary {
+ path: server_path.clone(),
+ arguments: vec!["--version".into()],
+ env: None,
+ })
+ .await
+ .inspect_err(|err| {
+ log::warn!("Unable to run {server_path:?} asset, redownloading: {err}",)
+ })
+ };
+ if let (Some(actual_digest), Some(expected_digest)) =
+ (&metadata.digest, &expected_digest)
+ {
+ if actual_digest == expected_digest {
+ if validity_check().await.is_ok() {
+ return Ok(binary);
+ }
+ } else {
+ log::info!(
+ "SHA-256 mismatch for {destination_path:?} asset, downloading new asset. Expected: {expected_digest}, Got: {actual_digest}"
+ );
+ }
+ } else if validity_check().await.is_ok() {
+ return Ok(binary);
+ }
+ }
+
+ download_server_binary(
+ delegate,
+ &url,
+ expected_digest.as_deref(),
+ &destination_path,
+ Self::GITHUB_ASSET_KIND,
+ )
+ .await?;
+ make_file_executable(&server_path).await?;
+ remove_matching(&container_dir, |path| path != destination_path).await;
+ GithubBinaryMetadata::write_to_file(
+ &GithubBinaryMetadata {
+ metadata_version: 1,
+ digest: expected_digest,
+ },
+ &metadata_path,
+ )
+ .await?;
+
+ Ok(LanguageServerBinary {
+ path: server_path,
+ env: None,
+ arguments: Default::default(),
+ })
+ }
+
+ async fn cached_server_binary(
+ &self,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Option<LanguageServerBinary> {
+ maybe!(async {
+ let mut last = None;
+ let mut entries = self.fs.read_dir(&container_dir).await?;
+ while let Some(entry) = entries.next().await {
+ let path = entry?;
+ if path.extension().is_some_and(|ext| ext == "metadata") {
+ continue;
+ }
+ last = Some(path);
+ }
+
+ let path = last.context("no cached binary")?;
+ let path = match TyLspAdapter::GITHUB_ASSET_KIND {
+ AssetKind::TarGz | AssetKind::Gz => path, // Tar and gzip extract in place.
+ AssetKind::Zip => path.join("ty.exe"), // zip contains a .exe
+ };
+
+ anyhow::Ok(LanguageServerBinary {
+ path,
+ env: None,
+ arguments: Default::default(),
+ })
+ })
+ .await
+ .log_err()
+ }
+
+ async fn workspace_configuration(
+ self: Arc<Self>,
+ _: &Arc<dyn LspAdapterDelegate>,
+ toolchain: Option<Toolchain>,
+ _cx: &mut AsyncApp,
+ ) -> Result<Value> {
+ let mut ret = json!({});
+ if let Some(toolchain) = toolchain.and_then(|toolchain| {
+ serde_json::from_value::<PythonEnvironment>(toolchain.as_json).ok()
+ }) {
+ _ = maybe!({
+ let uri = url::Url::from_file_path(toolchain.executable?).ok()?;
+ let sys_prefix = toolchain.prefix.clone()?;
+ let environment = json!({
+ "executable": {
+ "uri": uri,
+ "sysPrefix": sys_prefix
+ }
+ });
+ ret.as_object_mut()?.insert(
+ "pythonExtension".into(),
+ json!({ "activeEnvironment": environment }),
+ );
+ Some(())
+ });
+ }
+ Ok(json!({"ty": ret}))
+ }
+}
+
+pub struct PyrightLspAdapter {
node: NodeRuntime,
}
-impl PythonLspAdapter {
+impl PyrightLspAdapter {
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pyright");
pub fn new(node: NodeRuntime) -> Self {
- PythonLspAdapter { node }
+ PyrightLspAdapter { node }
}
}
#[async_trait(?Send)]
-impl LspAdapter for PythonLspAdapter {
+impl LspAdapter for PyrightLspAdapter {
fn name(&self) -> LanguageServerName {
Self::SERVER_NAME
}