mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-31 04:54:24 +00:00
python: Add built-in support for Ty (#37580)
- **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>
This commit is contained in:
parent
11b7913956
commit
87f5e72fc0
4 changed files with 239 additions and 16 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -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 {
|
||||
node: NodeRuntime,
|
||||
|
||||
pub struct TyLspAdapter {
|
||||
fs: Arc<dyn Fs>,
|
||||
}
|
||||
|
||||
impl PythonLspAdapter {
|
||||
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pyright");
|
||||
#[cfg(target_os = "macos")]
|
||||
impl TyLspAdapter {
|
||||
const GITHUB_ASSET_KIND: AssetKind = AssetKind::Gz;
|
||||
const ARCH_SERVER_NAME: &str = "apple-darwin";
|
||||
}
|
||||
|
||||
pub fn new(node: NodeRuntime) -> Self {
|
||||
PythonLspAdapter { node }
|
||||
#[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 PythonLspAdapter {
|
||||
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 PyrightLspAdapter {
|
||||
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pyright");
|
||||
|
||||
pub fn new(node: NodeRuntime) -> Self {
|
||||
PyrightLspAdapter { node }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspAdapter for PyrightLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
Self::SERVER_NAME
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue