Debug adapters log to console (#29957)

Closes #ISSUE

Release Notes:

- N/A
This commit is contained in:
Conrad Irwin 2025-05-06 11:21:34 +01:00 committed by GitHub
parent de554589a8
commit 68793c0ac2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 268 additions and 330 deletions

1
Cargo.lock generated
View file

@ -4100,6 +4100,7 @@ dependencies = [
"anyhow",
"async-trait",
"dap",
"futures 0.3.31",
"gpui",
"language",
"lsp-types",

View file

@ -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 {

View file

@ -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<dyn LanguageToolchainStore>;
fn fs(&self) -> Arc<dyn Fs>;
fn updated_adapters(&self) -> Arc<Mutex<HashSet<DebugAdapterName>>>;
fn update_status(&self, dap_name: DebugAdapterName, status: DapStatus);
fn output_to_console(&self, msg: String);
fn which(&self, command: &OsStr) -> Option<PathBuf>;
async fn shell_env(&self) -> collections::HashMap<String, String>;
}
@ -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<PathBuf>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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<AdapterVersion>;
/// 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<PathBuf>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary>;
fn inline_value_provider(&self) -> Option<Box<dyn InlineValueProvider>> {
@ -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<AdapterVersion> {
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<PathBuf>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
unimplemented!("get installed binary");
}
}

View file

@ -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

View file

@ -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<String>,
path_to_codelldb: OnceLock<String>,
}
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<PathBuf>,
user_installed_path: Option<PathBuf>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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(),

View file

@ -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));
})

View file

@ -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<AdapterVersion> {
unimplemented!("Fetch latest GDB version not implemented (yet)")
}
async fn get_installed_binary(
&self,
_: &dyn DapDelegate,
_: &DebugTaskDefinition,
_: Option<std::path::PathBuf>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
unimplemented!("GDB cannot be installed by Zed (yet)")
}
}

View file

@ -46,41 +46,8 @@ impl DebugAdapter for GoDebugAdapter {
&self,
delegate: &dyn DapDelegate,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
self.get_installed_binary(delegate, config, user_installed_path, cx)
.await
}
async fn fetch_latest_adapter_version(
&self,
_delegate: &dyn DapDelegate,
) -> Result<AdapterVersion> {
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<PathBuf>,
_: &mut AsyncApp,
_user_installed_path: Option<PathBuf>,
_cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let delve_path = delegate
.which(OsStr::new("dlv"))

View file

@ -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<PathBuf>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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
}
}

View file

@ -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<PathBuf>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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
}
}

View file

@ -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<PathBuf>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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<Box<dyn InlineValueProvider>> {
Some(Box::new(PythonInlineValueProvider))

View file

@ -104,7 +104,6 @@ pub struct LanguageRegistry {
language_server_download_dir: Option<Arc<Path>>,
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 {

View file

@ -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<dyn HttpClient>,
environment: Entity<ProjectEnvironment>,
language_registry: Arc<LanguageRegistry>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
}
@ -102,12 +98,13 @@ impl EventEmitter<DapStoreEvent> 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<dyn HttpClient>,
node_runtime: NodeRuntime,
fs: Arc<dyn Fs>,
language_registry: Arc<LanguageRegistry>,
environment: Entity<ProjectEnvironment>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
worktree_store: Entity<WorktreeStore>,
@ -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<String>,
cx: &mut Context<Self>,
) -> Task<Result<DebugAdapterBinary>> {
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<Path> = 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<Worktree>, cx: &mut App) -> DapAdapterDelegate {
fn delegate(
&self,
worktree: &Entity<Worktree>,
console: UnboundedSender<String>,
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<Self>,
envelope: TypedEnvelope<proto::LogToDebugConsole>,
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<dyn Fs>,
console: mpsc::UnboundedSender<String>,
worktree_id: WorktreeId,
node_runtime: NodeRuntime,
http_client: Arc<dyn HttpClient>,
language_registry: Arc<LanguageRegistry>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
updated_adapters: Arc<Mutex<HashSet<DebugAdapterName>>>,
load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
}
@ -827,21 +873,20 @@ impl DapAdapterDelegate {
pub fn new(
fs: Arc<dyn Fs>,
worktree_id: WorktreeId,
status: mpsc::UnboundedSender<String>,
node_runtime: NodeRuntime,
http_client: Arc<dyn HttpClient>,
language_registry: Arc<LanguageRegistry>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
) -> 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<Mutex<HashSet<DebugAdapterName>>> {
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<PathBuf> {

View file

@ -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<Self>) -> mpsc::UnboundedSender<String> {
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(_))
}

View file

@ -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(),

View file

@ -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<string, string> env = 4;
optional string cwd = 5;
}
message LogToDebugConsole {
uint64 project_id = 1;
uint64 session_id = 2;
string message = 3;
}

View file

@ -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;

View file

@ -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!(

View file

@ -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| {