ZIm/crates/project/src/debugger/dap_store.rs
Anthony Eid 862d0c07ca
debugger: Fix gdb adapter and logger (#28280)
There were two bugs that caused the gdb adapter not to work properly,
one on our end and one their end.

The bug on our end was sending `stopOnEntry: null` in our launch request
when stop on entry had no value. I fixed that bug across all dap
adapters

The other bug had to do with python's "great" type system and how we
serialized our unit structs to json; mainly,
`ConfigurationDoneArguments` and `ThreadsArguments`. Gdb seems to follow
a pattern for handling requests where they pass `**args` to a function,
this errors out when the equivalent json is `"arguments": null`.

```py
@capability("supportsConfigurationDoneRequest")
@request("configurationDone", on_dap_thread=True)
def config_done(**args): ### BUG!!
    ...
```

Release Notes:

- N/A
2025-04-07 22:02:13 +00:00

962 lines
31 KiB
Rust

use super::{
breakpoint_store::BreakpointStore,
locator_store::LocatorStore,
session::{self, Session},
};
use crate::{ProjectEnvironment, debugger, worktree_store::WorktreeStore};
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use collections::HashMap;
use dap::{
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse,
EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
Source, StartDebuggingRequestArguments,
adapters::{DapStatus, DebugAdapterName},
client::SessionId,
messages::Message,
requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},
};
use fs::Fs;
use futures::{
channel::{mpsc, oneshot},
future::{Shared, join_all},
};
use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};
use http_client::HttpClient;
use language::{BinaryStatus, LanguageRegistry, LanguageToolchainStore};
use lsp::LanguageServerName;
use node_runtime::NodeRuntime;
use rpc::{
AnyProtoClient, TypedEnvelope,
proto::{self},
};
use serde_json::Value;
use settings::WorktreeId;
use smol::{lock::Mutex, stream::StreamExt};
use std::{
borrow::Borrow,
collections::{BTreeMap, HashSet},
ffi::OsStr,
path::PathBuf,
sync::{Arc, atomic::Ordering::SeqCst},
};
use std::{collections::VecDeque, sync::atomic::AtomicU32};
use task::{DebugAdapterConfig, DebugRequestDisposition};
use util::ResultExt as _;
use worktree::Worktree;
pub enum DapStoreEvent {
DebugClientStarted(SessionId),
DebugSessionInitialized(SessionId),
DebugClientShutdown(SessionId),
DebugClientEvent {
session_id: SessionId,
message: Message,
},
RunInTerminal {
session_id: SessionId,
title: Option<String>,
cwd: PathBuf,
command: Option<String>,
args: Vec<String>,
envs: HashMap<String, String>,
sender: mpsc::Sender<Result<u32>>,
},
Notification(String),
RemoteHasInitialized,
}
#[allow(clippy::large_enum_variant)]
pub enum DapStoreMode {
Local(LocalDapStore), // ssh host and collab host
Remote(RemoteDapStore), // collab guest
}
pub struct LocalDapStore {
fs: Arc<dyn Fs>,
node_runtime: NodeRuntime,
next_session_id: AtomicU32,
http_client: Arc<dyn HttpClient>,
worktree_store: Entity<WorktreeStore>,
environment: Entity<ProjectEnvironment>,
language_registry: Arc<LanguageRegistry>,
debug_adapters: Arc<DapRegistry>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
locator_store: Arc<LocatorStore>,
start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
_start_debugging_task: Task<()>,
}
impl LocalDapStore {
fn next_session_id(&self) -> SessionId {
SessionId(self.next_session_id.fetch_add(1, SeqCst))
}
}
pub struct RemoteDapStore {
upstream_client: AnyProtoClient,
upstream_project_id: u64,
event_queue: Option<VecDeque<DapStoreEvent>>,
}
pub struct DapStore {
mode: DapStoreMode,
downstream_client: Option<(AnyProtoClient, u64)>,
breakpoint_store: Entity<BreakpointStore>,
sessions: BTreeMap<SessionId, Entity<Session>>,
}
impl EventEmitter<DapStoreEvent> for DapStore {}
impl DapStore {
pub fn init(_client: &AnyProtoClient) {
// todo(debugger): Reenable these after we finish handle_dap_command refactor
// client.add_entity_request_handler(Self::handle_dap_command::<NextCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<StepInCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<StepOutCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<StepBackCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<ContinueCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<PauseCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<DisconnectCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<TerminateThreadsCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<TerminateCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<RestartCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<VariablesCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<RestartStackFrameCommand>);
}
#[expect(clippy::too_many_arguments)]
pub fn new_local(
http_client: Arc<dyn HttpClient>,
node_runtime: NodeRuntime,
fs: Arc<dyn Fs>,
language_registry: Arc<LanguageRegistry>,
debug_adapters: Arc<DapRegistry>,
environment: Entity<ProjectEnvironment>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
breakpoint_store: Entity<BreakpointStore>,
worktree_store: Entity<WorktreeStore>,
cx: &mut Context<Self>,
) -> Self {
cx.on_app_quit(Self::shutdown_sessions).detach();
let (start_debugging_tx, mut message_rx) =
futures::channel::mpsc::unbounded::<(SessionId, Message)>();
let _start_debugging_task = cx.spawn(async move |this, cx| {
while let Some((session_id, message)) = message_rx.next().await {
match message {
Message::Request(request) => {
let _ = this
.update(cx, |this, cx| {
if request.command == StartDebugging::COMMAND {
this.handle_start_debugging_request(session_id, request, cx)
.detach_and_log_err(cx);
} else if request.command == RunInTerminal::COMMAND {
this.handle_run_in_terminal_request(session_id, request, cx)
.detach_and_log_err(cx);
}
})
.log_err();
}
_ => {}
}
}
});
Self {
mode: DapStoreMode::Local(LocalDapStore {
fs,
environment,
http_client,
node_runtime,
worktree_store,
toolchain_store,
language_registry,
debug_adapters,
start_debugging_tx,
_start_debugging_task,
locator_store: Arc::from(LocatorStore::new()),
next_session_id: Default::default(),
}),
downstream_client: None,
breakpoint_store,
sessions: Default::default(),
}
}
pub fn new_remote(
project_id: u64,
upstream_client: AnyProtoClient,
breakpoint_store: Entity<BreakpointStore>,
) -> Self {
Self {
mode: DapStoreMode::Remote(RemoteDapStore {
upstream_client,
upstream_project_id: project_id,
event_queue: Some(VecDeque::default()),
}),
downstream_client: None,
breakpoint_store,
sessions: Default::default(),
}
}
pub fn as_remote(&self) -> Option<&RemoteDapStore> {
match &self.mode {
DapStoreMode::Remote(remote_dap_store) => Some(remote_dap_store),
_ => None,
}
}
pub fn remote_event_queue(&mut self) -> Option<VecDeque<DapStoreEvent>> {
if let DapStoreMode::Remote(remote) = &mut self.mode {
remote.event_queue.take()
} else {
None
}
}
pub fn as_local(&self) -> Option<&LocalDapStore> {
match &self.mode {
DapStoreMode::Local(local_dap_store) => Some(local_dap_store),
_ => None,
}
}
pub fn as_local_mut(&mut self) -> Option<&mut LocalDapStore> {
match &mut self.mode {
DapStoreMode::Local(local_dap_store) => Some(local_dap_store),
_ => None,
}
}
pub fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> {
match &self.mode {
DapStoreMode::Remote(RemoteDapStore {
upstream_client,
upstream_project_id,
..
}) => Some((upstream_client.clone(), *upstream_project_id)),
DapStoreMode::Local(_) => None,
}
}
pub fn downstream_client(&self) -> Option<&(AnyProtoClient, u64)> {
self.downstream_client.as_ref()
}
pub fn add_remote_client(
&mut self,
session_id: SessionId,
ignore: Option<bool>,
cx: &mut Context<Self>,
) {
if let DapStoreMode::Remote(remote) = &self.mode {
self.sessions.insert(
session_id,
cx.new(|_| {
debugger::session::Session::remote(
session_id,
remote.upstream_client.clone(),
remote.upstream_project_id,
ignore.unwrap_or(false),
)
}),
);
} else {
debug_assert!(false);
}
}
pub fn session_by_id(
&self,
session_id: impl Borrow<SessionId>,
) -> Option<Entity<session::Session>> {
let session_id = session_id.borrow();
let client = self.sessions.get(session_id).cloned();
client
}
pub fn sessions(&self) -> impl Iterator<Item = &Entity<Session>> {
self.sessions.values()
}
pub fn capabilities_by_id(
&self,
session_id: impl Borrow<SessionId>,
cx: &App,
) -> Option<Capabilities> {
let session_id = session_id.borrow();
self.sessions
.get(session_id)
.map(|client| client.read(cx).capabilities.clone())
}
pub fn breakpoint_store(&self) -> &Entity<BreakpointStore> {
&self.breakpoint_store
}
#[allow(dead_code)]
async fn handle_ignore_breakpoint_state(
this: Entity<Self>,
envelope: TypedEnvelope<proto::IgnoreBreakpointState>,
mut cx: AsyncApp,
) -> Result<()> {
let session_id = SessionId::from_proto(envelope.payload.session_id);
this.update(&mut cx, |this, cx| {
if let Some(session) = this.session_by_id(&session_id) {
session.update(cx, |session, cx| {
session.set_ignore_breakpoints(envelope.payload.ignore, cx)
})
} else {
Task::ready(())
}
})?
.await;
Ok(())
}
pub fn new_session(
&mut self,
mut config: DebugAdapterConfig,
worktree: &Entity<Worktree>,
parent_session: Option<Entity<Session>>,
cx: &mut Context<Self>,
) -> (SessionId, Task<Result<Entity<Session>>>) {
let Some(local_store) = self.as_local() else {
unimplemented!("Starting session on remote side");
};
let delegate = DapAdapterDelegate::new(
local_store.fs.clone(),
worktree.read(cx).id(),
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| {
let worktree = worktree.read(cx);
env.get_environment(worktree.abs_path().into(), cx)
}),
);
let session_id = local_store.next_session_id();
if let Some(session) = &parent_session {
session.update(cx, |session, _| {
session.add_child_session_id(session_id);
});
}
let (initialized_tx, initialized_rx) = oneshot::channel();
let locator_store = local_store.locator_store.clone();
let debug_adapters = local_store.debug_adapters.clone();
let start_debugging_tx = local_store.start_debugging_tx.clone();
let task = cx.spawn(async move |this, cx| {
if config.locator.is_some() {
config = cx
.background_spawn(async move {
locator_store
.resolve_debug_config(&mut config)
.await
.map(|_| config)
})
.await?;
}
let start_client_task = this.update(cx, |this, cx| {
Session::local(
this.breakpoint_store.clone(),
session_id,
parent_session,
delegate,
config,
start_debugging_tx.clone(),
initialized_tx,
debug_adapters,
cx,
)
})?;
this.update(cx, |_, cx| {
create_new_session(session_id, initialized_rx, start_client_task, cx)
})?
.await
});
(session_id, task)
}
#[cfg(any(test, feature = "test-support"))]
pub fn new_fake_session(
&mut self,
config: DebugAdapterConfig,
worktree: &Entity<Worktree>,
parent_session: Option<Entity<Session>>,
caps: Capabilities,
fails: bool,
cx: &mut Context<Self>,
) -> (SessionId, Task<Result<Entity<Session>>>) {
let Some(local_store) = self.as_local() else {
unimplemented!("Starting session on remote side");
};
let delegate = DapAdapterDelegate::new(
local_store.fs.clone(),
worktree.read(cx).id(),
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| {
let worktree = worktree.read(cx);
env.get_environment(Some(worktree.abs_path()), cx)
}),
);
let session_id = local_store.next_session_id();
if let Some(session) = &parent_session {
session.update(cx, |session, _| {
session.add_child_session_id(session_id);
});
}
let (initialized_tx, initialized_rx) = oneshot::channel();
let start_client_task = Session::fake(
self.breakpoint_store.clone(),
session_id,
parent_session,
delegate,
config,
local_store.start_debugging_tx.clone(),
initialized_tx,
caps,
fails,
cx,
);
let task = create_new_session(session_id, initialized_rx, start_client_task, cx);
(session_id, task)
}
fn handle_start_debugging_request(
&mut self,
session_id: SessionId,
request: dap::messages::Request,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let Some(local_store) = self.as_local() else {
unreachable!("Cannot response for non-local session");
};
let Some(parent_session) = self.session_by_id(session_id) else {
return Task::ready(Err(anyhow!("Session not found")));
};
let args = serde_json::from_value::<StartDebuggingRequestArguments>(
request.arguments.unwrap_or_default(),
)
.expect("To parse StartDebuggingRequestArguments");
let worktree = local_store
.worktree_store
.update(cx, |this, _| this.worktrees().next())
.expect("worktree-less project");
let Some(config) = parent_session.read(cx).configuration() else {
unreachable!("there must be a config for local sessions");
};
let debug_config = DebugAdapterConfig {
label: config.label,
adapter: config.adapter,
request: DebugRequestDisposition::ReverseRequest(args),
initialize_args: config.initialize_args.clone(),
tcp_connection: config.tcp_connection.clone(),
locator: None,
stop_on_entry: config.stop_on_entry,
};
#[cfg(any(test, feature = "test-support"))]
let new_session_task = {
let caps = parent_session.read(cx).capabilities.clone();
self.new_fake_session(
debug_config,
&worktree,
Some(parent_session.clone()),
caps,
false,
cx,
)
.1
};
#[cfg(not(any(test, feature = "test-support")))]
let new_session_task = self
.new_session(debug_config, &worktree, Some(parent_session.clone()), cx)
.1;
let request_seq = request.seq;
cx.spawn(async move |_, cx| {
let (success, body) = match new_session_task.await {
Ok(_) => (true, None),
Err(error) => (
false,
Some(serde_json::to_value(ErrorResponse {
error: Some(dap::Message {
id: request_seq,
format: error.to_string(),
variables: None,
send_telemetry: None,
show_user: None,
url: None,
url_label: None,
}),
})?),
),
};
parent_session
.update(cx, |session, cx| {
session.respond_to_client(
request_seq,
success,
StartDebugging::COMMAND.to_string(),
body,
cx,
)
})?
.await
})
}
fn handle_run_in_terminal_request(
&mut self,
session_id: SessionId,
request: dap::messages::Request,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let Some(session) = self.session_by_id(session_id) else {
return Task::ready(Err(anyhow!("Session not found")));
};
let request_args = serde_json::from_value::<RunInTerminalRequestArguments>(
request.arguments.unwrap_or_default(),
)
.expect("To parse StartDebuggingRequestArguments");
let seq = request.seq;
let cwd = PathBuf::from(request_args.cwd);
match cwd.try_exists() {
Ok(true) => (),
Ok(false) | Err(_) => {
return session.update(cx, |session, cx| {
session.respond_to_client(
seq,
false,
RunInTerminal::COMMAND.to_string(),
serde_json::to_value(dap::ErrorResponse {
error: Some(dap::Message {
id: seq,
format: format!("Received invalid/unknown cwd: {cwd:?}"),
variables: None,
send_telemetry: None,
show_user: None,
url: None,
url_label: None,
}),
})
.ok(),
cx,
)
});
}
}
let mut args = request_args.args.clone();
// Handle special case for NodeJS debug adapter
// If only the Node binary path is provided, we set the command to None
// This prevents the NodeJS REPL from appearing, which is not the desired behavior
// The expected usage is for users to provide their own Node command, e.g., `node test.js`
// This allows the NodeJS debug client to attach correctly
let command = if args.len() > 1 {
Some(args.remove(0))
} else {
None
};
let mut envs: HashMap<String, String> = Default::default();
if let Some(Value::Object(env)) = request_args.env {
for (key, value) in env {
let value_str = match (key.as_str(), value) {
(_, Value::String(value)) => value,
_ => continue,
};
envs.insert(key, value_str);
}
}
let (tx, mut rx) = mpsc::channel::<Result<u32>>(1);
cx.emit(DapStoreEvent::RunInTerminal {
session_id,
title: request_args.title,
cwd,
command,
args,
envs,
sender: tx,
});
cx.notify();
let session = session.downgrade();
cx.spawn(async move |_, cx| {
let (success, body) = match rx.next().await {
Some(Ok(pid)) => (
true,
serde_json::to_value(dap::RunInTerminalResponse {
process_id: None,
shell_process_id: Some(pid as u64),
})
.ok(),
),
Some(Err(error)) => (
false,
serde_json::to_value(dap::ErrorResponse {
error: Some(dap::Message {
id: seq,
format: error.to_string(),
variables: None,
send_telemetry: None,
show_user: None,
url: None,
url_label: None,
}),
})
.ok(),
),
None => (
false,
serde_json::to_value(dap::ErrorResponse {
error: Some(dap::Message {
id: seq,
format: "failed to receive response from spawn terminal".to_string(),
variables: None,
send_telemetry: None,
show_user: None,
url: None,
url_label: None,
}),
})
.ok(),
),
};
session
.update(cx, |session, cx| {
session.respond_to_client(
seq,
success,
RunInTerminal::COMMAND.to_string(),
body,
cx,
)
})?
.await
})
}
pub fn evaluate(
&self,
session_id: &SessionId,
stack_frame_id: u64,
expression: String,
context: EvaluateArgumentsContext,
source: Option<Source>,
cx: &mut Context<Self>,
) -> Task<Result<EvaluateResponse>> {
let Some(client) = self
.session_by_id(session_id)
.and_then(|client| client.read(cx).adapter_client())
else {
return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
};
cx.background_executor().spawn(async move {
client
.request::<Evaluate>(EvaluateArguments {
expression: expression.clone(),
frame_id: Some(stack_frame_id),
context: Some(context),
format: None,
line: None,
column: None,
source,
})
.await
})
}
pub fn completions(
&self,
session_id: &SessionId,
stack_frame_id: u64,
text: String,
completion_column: u64,
cx: &mut Context<Self>,
) -> Task<Result<Vec<CompletionItem>>> {
let Some(client) = self
.session_by_id(session_id)
.and_then(|client| client.read(cx).adapter_client())
else {
return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
};
cx.background_executor().spawn(async move {
Ok(client
.request::<Completions>(CompletionsArguments {
frame_id: Some(stack_frame_id),
line: None,
text,
column: completion_column,
})
.await?
.targets)
})
}
pub fn shutdown_sessions(&mut self, cx: &mut Context<Self>) -> Task<()> {
let mut tasks = vec![];
for session_id in self.sessions.keys().cloned().collect::<Vec<_>>() {
tasks.push(self.shutdown_session(session_id, cx));
}
cx.background_executor().spawn(async move {
futures::future::join_all(tasks).await;
})
}
pub fn shutdown_session(
&mut self,
session_id: SessionId,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let Some(_) = self.as_local_mut() else {
return Task::ready(Err(anyhow!("Cannot shutdown session on remote side")));
};
let Some(session) = self.sessions.remove(&session_id) else {
return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id)));
};
let shutdown_children = session
.read(cx)
.child_session_ids()
.iter()
.map(|session_id| self.shutdown_session(*session_id, cx))
.collect::<Vec<_>>();
let shutdown_parent_task = if let Some(parent_session) = session
.read(cx)
.parent_id()
.and_then(|session_id| self.session_by_id(session_id))
{
let shutdown_id = parent_session.update(cx, |parent_session, _| {
parent_session.remove_child_session_id(session_id);
if parent_session.child_session_ids().len() == 0 {
Some(parent_session.session_id())
} else {
None
}
});
shutdown_id.map(|session_id| self.shutdown_session(session_id, cx))
} else {
None
};
let shutdown_task = session.update(cx, |this, cx| this.shutdown(cx));
cx.background_spawn(async move {
if shutdown_children.len() > 0 {
let _ = join_all(shutdown_children).await;
}
shutdown_task.await;
if let Some(parent_task) = shutdown_parent_task {
parent_task.await?;
}
Ok(())
})
}
pub fn shared(
&mut self,
project_id: u64,
downstream_client: AnyProtoClient,
_: &mut Context<Self>,
) {
self.downstream_client = Some((downstream_client.clone(), project_id));
}
pub fn unshared(&mut self, cx: &mut Context<Self>) {
self.downstream_client.take();
cx.notify();
}
}
fn create_new_session(
session_id: SessionId,
initialized_rx: oneshot::Receiver<()>,
start_client_task: Task<Result<Entity<Session>, anyhow::Error>>,
cx: &mut Context<DapStore>,
) -> Task<Result<Entity<Session>>> {
let task = cx.spawn(async move |this, cx| {
let session = match start_client_task.await {
Ok(session) => session,
Err(error) => {
this.update(cx, |_, cx| {
cx.emit(DapStoreEvent::Notification(error.to_string()));
})
.log_err();
return Err(error);
}
};
// we have to insert the session early, so we can handle reverse requests
// that need the session to be available
this.update(cx, |store, cx| {
store.sessions.insert(session_id, session.clone());
cx.emit(DapStoreEvent::DebugClientStarted(session_id));
cx.notify();
})?;
match {
session
.update(cx, |session, cx| session.request_initialize(cx))?
.await?;
session
.update(cx, |session, cx| {
session.initialize_sequence(initialized_rx, cx)
})?
.await
} {
Ok(_) => {}
Err(error) => {
this.update(cx, |this, cx| {
cx.emit(DapStoreEvent::Notification(error.to_string()));
this.shutdown_session(session_id, cx)
})?
.await
.log_err();
return Err(error);
}
}
this.update(cx, |_, cx| {
cx.emit(DapStoreEvent::DebugSessionInitialized(session_id));
})?;
Ok(session)
});
task
}
#[derive(Clone)]
pub struct DapAdapterDelegate {
fs: Arc<dyn Fs>,
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>>>>,
}
impl DapAdapterDelegate {
pub fn new(
fs: Arc<dyn Fs>,
worktree_id: WorktreeId,
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,
worktree_id,
http_client,
node_runtime,
toolchain_store,
language_registry,
load_shell_env_task,
updated_adapters: Default::default(),
}
}
}
#[async_trait(?Send)]
impl dap::adapters::DapDelegate for DapAdapterDelegate {
fn worktree_id(&self) -> WorktreeId {
self.worktree_id
}
fn http_client(&self) -> Arc<dyn HttpClient> {
self.http_client.clone()
}
fn node_runtime(&self) -> NodeRuntime {
self.node_runtime.clone()
}
fn fs(&self) -> Arc<dyn Fs> {
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 which(&self, command: &OsStr) -> Option<PathBuf> {
which::which(command).ok()
}
async fn shell_env(&self) -> HashMap<String, String> {
let task = self.load_shell_env_task.clone();
task.await.unwrap_or_default()
}
fn toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
self.toolchain_store.clone()
}
}