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

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(_))
}