2438 lines · 86.2 KB · Rust
RawDownload
use anyhow::{Context as _, ensure};use anyhow::{Result, anyhow};use async_trait::async_trait;use collections::HashMap;use futures::{AsyncBufReadExt, StreamExt as _};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, LspInstaller};use language::{ContextProvider, LspAdapter, LspAdapterDelegate};use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery};use language::{Toolchain, ToolchainList, ToolchainLister, ToolchainMetadata};use lsp::LanguageServerBinary;use lsp::LanguageServerName;use node_runtime::{NodeRuntime, VersionStrategy};use pet_core::Configuration;use pet_core::os_environment::Environment;use pet_core::python_environment::{PythonEnvironment, PythonEnvironmentKind};use pet_virtualenv::is_virtualenv_dir;use project::Fs;use project::lsp_store::language_server_settings;use serde::{Deserialize, Serialize};use serde_json::{Value, json};use settings::Settings;use smol::lock::OnceCell;use std::cmp::Ordering;use std::env::consts;use terminal::terminal_settings::TerminalSettings;use util::command::new_smol_command;use util::fs::{make_file_executable, remove_matching};use util::rel_path::RelPath; use http_client::github_download::{GithubBinaryMetadata, download_server_binary};use parking_lot::Mutex;use std::str::FromStr;use std::{    borrow::Cow,    fmt::Write,    path::{Path, PathBuf},    sync::Arc,};use task::{ShellKind, TaskTemplate, TaskTemplates, VariableName};use util::{ResultExt, maybe}; #[derive(Debug, Serialize, Deserialize)]pub(crate) struct PythonToolchainData {    #[serde(flatten)]    environment: PythonEnvironment,    #[serde(skip_serializing_if = "Option::is_none")]    activation_scripts: Option<HashMap<ShellKind, PathBuf>>,} pub(crate) struct PyprojectTomlManifestProvider; impl ManifestProvider for PyprojectTomlManifestProvider {    fn name(&self) -> ManifestName {        SharedString::new_static("pyproject.toml").into()    }     fn search(        &self,        ManifestQuery {            path,            depth,            delegate,        }: ManifestQuery,    ) -> Option<Arc<RelPath>> {        for path in path.ancestors().take(depth) {            let p = path.join(RelPath::unix("pyproject.toml").unwrap());            if delegate.exists(&p, Some(false)) {                return Some(path.into());            }        }         None    }} enum TestRunner {    UNITTEST,    PYTEST,} impl FromStr for TestRunner {    type Err = ();     fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {        match s {            "unittest" => Ok(Self::UNITTEST),            "pytest" => Ok(Self::PYTEST),            _ => Err(()),        }    }} /// Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`./// Where `XX` is the sorting category, `YYYY` is based on most recent usage,/// and `name` is the symbol name itself.////// The problem with it is that Pyright adjusts the sort text based on previous resolutions (items for which we've issued `completion/resolve` call have their sortText adjusted),/// which - long story short - makes completion items list non-stable. Pyright probably relies on VSCode's implementation detail./// see https://github.com/microsoft/pyright/blob/95ef4e103b9b2f129c9320427e51b73ea7cf78bd/packages/pyright-internal/src/languageService/completionProvider.ts#LL2873fn process_pyright_completions(items: &mut [lsp::CompletionItem]) {    for item in items {        item.sort_text.take();    }} pub struct TyLspAdapter {    fs: Arc<dyn Fs>,} #[cfg(target_os = "macos")]impl TyLspAdapter {    const GITHUB_ASSET_KIND: AssetKind = AssetKind::TarGz;    const ARCH_SERVER_NAME: &str = "apple-darwin";} #[cfg(target_os = "linux")]impl TyLspAdapter {    const GITHUB_ASSET_KIND: AssetKind = AssetKind::TarGz;    const ARCH_SERVER_NAME: &str = "unknown-linux-gnu";} #[cfg(target_os = "freebsd")]impl TyLspAdapter {    const GITHUB_ASSET_KIND: AssetKind = AssetKind::TarGz;    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 label_for_completion(        &self,        item: &lsp::CompletionItem,        language: &Arc<language::Language>,    ) -> Option<language::CodeLabel> {        let label = &item.label;        let label_len = label.len();        let grammar = language.grammar()?;        let highlight_id = match item.kind? {            lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method"),            lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function"),            lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type"),            lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant"),            lsp::CompletionItemKind::VARIABLE => grammar.highlight_id_for_name("variable"),            _ => {                return None;            }        };         let mut text = label.clone();        if let Some(completion_details) = item            .label_details            .as_ref()            .and_then(|details| details.detail.as_ref())        {            write!(&mut text, " {}", completion_details).ok();        }         Some(language::CodeLabel::filtered(            text,            label_len,            item.filter_text.as_deref(),            highlight_id                .map(|id| (0..label_len, id))                .into_iter()                .collect(),        ))    }     async fn workspace_configuration(        self: Arc<Self>,        delegate: &Arc<dyn LspAdapterDelegate>,        toolchain: Option<Toolchain>,        cx: &mut AsyncApp,    ) -> Result<Value> {        let mut ret = cx            .update(|cx| {                language_server_settings(delegate.as_ref(), &self.name(), cx)                    .and_then(|s| s.settings.clone())            })?            .unwrap_or_else(|| json!({}));        if let Some(toolchain) = toolchain.and_then(|toolchain| {            serde_json::from_value::<PythonToolchainData>(toolchain.as_json).ok()        }) {            _ = maybe!({                let uri =                    url::Url::from_file_path(toolchain.environment.executable.as_ref()?).ok()?;                let sys_prefix = toolchain.environment.prefix.clone()?;                let environment = json!({                    "executable": {                        "uri": uri,                        "sysPrefix": sys_prefix                    }                });                ret.as_object_mut()?                    .entry("pythonExtension")                    .or_insert_with(|| json!({ "activeEnvironment": environment }));                Some(())            });        }        Ok(json!({"ty": ret}))    }} impl LspInstaller for TyLspAdapter {    type BinaryVersion = GitHubLspBinaryVersion;    async fn fetch_latest_server_version(        &self,        delegate: &dyn LspAdapterDelegate,        _: bool,        _: &mut AsyncApp,    ) -> Result<Self::BinaryVersion> {        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(GitHubLspBinaryVersion {            name: release.tag_name,            url: asset.browser_download_url,            digest: asset.digest,        })    }     async fn fetch_server_binary(        &self,        latest_version: Self::BinaryVersion,        container_dir: PathBuf,        delegate: &dyn LspAdapterDelegate,    ) -> Result<LanguageServerBinary> {        let GitHubLspBinaryVersion {            name,            url,            digest: expected_digest,        } = latest_version;        let destination_path = container_dir.join(format!("ty-{name}"));         async_fs::create_dir_all(&destination_path).await?;         let server_path = match Self::GITHUB_ASSET_KIND {            AssetKind::TarGz | AssetKind::Gz => destination_path                .join(Self::build_asset_name()?.0)                .join("ty"),            AssetKind::Zip => destination_path.clone().join("ty.exe"),        };         let binary = LanguageServerBinary {            path: server_path.clone(),            env: None,            arguments: vec!["server".into()],        };         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.http_client(),            &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: vec!["server".into()],        })    }     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.join(Self::build_asset_name()?.0).join("ty")                }                AssetKind::Zip => path.join("ty.exe"),            };             anyhow::Ok(LanguageServerBinary {                path,                env: None,                arguments: vec!["server".into()],            })        })        .await        .log_err()    }} pub struct PyrightLspAdapter {    node: NodeRuntime,} impl PyrightLspAdapter {    const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pyright");    const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js";    const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js";     pub fn new(node: NodeRuntime) -> Self {        PyrightLspAdapter { node }    }     async fn get_cached_server_binary(        container_dir: PathBuf,        node: &NodeRuntime,    ) -> Option<LanguageServerBinary> {        let server_path = container_dir.join(Self::SERVER_PATH);        if server_path.exists() {            Some(LanguageServerBinary {                path: node.binary_path().await.log_err()?,                env: None,                arguments: vec![server_path.into(), "--stdio".into()],            })        } else {            log::error!("missing executable in directory {:?}", server_path);            None        }    }} #[async_trait(?Send)]impl LspAdapter for PyrightLspAdapter {    fn name(&self) -> LanguageServerName {        Self::SERVER_NAME    }     async fn initialization_options(        self: Arc<Self>,        _: &Arc<dyn LspAdapterDelegate>,    ) -> Result<Option<Value>> {        // Provide minimal initialization options        // Virtual environment configuration will be handled through workspace configuration        Ok(Some(json!({            "python": {                "analysis": {                    "autoSearchPaths": true,                    "useLibraryCodeForTypes": true,                    "autoImportCompletions": true                }            }        })))    }     async fn process_completions(&self, items: &mut [lsp::CompletionItem]) {        process_pyright_completions(items);    }     async fn label_for_completion(        &self,        item: &lsp::CompletionItem,        language: &Arc<language::Language>,    ) -> Option<language::CodeLabel> {        let label = &item.label;        let label_len = label.len();        let grammar = language.grammar()?;        let highlight_id = match item.kind? {            lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method"),            lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function"),            lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type"),            lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant"),            lsp::CompletionItemKind::VARIABLE => grammar.highlight_id_for_name("variable"),            _ => {                return None;            }        };        let mut text = label.clone();        if let Some(completion_details) = item            .label_details            .as_ref()            .and_then(|details| details.description.as_ref())        {            write!(&mut text, " {}", completion_details).ok();        }        Some(language::CodeLabel::filtered(            text,            label_len,            item.filter_text.as_deref(),            highlight_id                .map(|id| (0..label_len, id))                .into_iter()                .collect(),        ))    }     async fn label_for_symbol(        &self,        name: &str,        kind: lsp::SymbolKind,        language: &Arc<language::Language>,    ) -> Option<language::CodeLabel> {        let (text, filter_range, display_range) = match kind {            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {                let text = format!("def {}():\n", name);                let filter_range = 4..4 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::CLASS => {                let text = format!("class {}:", name);                let filter_range = 6..6 + name.len();                let display_range = 0..filter_range.end;                (text, filter_range, display_range)            }            lsp::SymbolKind::CONSTANT => {

Lines 1–500 of 2438.