diff --git a/Cargo.lock b/Cargo.lock index 8e292181c2..4b82b38762 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4100,6 +4100,7 @@ dependencies = [ "anyhow", "async-trait", "dap", + "futures 0.3.31", "gpui", "language", "lsp-types", diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 2409865285..f1cb1f0952 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -84,19 +84,6 @@ impl ActivityIndicator { }) .detach(); - let mut status_events = languages.dap_server_binary_statuses(); - cx.spawn(async move |this, cx| { - while let Some((name, status)) = status_events.next().await { - this.update(cx, |this, cx| { - this.statuses.retain(|s| s.name != name); - this.statuses.push(ServerStatus { name, status }); - cx.notify(); - })?; - } - anyhow::Ok(()) - }) - .detach(); - cx.subscribe( &project.read(cx).lsp_store(), |_, _, event, cx| match event { diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 073b852778..3cd8bb9fb7 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -12,10 +12,9 @@ use language::LanguageToolchainStore; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use settings::WorktreeId; -use smol::{self, fs::File, lock::Mutex}; +use smol::{self, fs::File}; use std::{ borrow::Borrow, - collections::HashSet, ffi::OsStr, fmt::Debug, net::Ipv4Addr, @@ -24,7 +23,6 @@ use std::{ sync::Arc, }; use task::{AttachRequest, DebugRequest, DebugScenario, LaunchRequest, TcpArgumentsTemplate}; -use util::ResultExt; #[derive(Clone, Debug, PartialEq, Eq)] pub enum DapStatus { @@ -41,8 +39,7 @@ pub trait DapDelegate { fn node_runtime(&self) -> NodeRuntime; fn toolchain_store(&self) -> Arc; fn fs(&self) -> Arc; - fn updated_adapters(&self) -> Arc>>; - fn update_status(&self, dap_name: DebugAdapterName, status: DapStatus); + fn output_to_console(&self, msg: String); fn which(&self, command: &OsStr) -> Option; async fn shell_env(&self) -> collections::HashMap; } @@ -293,7 +290,7 @@ impl DebugAdapterBinary { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct AdapterVersion { pub tag_name: String, pub url: String, @@ -335,6 +332,7 @@ pub async fn download_adapter_from_github( adapter_name, &github_version.url, ); + delegate.output_to_console(format!("Downloading from {}...", github_version.url)); let mut response = delegate .http_client() @@ -418,81 +416,6 @@ pub trait DebugAdapter: 'static + Send + Sync { config: &DebugTaskDefinition, user_installed_path: Option, cx: &mut AsyncApp, - ) -> Result { - if delegate - .updated_adapters() - .lock() - .await - .contains(&self.name()) - { - log::info!("Using cached debug adapter binary {}", self.name()); - - if let Some(binary) = self - .get_installed_binary(delegate, &config, user_installed_path.clone(), cx) - .await - .log_err() - { - return Ok(binary); - } - - log::info!( - "Cached binary {} is corrupt falling back to install", - self.name() - ); - } - - log::info!("Getting latest version of debug adapter {}", self.name()); - delegate.update_status(self.name(), DapStatus::CheckingForUpdate); - if let Some(version) = self.fetch_latest_adapter_version(delegate).await.log_err() { - log::info!("Installing latest version of debug adapter {}", self.name()); - delegate.update_status(self.name(), DapStatus::Downloading); - match self.install_binary(version, delegate).await { - Ok(_) => { - delegate.update_status(self.name(), DapStatus::None); - } - Err(error) => { - delegate.update_status( - self.name(), - DapStatus::Failed { - error: error.to_string(), - }, - ); - - return Err(error); - } - } - - delegate - .updated_adapters() - .lock_arc() - .await - .insert(self.name()); - } - - self.get_installed_binary(delegate, &config, user_installed_path, cx) - .await - } - - async fn fetch_latest_adapter_version( - &self, - delegate: &dyn DapDelegate, - ) -> Result; - - /// Installs the binary for the debug adapter. - /// This method is called when the adapter binary is not found or needs to be updated. - /// It should download and install the necessary files for the debug adapter to function. - async fn install_binary( - &self, - version: AdapterVersion, - delegate: &dyn DapDelegate, - ) -> Result<()>; - - async fn get_installed_binary( - &self, - delegate: &dyn DapDelegate, - config: &DebugTaskDefinition, - user_installed_path: Option, - cx: &mut AsyncApp, ) -> Result; fn inline_value_provider(&self) -> Option> { @@ -561,29 +484,4 @@ impl DebugAdapter for FakeAdapter { request_args: self.request_args(config), }) } - - async fn fetch_latest_adapter_version( - &self, - _delegate: &dyn DapDelegate, - ) -> Result { - unimplemented!("fetch latest adapter version"); - } - - async fn install_binary( - &self, - _version: AdapterVersion, - _delegate: &dyn DapDelegate, - ) -> Result<()> { - unimplemented!("install binary"); - } - - async fn get_installed_binary( - &self, - _: &dyn DapDelegate, - _: &DebugTaskDefinition, - _: Option, - _: &mut AsyncApp, - ) -> Result { - unimplemented!("get installed binary"); - } } diff --git a/crates/dap_adapters/Cargo.toml b/crates/dap_adapters/Cargo.toml index ba461c3e68..794db55feb 100644 --- a/crates/dap_adapters/Cargo.toml +++ b/crates/dap_adapters/Cargo.toml @@ -24,6 +24,7 @@ doctest = false anyhow.workspace = true async-trait.workspace = true dap.workspace = true +futures.workspace = true gpui.workspace = true language.workspace = true lsp-types.workspace = true diff --git a/crates/dap_adapters/src/codelldb.rs b/crates/dap_adapters/src/codelldb.rs index 78a376c3c0..8acfec1b5e 100644 --- a/crates/dap_adapters/src/codelldb.rs +++ b/crates/dap_adapters/src/codelldb.rs @@ -1,16 +1,18 @@ use std::{collections::HashMap, path::PathBuf, sync::OnceLock}; -use anyhow::{Result, bail}; +use anyhow::Result; use async_trait::async_trait; use dap::adapters::{DebugTaskDefinition, InlineValueProvider, latest_github_release}; +use futures::StreamExt; use gpui::AsyncApp; use task::DebugRequest; +use util::fs::remove_matching; use crate::*; #[derive(Default)] pub(crate) struct CodeLldbDebugAdapter { - last_known_version: OnceLock, + path_to_codelldb: OnceLock, } impl CodeLldbDebugAdapter { @@ -54,29 +56,6 @@ impl CodeLldbDebugAdapter { configuration, } } -} - -#[async_trait(?Send)] -impl DebugAdapter for CodeLldbDebugAdapter { - fn name(&self) -> DebugAdapterName { - DebugAdapterName(Self::ADAPTER_NAME.into()) - } - - async fn install_binary( - &self, - version: AdapterVersion, - delegate: &dyn DapDelegate, - ) -> Result<()> { - adapters::download_adapter_from_github( - self.name(), - version, - adapters::DownloadedFileType::Vsix, - delegate, - ) - .await?; - - Ok(()) - } async fn fetch_latest_adapter_version( &self, @@ -107,7 +86,6 @@ impl DebugAdapter for CodeLldbDebugAdapter { } }; let asset_name = format!("codelldb-{platform}-{arch}.vsix"); - let _ = self.last_known_version.set(release.tag_name.clone()); let ret = AdapterVersion { tag_name: release.tag_name, url: release @@ -121,28 +99,56 @@ impl DebugAdapter for CodeLldbDebugAdapter { Ok(ret) } +} - async fn get_installed_binary( +#[async_trait(?Send)] +impl DebugAdapter for CodeLldbDebugAdapter { + fn name(&self) -> DebugAdapterName { + DebugAdapterName(Self::ADAPTER_NAME.into()) + } + + async fn get_binary( &self, - _: &dyn DapDelegate, + delegate: &dyn DapDelegate, config: &DebugTaskDefinition, - _: Option, + user_installed_path: Option, _: &mut AsyncApp, ) -> Result { - let Some(version) = self.last_known_version.get() else { - bail!("Could not determine latest CodeLLDB version"); - }; - let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME); - let version_path = adapter_path.join(format!("{}_{}", Self::ADAPTER_NAME, version)); + let mut command = user_installed_path + .map(|p| p.to_string_lossy().to_string()) + .or(self.path_to_codelldb.get().cloned()); + + if command.is_none() { + delegate.output_to_console(format!("Checking latest version of {}...", self.name())); + let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME); + let version_path = + if let Ok(version) = self.fetch_latest_adapter_version(delegate).await { + adapters::download_adapter_from_github( + self.name(), + version.clone(), + adapters::DownloadedFileType::Vsix, + delegate, + ) + .await?; + let version_path = + adapter_path.join(format!("{}_{}", Self::ADAPTER_NAME, version.tag_name)); + remove_matching(&adapter_path, |entry| entry != version_path).await; + version_path + } else { + let mut paths = delegate.fs().read_dir(&adapter_path).await?; + paths + .next() + .await + .ok_or_else(|| anyhow!("No adapter found"))?? + }; + let adapter_dir = version_path.join("extension").join("adapter"); + let path = adapter_dir.join("codelldb").to_string_lossy().to_string(); + self.path_to_codelldb.set(path.clone()).ok(); + command = Some(path); + }; - let adapter_dir = version_path.join("extension").join("adapter"); - let command = adapter_dir.join("codelldb"); - let command = command - .to_str() - .map(ToOwned::to_owned) - .ok_or_else(|| anyhow!("Adapter path is expected to be valid UTF-8"))?; Ok(DebugAdapterBinary { - command, + command: command.unwrap(), cwd: None, arguments: vec![ "--settings".into(), diff --git a/crates/dap_adapters/src/dap_adapters.rs b/crates/dap_adapters/src/dap_adapters.rs index 25e9c32679..93af93c6b9 100644 --- a/crates/dap_adapters/src/dap_adapters.rs +++ b/crates/dap_adapters/src/dap_adapters.rs @@ -29,9 +29,9 @@ use task::TcpArgumentsTemplate; pub fn init(cx: &mut App) { cx.update_default_global(|registry: &mut DapRegistry, _cx| { registry.add_adapter(Arc::from(CodeLldbDebugAdapter::default())); - registry.add_adapter(Arc::from(PythonDebugAdapter)); - registry.add_adapter(Arc::from(PhpDebugAdapter)); - registry.add_adapter(Arc::from(JsDebugAdapter)); + registry.add_adapter(Arc::from(PythonDebugAdapter::default())); + registry.add_adapter(Arc::from(PhpDebugAdapter::default())); + registry.add_adapter(Arc::from(JsDebugAdapter::default())); registry.add_adapter(Arc::from(GoDebugAdapter)); registry.add_adapter(Arc::from(GdbDebugAdapter)); }) diff --git a/crates/dap_adapters/src/gdb.rs b/crates/dap_adapters/src/gdb.rs index 028dc56081..7e8ef46626 100644 --- a/crates/dap_adapters/src/gdb.rs +++ b/crates/dap_adapters/src/gdb.rs @@ -90,26 +90,4 @@ impl DebugAdapter for GdbDebugAdapter { request_args: self.request_args(config), }) } - - async fn install_binary( - &self, - _version: AdapterVersion, - _delegate: &dyn DapDelegate, - ) -> Result<()> { - unimplemented!("GDB debug adapter cannot be installed by Zed (yet)") - } - - async fn fetch_latest_adapter_version(&self, _: &dyn DapDelegate) -> Result { - unimplemented!("Fetch latest GDB version not implemented (yet)") - } - - async fn get_installed_binary( - &self, - _: &dyn DapDelegate, - _: &DebugTaskDefinition, - _: Option, - _: &mut AsyncApp, - ) -> Result { - unimplemented!("GDB cannot be installed by Zed (yet)") - } } diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index ea69047e6c..8ad885ef4d 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -46,41 +46,8 @@ impl DebugAdapter for GoDebugAdapter { &self, delegate: &dyn DapDelegate, config: &DebugTaskDefinition, - user_installed_path: Option, - cx: &mut AsyncApp, - ) -> Result { - self.get_installed_binary(delegate, config, user_installed_path, cx) - .await - } - - async fn fetch_latest_adapter_version( - &self, - _delegate: &dyn DapDelegate, - ) -> Result { - unimplemented!("This adapter is used from path for now"); - } - - async fn install_binary( - &self, - version: AdapterVersion, - delegate: &dyn DapDelegate, - ) -> Result<()> { - adapters::download_adapter_from_github( - self.name(), - version, - adapters::DownloadedFileType::Zip, - delegate, - ) - .await?; - Ok(()) - } - - async fn get_installed_binary( - &self, - delegate: &dyn DapDelegate, - config: &DebugTaskDefinition, - _: Option, - _: &mut AsyncApp, + _user_installed_path: Option, + _cx: &mut AsyncApp, ) -> Result { let delve_path = delegate .which(OsStr::new("dlv")) diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index 825c3661ae..b122b0d193 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -1,13 +1,16 @@ use adapters::latest_github_release; use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition}; use gpui::AsyncApp; -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, path::PathBuf, sync::OnceLock}; use task::DebugRequest; +use util::ResultExt; use crate::*; -#[derive(Debug)] -pub(crate) struct JsDebugAdapter; +#[derive(Debug, Default)] +pub(crate) struct JsDebugAdapter { + checked: OnceLock<()>, +} impl JsDebugAdapter { const ADAPTER_NAME: &'static str = "JavaScript"; @@ -47,13 +50,6 @@ impl JsDebugAdapter { request: config.request.to_dap(), } } -} - -#[async_trait(?Send)] -impl DebugAdapter for JsDebugAdapter { - fn name(&self) -> DebugAdapterName { - DebugAdapterName(Self::ADAPTER_NAME.into()) - } async fn fetch_latest_adapter_version( &self, @@ -130,20 +126,35 @@ impl DebugAdapter for JsDebugAdapter { request_args: self.request_args(config), }) } +} - async fn install_binary( +#[async_trait(?Send)] +impl DebugAdapter for JsDebugAdapter { + fn name(&self) -> DebugAdapterName { + DebugAdapterName(Self::ADAPTER_NAME.into()) + } + + async fn get_binary( &self, - version: AdapterVersion, delegate: &dyn DapDelegate, - ) -> Result<()> { - adapters::download_adapter_from_github( - self.name(), - version, - adapters::DownloadedFileType::GzipTar, - delegate, - ) - .await?; + config: &DebugTaskDefinition, + user_installed_path: Option, + cx: &mut AsyncApp, + ) -> Result { + if self.checked.set(()).is_ok() { + delegate.output_to_console(format!("Checking latest version of {}...", self.name())); + if let Some(version) = self.fetch_latest_adapter_version(delegate).await.log_err() { + adapters::download_adapter_from_github( + self.name(), + version, + adapters::DownloadedFileType::GzipTar, + delegate, + ) + .await?; + } + } - return Ok(()); + self.get_installed_binary(delegate, &config, user_installed_path, cx) + .await } } diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index 2024dea0bf..7b07d76689 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -1,12 +1,15 @@ use adapters::latest_github_release; use dap::adapters::{DebugTaskDefinition, TcpArguments}; use gpui::AsyncApp; -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, path::PathBuf, sync::OnceLock}; +use util::ResultExt; use crate::*; #[derive(Default)] -pub(crate) struct PhpDebugAdapter; +pub(crate) struct PhpDebugAdapter { + checked: OnceLock<()>, +} impl PhpDebugAdapter { const ADAPTER_NAME: &'static str = "PHP"; @@ -32,13 +35,6 @@ impl PhpDebugAdapter { }), } } -} - -#[async_trait(?Send)] -impl DebugAdapter for PhpDebugAdapter { - fn name(&self) -> DebugAdapterName { - DebugAdapterName(Self::ADAPTER_NAME.into()) - } async fn fetch_latest_adapter_version( &self, @@ -114,20 +110,35 @@ impl DebugAdapter for PhpDebugAdapter { request_args: self.request_args(config)?, }) } +} - async fn install_binary( +#[async_trait(?Send)] +impl DebugAdapter for PhpDebugAdapter { + fn name(&self) -> DebugAdapterName { + DebugAdapterName(Self::ADAPTER_NAME.into()) + } + + async fn get_binary( &self, - version: AdapterVersion, delegate: &dyn DapDelegate, - ) -> Result<()> { - adapters::download_adapter_from_github( - self.name(), - version, - adapters::DownloadedFileType::Vsix, - delegate, - ) - .await?; + config: &DebugTaskDefinition, + user_installed_path: Option, + cx: &mut AsyncApp, + ) -> Result { + if self.checked.set(()).is_ok() { + delegate.output_to_console(format!("Checking latest version of {}...", self.name())); + if let Some(version) = self.fetch_latest_adapter_version(delegate).await.log_err() { + adapters::download_adapter_from_github( + self.name(), + version, + adapters::DownloadedFileType::Vsix, + delegate, + ) + .await?; + } + } - Ok(()) + self.get_installed_binary(delegate, &config, user_installed_path, cx) + .await } } diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 8ab088b8c6..f1eb24c6e8 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -4,10 +4,13 @@ use dap::{ adapters::InlineValueProvider, }; use gpui::AsyncApp; -use std::{collections::HashMap, ffi::OsStr, path::PathBuf}; +use std::{collections::HashMap, ffi::OsStr, path::PathBuf, sync::OnceLock}; +use util::ResultExt; #[derive(Default)] -pub(crate) struct PythonDebugAdapter; +pub(crate) struct PythonDebugAdapter { + checked: OnceLock<()>, +} impl PythonDebugAdapter { const ADAPTER_NAME: &'static str = "Debugpy"; @@ -46,14 +49,6 @@ impl PythonDebugAdapter { request: config.request.to_dap(), } } -} - -#[async_trait(?Send)] -impl DebugAdapter for PythonDebugAdapter { - fn name(&self) -> DebugAdapterName { - DebugAdapterName(Self::ADAPTER_NAME.into()) - } - async fn fetch_latest_adapter_version( &self, delegate: &dyn DapDelegate, @@ -162,6 +157,31 @@ impl DebugAdapter for PythonDebugAdapter { request_args: self.request_args(config), }) } +} + +#[async_trait(?Send)] +impl DebugAdapter for PythonDebugAdapter { + fn name(&self) -> DebugAdapterName { + DebugAdapterName(Self::ADAPTER_NAME.into()) + } + + async fn get_binary( + &self, + delegate: &dyn DapDelegate, + config: &DebugTaskDefinition, + user_installed_path: Option, + cx: &mut AsyncApp, + ) -> Result { + if self.checked.set(()).is_ok() { + delegate.output_to_console(format!("Checking latest version of {}...", self.name())); + if let Some(version) = self.fetch_latest_adapter_version(delegate).await.log_err() { + self.install_binary(version, delegate).await?; + } + } + + self.get_installed_binary(delegate, &config, user_installed_path, cx) + .await + } fn inline_value_provider(&self) -> Option> { Some(Box::new(PythonInlineValueProvider)) diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index d87c6db5dd..3213fd2514 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -104,7 +104,6 @@ pub struct LanguageRegistry { language_server_download_dir: Option>, executor: BackgroundExecutor, lsp_binary_status_tx: BinaryStatusSender, - dap_binary_status_tx: BinaryStatusSender, } struct LanguageRegistryState { @@ -269,7 +268,6 @@ impl LanguageRegistry { }), language_server_download_dir: None, lsp_binary_status_tx: Default::default(), - dap_binary_status_tx: Default::default(), executor, }; this.add(PLAIN_TEXT.clone()); @@ -986,10 +984,6 @@ impl LanguageRegistry { self.lsp_binary_status_tx.send(server_name.0, status); } - pub fn update_dap_status(&self, server_name: LanguageServerName, status: BinaryStatus) { - self.dap_binary_status_tx.send(server_name.0, status); - } - pub fn next_language_server_id(&self) -> LanguageServerId { self.state.write().next_language_server_id() } @@ -1046,12 +1040,6 @@ impl LanguageRegistry { self.lsp_binary_status_tx.subscribe() } - pub fn dap_server_binary_statuses( - &self, - ) -> mpsc::UnboundedReceiver<(SharedString, BinaryStatus)> { - self.dap_binary_status_tx.subscribe() - } - pub async fn delete_server_container(&self, name: LanguageServerName) { log::info!("deleting server container"); let Some(dir) = self.language_server_download_dir(&name) else { diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 11cc0c92fb..be9139318c 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -16,22 +16,20 @@ use collections::HashMap; use dap::{ Capabilities, CompletionItem, CompletionsArguments, DapRegistry, DebugRequest, EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, Source, StackFrameId, - adapters::{ - DapStatus, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, TcpArguments, - }, + adapters::{DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, TcpArguments}, client::SessionId, messages::Message, requests::{Completions, Evaluate}, }; use fs::Fs; -use futures::future::{Shared, join_all}; +use futures::{ + StreamExt, + channel::mpsc::{self, UnboundedSender}, + future::{Shared, join_all}, +}; use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task}; use http_client::HttpClient; -use language::{ - BinaryStatus, Buffer, LanguageRegistry, LanguageToolchainStore, - language_settings::InlayHintKind, range_from_lsp, -}; -use lsp::LanguageServerName; +use language::{Buffer, LanguageToolchainStore, language_settings::InlayHintKind, range_from_lsp}; use node_runtime::NodeRuntime; use remote::SshRemoteClient; @@ -40,10 +38,9 @@ use rpc::{ proto::{self}, }; use settings::{Settings, WorktreeId}; -use smol::lock::Mutex; use std::{ borrow::Borrow, - collections::{BTreeMap, HashSet}, + collections::BTreeMap, ffi::OsStr, net::Ipv4Addr, path::{Path, PathBuf}, @@ -78,7 +75,6 @@ pub struct LocalDapStore { node_runtime: NodeRuntime, http_client: Arc, environment: Entity, - language_registry: Arc, toolchain_store: Arc, } @@ -102,12 +98,13 @@ impl EventEmitter for DapStore {} impl DapStore { pub fn init(client: &AnyProtoClient, cx: &mut App) { static ADD_LOCATORS: Once = Once::new(); - client.add_entity_request_handler(Self::handle_run_debug_locator); - client.add_entity_request_handler(Self::handle_get_debug_adapter_binary); ADD_LOCATORS.call_once(|| { DapRegistry::global(cx) .add_locator("cargo".into(), Arc::new(locators::cargo::CargoLocator {})) }); + client.add_entity_request_handler(Self::handle_run_debug_locator); + client.add_entity_request_handler(Self::handle_get_debug_adapter_binary); + client.add_entity_message_handler(Self::handle_log_to_debug_console); } #[expect(clippy::too_many_arguments)] @@ -115,7 +112,6 @@ impl DapStore { http_client: Arc, node_runtime: NodeRuntime, fs: Arc, - language_registry: Arc, environment: Entity, toolchain_store: Arc, worktree_store: Entity, @@ -128,7 +124,6 @@ impl DapStore { http_client, node_runtime, toolchain_store, - language_registry, }); Self::new(mode, breakpoint_store, worktree_store, cx) @@ -179,6 +174,8 @@ impl DapStore { pub fn get_debug_adapter_binary( &mut self, definition: DebugTaskDefinition, + session_id: SessionId, + console: UnboundedSender, cx: &mut Context, ) -> Task> { match &self.mode { @@ -196,7 +193,7 @@ impl DapStore { .get(&adapter.name()) .and_then(|s| s.binary.as_ref().map(PathBuf::from)); - let delegate = self.delegate(&worktree, cx); + let delegate = self.delegate(&worktree, console, cx); let cwd: Arc = definition .cwd() .unwrap_or(worktree.read(cx).abs_path().as_ref()) @@ -228,6 +225,7 @@ impl DapStore { } DapStoreMode::Ssh(ssh) => { let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary { + session_id: session_id.to_proto(), project_id: ssh.upstream_project_id, definition: Some(definition.to_proto()), }); @@ -445,13 +443,15 @@ impl DapStore { }; let dap_store = cx.weak_entity(); + let console = session.update(cx, |session, cx| session.console_output(cx)); + let session_id = session.read(cx).session_id(); cx.spawn({ let session = session.clone(); async move |this, cx| { let mut binary = this .update(cx, |this, cx| { - this.get_debug_adapter_binary(definition.clone(), cx) + this.get_debug_adapter_binary(definition.clone(), session_id, console, cx) })? .await?; @@ -522,7 +522,12 @@ impl DapStore { Ok(()) } - fn delegate(&self, worktree: &Entity, cx: &mut App) -> DapAdapterDelegate { + fn delegate( + &self, + worktree: &Entity, + console: UnboundedSender, + cx: &mut App, + ) -> DapAdapterDelegate { let Some(local_store) = self.as_local() else { unimplemented!("Starting session on remote side"); }; @@ -530,9 +535,9 @@ impl DapStore { DapAdapterDelegate::new( local_store.fs.clone(), worktree.read(cx).id(), + console, local_store.node_runtime.clone(), local_store.http_client.clone(), - local_store.language_registry.clone(), local_store.toolchain_store.clone(), local_store.environment.update(cx, |env, cx| { env.get_worktree_environment(worktree.clone(), cx) @@ -802,24 +807,65 @@ impl DapStore { .definition .ok_or_else(|| anyhow!("missing definition"))?, )?; + let (tx, mut rx) = mpsc::unbounded(); + let session_id = envelope.payload.session_id; + cx.spawn({ + let this = this.clone(); + async move |cx| { + while let Some(message) = rx.next().await { + this.update(cx, |this, _| { + if let Some((downstream, project_id)) = this.downstream_client.clone() { + downstream + .send(proto::LogToDebugConsole { + project_id, + session_id, + message, + }) + .ok(); + } + }) + .ok(); + } + } + }) + .detach(); + let binary = this .update(&mut cx, |this, cx| { - this.get_debug_adapter_binary(definition, cx) + this.get_debug_adapter_binary(definition, SessionId::from_proto(session_id), tx, cx) })? .await?; Ok(binary.to_proto()) } + + async fn handle_log_to_debug_console( + this: Entity, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result<()> { + let session_id = SessionId::from_proto(envelope.payload.session_id); + this.update(&mut cx, |this, cx| { + let Some(session) = this.sessions.get(&session_id) else { + return; + }; + session.update(cx, |session, cx| { + session + .console_output(cx) + .unbounded_send(envelope.payload.message) + .ok(); + }) + }) + } } #[derive(Clone)] pub struct DapAdapterDelegate { fs: Arc, + console: mpsc::UnboundedSender, worktree_id: WorktreeId, node_runtime: NodeRuntime, http_client: Arc, - language_registry: Arc, toolchain_store: Arc, - updated_adapters: Arc>>, load_shell_env_task: Shared>>>, } @@ -827,21 +873,20 @@ impl DapAdapterDelegate { pub fn new( fs: Arc, worktree_id: WorktreeId, + status: mpsc::UnboundedSender, node_runtime: NodeRuntime, http_client: Arc, - language_registry: Arc, toolchain_store: Arc, load_shell_env_task: Shared>>>, ) -> Self { Self { fs, + console: status, worktree_id, http_client, node_runtime, toolchain_store, - language_registry, load_shell_env_task, - updated_adapters: Default::default(), } } } @@ -864,21 +909,8 @@ impl dap::adapters::DapDelegate for DapAdapterDelegate { self.fs.clone() } - fn updated_adapters(&self) -> Arc>> { - self.updated_adapters.clone() - } - - fn update_status(&self, dap_name: DebugAdapterName, status: dap::adapters::DapStatus) { - let name = SharedString::from(dap_name.to_string()); - let status = match status { - DapStatus::None => BinaryStatus::None, - DapStatus::Downloading => BinaryStatus::Downloading, - DapStatus::Failed { error } => BinaryStatus::Failed { error }, - DapStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate, - }; - - self.language_registry - .update_dap_status(LanguageServerName(name), status); + fn output_to_console(&self, msg: String) { + self.console.unbounded_send(msg).ok(); } fn which(&self, command: &OsStr) -> Option { diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index 33caa192c4..946073333c 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -170,7 +170,7 @@ impl LocalMode { } else { DebugAdapterClient::start(session_id, binary.clone(), message_handler, cx.clone()) .await - .with_context(|| "Failed to start communication with debug adapter")? + .with_context(|| format!("Failed to start {:?}", &binary.command))? }, ); @@ -815,6 +815,33 @@ impl Session { self.is_session_terminated } + pub fn console_output(&mut self, cx: &mut Context) -> mpsc::UnboundedSender { + let (tx, mut rx) = mpsc::unbounded(); + + cx.spawn(async move |this, cx| { + while let Some(output) = rx.next().await { + this.update(cx, |this, _| { + this.output_token.0 += 1; + this.output.push_back(dap::OutputEvent { + category: None, + output, + group: None, + variables_reference: None, + source: None, + line: None, + column: None, + data: None, + location_reference: None, + }); + })?; + } + anyhow::Ok(()) + }) + .detach(); + + return tx; + } + pub fn is_local(&self) -> bool { matches!(self.mode, Mode::Running(_)) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7c693cca5c..38bfb084c2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -894,7 +894,6 @@ impl Project { client.http_client(), node.clone(), fs.clone(), - languages.clone(), environment.clone(), toolchain_store.read(cx).as_language_toolchain_store(), worktree_store.clone(), diff --git a/crates/proto/proto/debugger.proto b/crates/proto/proto/debugger.proto index 9337d36f5f..8a8f037413 100644 --- a/crates/proto/proto/debugger.proto +++ b/crates/proto/proto/debugger.proto @@ -559,6 +559,7 @@ message DapModuleId { message GetDebugAdapterBinary { uint64 project_id = 1; + uint64 session_id = 3; DebugTaskDefinition definition = 2; } @@ -605,3 +606,9 @@ message SpawnInTerminal { map env = 4; optional string cwd = 5; } + +message LogToDebugConsole { + uint64 project_id = 1; + uint64 session_id = 2; + string message = 3; +} diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 822f1cd9e5..8bf418b10b 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -384,7 +384,9 @@ message Envelope { LspExtGoToParentModuleResponse lsp_ext_go_to_parent_module_response = 344; LspExtCancelFlycheck lsp_ext_cancel_flycheck = 345; LspExtRunFlycheck lsp_ext_run_flycheck = 346; - LspExtClearFlycheck lsp_ext_clear_flycheck = 347; // current max + LspExtClearFlycheck lsp_ext_clear_flycheck = 347; + + LogToDebugConsole log_to_debug_console = 348; // current max } reserved 87 to 88; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index 34ee21d7dd..9c012a758f 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -305,6 +305,7 @@ messages!( (DebugAdapterBinary, Background), (RunDebugLocators, Background), (DebugRequest, Background), + (LogToDebugConsole, Background), ); request_messages!( @@ -591,6 +592,7 @@ entity_messages!( ToggleBreakpoint, RunDebugLocators, GetDebugAdapterBinary, + LogToDebugConsole, ); entity_messages!( diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 9854f6f7e9..08ac6fc70b 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -106,17 +106,18 @@ impl HeadlessProject { cx.new(|_| BreakpointStore::local(worktree_store.clone(), buffer_store.clone())); let dap_store = cx.new(|cx| { - DapStore::new_local( + let mut dap_store = DapStore::new_local( http_client.clone(), node_runtime.clone(), fs.clone(), - languages.clone(), environment.clone(), toolchain_store.read(cx).as_language_toolchain_store(), worktree_store.clone(), breakpoint_store.clone(), cx, - ) + ); + dap_store.shared(SSH_PROJECT_ID, session.clone().into(), cx); + dap_store }); let git_store = cx.new(|cx| {