debugger: Remove fake adapter and un-gate GDB (#27557)
This is a clean-up PR in anticipation of introduction of Debugger Registry. I wanna get rid of DebugAdapterKind (or rather, it being an enum). Release Notes: - N/A --------- Co-authored-by: Anthony Eid <hello@anthonyeid.me> Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
parent
56eb650f09
commit
4839195003
53 changed files with 1315 additions and 924 deletions
|
@ -15,5 +15,3 @@ pub mod breakpoint_store;
|
|||
pub mod dap_command;
|
||||
pub mod dap_store;
|
||||
pub mod session;
|
||||
|
||||
pub use dap_adapters::attach_processes;
|
||||
|
|
|
@ -20,10 +20,9 @@ use dap::{
|
|||
Completions, Evaluate, Request as _, RunInTerminal, SetExpression, SetVariable,
|
||||
StartDebugging,
|
||||
},
|
||||
Capabilities, CompletionItem, CompletionsArguments, ErrorResponse, EvaluateArguments,
|
||||
EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
|
||||
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse,
|
||||
EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
|
||||
SetExpressionArguments, SetVariableArguments, Source, StartDebuggingRequestArguments,
|
||||
StartDebuggingRequestArgumentsRequest,
|
||||
};
|
||||
use fs::Fs;
|
||||
use futures::{
|
||||
|
@ -51,7 +50,7 @@ use std::{
|
|||
sync::{atomic::Ordering::SeqCst, Arc},
|
||||
};
|
||||
use std::{collections::VecDeque, sync::atomic::AtomicU32};
|
||||
use task::{AttachConfig, DebugAdapterConfig, DebugRequestType};
|
||||
use task::{DebugAdapterConfig, DebugRequestDisposition};
|
||||
use util::ResultExt as _;
|
||||
use worktree::Worktree;
|
||||
|
||||
|
@ -89,6 +88,7 @@ pub struct LocalDapStore {
|
|||
worktree_store: Entity<WorktreeStore>,
|
||||
environment: Entity<ProjectEnvironment>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
_start_debugging_task: Task<()>,
|
||||
|
@ -138,6 +138,7 @@ impl DapStore {
|
|||
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>,
|
||||
|
@ -178,6 +179,7 @@ impl DapStore {
|
|||
worktree_store,
|
||||
toolchain_store,
|
||||
language_registry,
|
||||
debug_adapters,
|
||||
start_debugging_tx,
|
||||
_start_debugging_task,
|
||||
next_session_id: Default::default(),
|
||||
|
@ -364,52 +366,63 @@ impl DapStore {
|
|||
config,
|
||||
local_store.start_debugging_tx.clone(),
|
||||
initialized_tx,
|
||||
local_store.debug_adapters.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
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();
|
||||
let task = create_new_session(session_id, initialized_rx, start_client_task, cx);
|
||||
(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");
|
||||
};
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
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.id()), Some(worktree.abs_path()), cx)
|
||||
}),
|
||||
);
|
||||
let session_id = local_store.next_session_id();
|
||||
|
||||
// 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();
|
||||
})?;
|
||||
if let Some(session) = &parent_session {
|
||||
session.update(cx, |session, _| {
|
||||
session.add_child_session_id(session_id);
|
||||
});
|
||||
}
|
||||
|
||||
match 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()));
|
||||
let (initialized_tx, initialized_rx) = oneshot::channel();
|
||||
|
||||
this.shutdown_session(session_id, cx)
|
||||
})?
|
||||
.await
|
||||
.log_err();
|
||||
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,
|
||||
);
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(session)
|
||||
});
|
||||
let task = create_new_session(session_id, initialized_rx, start_client_task, cx);
|
||||
(session_id, task)
|
||||
}
|
||||
|
||||
|
@ -431,7 +444,6 @@ impl DapStore {
|
|||
request.arguments.unwrap_or_default(),
|
||||
)
|
||||
.expect("To parse StartDebuggingRequestArguments");
|
||||
|
||||
let worktree = local_store
|
||||
.worktree_store
|
||||
.update(cx, |this, _| this.worktrees().next())
|
||||
|
@ -441,25 +453,30 @@ impl DapStore {
|
|||
unreachable!("there must be a config for local sessions");
|
||||
};
|
||||
|
||||
let (_, new_session_task) = self.new_session(
|
||||
DebugAdapterConfig {
|
||||
label: config.label,
|
||||
kind: config.kind,
|
||||
request: match &args.request {
|
||||
StartDebuggingRequestArgumentsRequest::Launch => DebugRequestType::Launch,
|
||||
StartDebuggingRequestArgumentsRequest::Attach => {
|
||||
DebugRequestType::Attach(AttachConfig::default())
|
||||
}
|
||||
},
|
||||
program: config.program,
|
||||
cwd: config.cwd,
|
||||
initialize_args: Some(args.configuration),
|
||||
supports_attach: config.supports_attach,
|
||||
},
|
||||
&worktree,
|
||||
Some(parent_session.clone()),
|
||||
cx,
|
||||
);
|
||||
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(),
|
||||
};
|
||||
#[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| {
|
||||
|
@ -830,6 +847,58 @@ impl DapStore {
|
|||
}
|
||||
}
|
||||
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(session)
|
||||
});
|
||||
task
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DapAdapterDelegate {
|
||||
fs: Arc<dyn Fs>,
|
||||
|
|
|
@ -14,7 +14,6 @@ use anyhow::{anyhow, Result};
|
|||
use collections::{HashMap, HashSet, IndexMap, IndexSet};
|
||||
use dap::adapters::{DebugAdapter, DebugAdapterBinary};
|
||||
use dap::messages::Response;
|
||||
use dap::OutputEventCategory;
|
||||
use dap::{
|
||||
adapters::{DapDelegate, DapStatus},
|
||||
client::{DebugAdapterClient, SessionId},
|
||||
|
@ -22,7 +21,7 @@ use dap::{
|
|||
Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId,
|
||||
SteppingGranularity, StoppedEvent, VariableReference,
|
||||
};
|
||||
use dap_adapters::build_adapter;
|
||||
use dap::{DapRegistry, DebugRequestType, OutputEventCategory};
|
||||
use futures::channel::oneshot;
|
||||
use futures::{future::Shared, FutureExt};
|
||||
use gpui::{
|
||||
|
@ -42,7 +41,7 @@ use std::{
|
|||
path::Path,
|
||||
sync::Arc,
|
||||
};
|
||||
use task::DebugAdapterConfig;
|
||||
use task::{DebugAdapterConfig, DebugTaskDefinition};
|
||||
use text::{PointUtf16, ToPointUtf16};
|
||||
use util::{merge_json_value_into, ResultExt};
|
||||
|
||||
|
@ -183,6 +182,7 @@ fn client_source(abs_path: &Path) -> dap::Source {
|
|||
|
||||
impl LocalMode {
|
||||
fn new(
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
|
@ -190,9 +190,168 @@ impl LocalMode {
|
|||
delegate: DapAdapterDelegate,
|
||||
messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
|
||||
cx: AsyncApp,
|
||||
) -> Task<Result<(Self, Capabilities)>> {
|
||||
Self::new_inner(
|
||||
debug_adapters,
|
||||
session_id,
|
||||
parent_session,
|
||||
breakpoint_store,
|
||||
config,
|
||||
delegate,
|
||||
messages_tx,
|
||||
async |_, _| {},
|
||||
cx,
|
||||
)
|
||||
}
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn new_fake(
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
config: DebugAdapterConfig,
|
||||
delegate: DapAdapterDelegate,
|
||||
messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
|
||||
caps: Capabilities,
|
||||
fail: bool,
|
||||
cx: AsyncApp,
|
||||
) -> Task<Result<(Self, Capabilities)>> {
|
||||
use task::DebugRequestDisposition;
|
||||
|
||||
let request = match config.request.clone() {
|
||||
DebugRequestDisposition::UserConfigured(request) => request,
|
||||
DebugRequestDisposition::ReverseRequest(reverse_request_args) => {
|
||||
match reverse_request_args.request {
|
||||
dap::StartDebuggingRequestArgumentsRequest::Launch => {
|
||||
DebugRequestType::Launch(task::LaunchConfig {
|
||||
program: "".to_owned(),
|
||||
cwd: None,
|
||||
})
|
||||
}
|
||||
dap::StartDebuggingRequestArgumentsRequest::Attach => {
|
||||
DebugRequestType::Attach(task::AttachConfig {
|
||||
process_id: Some(0),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let callback = async move |session: &mut LocalMode, cx: AsyncApp| {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Initialize, _>(move |_, _| Ok(caps.clone()))
|
||||
.await;
|
||||
|
||||
let paths = cx
|
||||
.update(|cx| session.breakpoint_store.read(cx).breakpoint_paths())
|
||||
.expect("Breakpoint store should exist in all tests that start debuggers");
|
||||
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::SetBreakpoints, _>(move |_, args| {
|
||||
let p = Arc::from(Path::new(&args.source.path.unwrap()));
|
||||
if !paths.contains(&p) {
|
||||
panic!("Sent breakpoints for path without any")
|
||||
}
|
||||
|
||||
Ok(dap::SetBreakpointsResponse {
|
||||
breakpoints: Vec::default(),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
||||
match request {
|
||||
dap::DebugRequestType::Launch(_) => {
|
||||
if fail {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Launch, _>(move |_, _| {
|
||||
Err(dap::ErrorResponse {
|
||||
error: Some(dap::Message {
|
||||
id: 1,
|
||||
format: "error".into(),
|
||||
variables: None,
|
||||
send_telemetry: None,
|
||||
show_user: None,
|
||||
url: None,
|
||||
url_label: None,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
} else {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Launch, _>(move |_, _| Ok(()))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
dap::DebugRequestType::Attach(attach_config) => {
|
||||
if fail {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Attach, _>(move |_, _| {
|
||||
Err(dap::ErrorResponse {
|
||||
error: Some(dap::Message {
|
||||
id: 1,
|
||||
format: "error".into(),
|
||||
variables: None,
|
||||
send_telemetry: None,
|
||||
show_user: None,
|
||||
url: None,
|
||||
url_label: None,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
} else {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Attach, _>(move |_, args| {
|
||||
assert_eq!(
|
||||
json!({"request": "attach", "process_id": attach_config.process_id.unwrap()}),
|
||||
args.raw
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Disconnect, _>(move |_, _| Ok(()))
|
||||
.await;
|
||||
session.client.fake_event(Events::Initialized(None)).await;
|
||||
};
|
||||
Self::new_inner(
|
||||
DapRegistry::fake().into(),
|
||||
session_id,
|
||||
parent_session,
|
||||
breakpoint_store,
|
||||
config,
|
||||
delegate,
|
||||
messages_tx,
|
||||
callback,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
fn new_inner(
|
||||
registry: Arc<DapRegistry>,
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
config: DebugAdapterConfig,
|
||||
delegate: DapAdapterDelegate,
|
||||
messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
|
||||
on_initialized: impl AsyncFnOnce(&mut LocalMode, AsyncApp) + 'static,
|
||||
cx: AsyncApp,
|
||||
) -> Task<Result<(Self, Capabilities)>> {
|
||||
cx.spawn(async move |cx| {
|
||||
let (adapter, binary) = Self::get_adapter_binary(&config, &delegate, cx).await?;
|
||||
let (adapter, binary) =
|
||||
Self::get_adapter_binary(®istry, &config, &delegate, cx).await?;
|
||||
|
||||
let message_handler = Box::new(move |message| {
|
||||
messages_tx.unbounded_send(message).ok();
|
||||
|
@ -219,99 +378,14 @@ impl LocalMode {
|
|||
);
|
||||
|
||||
let adapter_id = adapter.name().to_string().to_owned();
|
||||
let session = Self {
|
||||
let mut session = Self {
|
||||
client,
|
||||
adapter,
|
||||
breakpoint_store,
|
||||
config: config.clone(),
|
||||
};
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
{
|
||||
let dap::DebugAdapterKind::Fake((fail, caps)) = session.config.kind.clone() else {
|
||||
panic!("Only fake debug adapter configs should be used in tests");
|
||||
};
|
||||
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Initialize, _>(move |_, _| Ok(caps.clone()))
|
||||
.await;
|
||||
|
||||
let paths = cx.update(|cx| session.breakpoint_store.read(cx).breakpoint_paths()).expect("Breakpoint store should exist in all tests that start debuggers");
|
||||
|
||||
session.client.on_request::<dap::requests::SetBreakpoints, _>(move |_, args| {
|
||||
let p = Arc::from(Path::new(&args.source.path.unwrap()));
|
||||
if !paths.contains(&p) {
|
||||
panic!("Sent breakpoints for path without any")
|
||||
}
|
||||
|
||||
Ok(dap::SetBreakpointsResponse {
|
||||
breakpoints: Vec::default(),
|
||||
})
|
||||
}).await;
|
||||
|
||||
match config.request.clone() {
|
||||
dap::DebugRequestType::Launch if fail => {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Launch, _>(move |_, _| {
|
||||
Err(dap::ErrorResponse {
|
||||
error: Some(dap::Message {
|
||||
id: 1,
|
||||
format: "error".into(),
|
||||
variables: None,
|
||||
send_telemetry: None,
|
||||
show_user: None,
|
||||
url: None,
|
||||
url_label: None,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
}
|
||||
dap::DebugRequestType::Launch => {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Launch, _>(move |_, _| Ok(()))
|
||||
.await;
|
||||
}
|
||||
dap::DebugRequestType::Attach(_) if fail => {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Attach, _>(move |_, _| {
|
||||
Err(dap::ErrorResponse {
|
||||
error: Some(dap::Message {
|
||||
id: 1,
|
||||
format: "error".into(),
|
||||
variables: None,
|
||||
send_telemetry: None,
|
||||
show_user: None,
|
||||
url: None,
|
||||
url_label: None,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
}
|
||||
dap::DebugRequestType::Attach(attach_config) => {
|
||||
session
|
||||
.client
|
||||
.on_request::<dap::requests::Attach, _>(move |_, args| {
|
||||
assert_eq!(
|
||||
json!({"request": "attach", "process_id": attach_config.process_id.unwrap()}),
|
||||
args.raw
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
session.client.on_request::<dap::requests::Disconnect, _>(move |_, _| Ok(())).await;
|
||||
session.client.fake_event(Events::Initialized(None)).await;
|
||||
}
|
||||
|
||||
on_initialized(&mut session, cx.clone()).await;
|
||||
let capabilities = session
|
||||
.request(Initialize { adapter_id }, cx.background_executor().clone())
|
||||
.await?;
|
||||
|
@ -420,11 +494,14 @@ impl LocalMode {
|
|||
}
|
||||
|
||||
async fn get_adapter_binary(
|
||||
registry: &Arc<DapRegistry>,
|
||||
config: &DebugAdapterConfig,
|
||||
delegate: &DapAdapterDelegate,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<(Arc<dyn DebugAdapter>, DebugAdapterBinary)> {
|
||||
let adapter = build_adapter(&config.kind).await?;
|
||||
let adapter = registry
|
||||
.adapter(&config.adapter)
|
||||
.ok_or_else(|| anyhow!("Debug adapter with name `{}` was not found", config.adapter))?;
|
||||
|
||||
let binary = cx.update(|cx| {
|
||||
ProjectSettings::get_global(cx)
|
||||
|
@ -465,20 +542,36 @@ impl LocalMode {
|
|||
initialized_rx: oneshot::Receiver<()>,
|
||||
cx: &App,
|
||||
) -> Task<Result<()>> {
|
||||
let mut raw = self.adapter.request_args(&self.config);
|
||||
let (mut raw, is_launch) = match &self.config.request {
|
||||
task::DebugRequestDisposition::UserConfigured(_) => {
|
||||
let Ok(raw) = DebugTaskDefinition::try_from(self.config.clone()) else {
|
||||
debug_assert!(false, "This part of code should be unreachable in practice");
|
||||
return Task::ready(Err(anyhow!(
|
||||
"Expected debug config conversion to succeed"
|
||||
)));
|
||||
};
|
||||
let is_launch = matches!(raw.request, DebugRequestType::Launch(_));
|
||||
let raw = self.adapter.request_args(&raw);
|
||||
(raw, is_launch)
|
||||
}
|
||||
task::DebugRequestDisposition::ReverseRequest(start_debugging_request_arguments) => (
|
||||
start_debugging_request_arguments.configuration.clone(),
|
||||
matches!(
|
||||
start_debugging_request_arguments.request,
|
||||
dap::StartDebuggingRequestArgumentsRequest::Launch
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
merge_json_value_into(
|
||||
self.config.initialize_args.clone().unwrap_or(json!({})),
|
||||
&mut raw,
|
||||
);
|
||||
|
||||
// Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
|
||||
let launch = match &self.config.request {
|
||||
dap::DebugRequestType::Launch => {
|
||||
self.request(Launch { raw }, cx.background_executor().clone())
|
||||
}
|
||||
dap::DebugRequestType::Attach(_) => {
|
||||
self.request(Attach { raw }, cx.background_executor().clone())
|
||||
}
|
||||
let launch = if is_launch {
|
||||
self.request(Launch { raw }, cx.background_executor().clone())
|
||||
} else {
|
||||
self.request(Attach { raw }, cx.background_executor().clone())
|
||||
};
|
||||
|
||||
let configuration_done_supported = ConfigurationDone::is_supported(capabilities);
|
||||
|
@ -745,12 +838,14 @@ impl Session {
|
|||
config: DebugAdapterConfig,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
initialized_tx: oneshot::Sender<()>,
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
let (message_tx, mut message_rx) = futures::channel::mpsc::unbounded();
|
||||
let (message_tx, message_rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let (mode, capabilities) = LocalMode::new(
|
||||
debug_adapters,
|
||||
session_id,
|
||||
parent_session.clone(),
|
||||
breakpoint_store.clone(),
|
||||
|
@ -762,74 +857,62 @@ impl Session {
|
|||
.await?;
|
||||
|
||||
cx.new(|cx| {
|
||||
let _background_tasks = vec![cx.spawn(async move |this: WeakEntity<Self>, cx| {
|
||||
let mut initialized_tx = Some(initialized_tx);
|
||||
while let Some(message) = message_rx.next().await {
|
||||
if let Message::Event(event) = message {
|
||||
if let Events::Initialized(_) = *event {
|
||||
if let Some(tx) = initialized_tx.take() {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
} else {
|
||||
let Ok(_) = this.update(cx, |session, cx| {
|
||||
session.handle_dap_event(event, cx);
|
||||
}) else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let Ok(_) =
|
||||
start_debugging_requests_tx.unbounded_send((session_id, message))
|
||||
else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
})];
|
||||
|
||||
cx.subscribe(&breakpoint_store, |this, _, event, cx| match event {
|
||||
BreakpointStoreEvent::BreakpointsUpdated(path, reason) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local
|
||||
.send_breakpoints_from_path(path.clone(), *reason, cx)
|
||||
.detach();
|
||||
};
|
||||
}
|
||||
BreakpointStoreEvent::BreakpointsCleared(paths) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local.unset_breakpoints_from_paths(paths, cx).detach();
|
||||
}
|
||||
}
|
||||
BreakpointStoreEvent::ActiveDebugLineChanged => {}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
mode: Mode::Local(mode),
|
||||
id: session_id,
|
||||
child_session_ids: HashSet::default(),
|
||||
parent_id: parent_session.map(|session| session.read(cx).id),
|
||||
variables: Default::default(),
|
||||
create_local_session(
|
||||
breakpoint_store,
|
||||
session_id,
|
||||
parent_session,
|
||||
start_debugging_requests_tx,
|
||||
initialized_tx,
|
||||
message_rx,
|
||||
mode,
|
||||
capabilities,
|
||||
thread_states: ThreadStates::default(),
|
||||
output_token: OutputToken(0),
|
||||
ignore_breakpoints: false,
|
||||
output: circular_buffer::CircularBuffer::boxed(),
|
||||
requests: HashMap::default(),
|
||||
modules: Vec::default(),
|
||||
loaded_sources: Vec::default(),
|
||||
threads: IndexMap::default(),
|
||||
stack_frames: IndexMap::default(),
|
||||
locations: Default::default(),
|
||||
_background_tasks,
|
||||
is_session_terminated: false,
|
||||
}
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) fn fake(
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
delegate: DapAdapterDelegate,
|
||||
config: DebugAdapterConfig,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
initialized_tx: oneshot::Sender<()>,
|
||||
caps: Capabilities,
|
||||
fails: bool,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
let (message_tx, message_rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let (mode, capabilities) = LocalMode::new_fake(
|
||||
session_id,
|
||||
parent_session.clone(),
|
||||
breakpoint_store.clone(),
|
||||
config.clone(),
|
||||
delegate,
|
||||
message_tx,
|
||||
caps,
|
||||
fails,
|
||||
cx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
cx.new(|cx| {
|
||||
create_local_session(
|
||||
breakpoint_store,
|
||||
session_id,
|
||||
parent_session,
|
||||
start_debugging_requests_tx,
|
||||
initialized_tx,
|
||||
message_rx,
|
||||
mode,
|
||||
capabilities,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -1838,3 +1921,83 @@ impl Session {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_local_session(
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
initialized_tx: oneshot::Sender<()>,
|
||||
mut message_rx: futures::channel::mpsc::UnboundedReceiver<Message>,
|
||||
mode: LocalMode,
|
||||
capabilities: Capabilities,
|
||||
cx: &mut Context<'_, Session>,
|
||||
) -> Session {
|
||||
let _background_tasks = vec![cx.spawn(async move |this: WeakEntity<Session>, cx| {
|
||||
let mut initialized_tx = Some(initialized_tx);
|
||||
while let Some(message) = message_rx.next().await {
|
||||
if let Message::Event(event) = message {
|
||||
if let Events::Initialized(_) = *event {
|
||||
if let Some(tx) = initialized_tx.take() {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
} else {
|
||||
let Ok(_) = this.update(cx, |session, cx| {
|
||||
session.handle_dap_event(event, cx);
|
||||
}) else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let Ok(_) = start_debugging_requests_tx.unbounded_send((session_id, message))
|
||||
else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
})];
|
||||
|
||||
cx.subscribe(&breakpoint_store, |this, _, event, cx| match event {
|
||||
BreakpointStoreEvent::BreakpointsUpdated(path, reason) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local
|
||||
.send_breakpoints_from_path(path.clone(), *reason, cx)
|
||||
.detach();
|
||||
};
|
||||
}
|
||||
BreakpointStoreEvent::BreakpointsCleared(paths) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local.unset_breakpoints_from_paths(paths, cx).detach();
|
||||
}
|
||||
}
|
||||
BreakpointStoreEvent::ActiveDebugLineChanged => {}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Session {
|
||||
mode: Mode::Local(mode),
|
||||
id: session_id,
|
||||
child_session_ids: HashSet::default(),
|
||||
parent_id: parent_session.map(|session| session.read(cx).id),
|
||||
variables: Default::default(),
|
||||
capabilities,
|
||||
thread_states: ThreadStates::default(),
|
||||
output_token: OutputToken(0),
|
||||
ignore_breakpoints: false,
|
||||
output: circular_buffer::CircularBuffer::boxed(),
|
||||
requests: HashMap::default(),
|
||||
modules: Vec::default(),
|
||||
loaded_sources: Vec::default(),
|
||||
threads: IndexMap::default(),
|
||||
stack_frames: IndexMap::default(),
|
||||
locations: Default::default(),
|
||||
_background_tasks,
|
||||
is_session_terminated: false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ use client::{
|
|||
};
|
||||
use clock::ReplicaId;
|
||||
|
||||
use dap::{client::DebugAdapterClient, DebugAdapterConfig};
|
||||
use dap::{client::DebugAdapterClient, DapRegistry, DebugAdapterConfig};
|
||||
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
use debounced_delay::DebouncedDelay;
|
||||
|
@ -163,6 +163,7 @@ pub struct Project {
|
|||
active_entry: Option<ProjectEntryId>,
|
||||
buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
dap_store: Entity<DapStore>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
client: Arc<client::Client>,
|
||||
|
@ -818,6 +819,7 @@ impl Project {
|
|||
node: NodeRuntime,
|
||||
user_store: Entity<UserStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
env: Option<HashMap<String, String>>,
|
||||
cx: &mut App,
|
||||
|
@ -854,6 +856,7 @@ impl Project {
|
|||
node.clone(),
|
||||
fs.clone(),
|
||||
languages.clone(),
|
||||
debug_adapters.clone(),
|
||||
environment.clone(),
|
||||
toolchain_store.read(cx).as_language_toolchain_store(),
|
||||
breakpoint_store.clone(),
|
||||
|
@ -940,6 +943,7 @@ impl Project {
|
|||
active_entry: None,
|
||||
snippets,
|
||||
languages,
|
||||
debug_adapters,
|
||||
client,
|
||||
task_store,
|
||||
user_store,
|
||||
|
@ -1102,6 +1106,7 @@ impl Project {
|
|||
active_entry: None,
|
||||
snippets,
|
||||
languages,
|
||||
debug_adapters: Arc::new(DapRegistry::default()),
|
||||
client,
|
||||
task_store,
|
||||
user_store,
|
||||
|
@ -1239,7 +1244,6 @@ impl Project {
|
|||
|
||||
let breakpoint_store =
|
||||
cx.new(|_| BreakpointStore::remote(remote_id, client.clone().into()))?;
|
||||
|
||||
let dap_store = cx.new(|_cx| {
|
||||
DapStore::new_remote(remote_id, client.clone().into(), breakpoint_store.clone())
|
||||
})?;
|
||||
|
@ -1326,6 +1330,7 @@ impl Project {
|
|||
collaborators: Default::default(),
|
||||
join_project_response_message_id: response.message_id,
|
||||
languages,
|
||||
debug_adapters: Arc::new(DapRegistry::default()),
|
||||
user_store: user_store.clone(),
|
||||
task_store,
|
||||
snippets,
|
||||
|
@ -1450,13 +1455,7 @@ impl Project {
|
|||
config: DebugAdapterConfig,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
let worktree = maybe!({
|
||||
if let Some(cwd) = &config.cwd {
|
||||
Some(self.find_worktree(cwd.as_path(), cx)?.0)
|
||||
} else {
|
||||
self.worktrees(cx).next()
|
||||
}
|
||||
});
|
||||
let worktree = maybe!({ self.worktrees(cx).next() });
|
||||
|
||||
let Some(worktree) = &worktree else {
|
||||
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
||||
|
@ -1469,6 +1468,40 @@ impl Project {
|
|||
.1
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn fake_debug_session(
|
||||
&mut self,
|
||||
request: task::DebugRequestType,
|
||||
caps: Option<dap::Capabilities>,
|
||||
fails: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
use dap::{Capabilities, FakeAdapter};
|
||||
use task::DebugRequestDisposition;
|
||||
|
||||
let worktree = maybe!({ self.worktrees(cx).next() });
|
||||
|
||||
let Some(worktree) = &worktree else {
|
||||
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
||||
};
|
||||
let config = DebugAdapterConfig {
|
||||
label: "test config".into(),
|
||||
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
||||
request: DebugRequestDisposition::UserConfigured(request),
|
||||
initialize_args: None,
|
||||
tcp_connection: None,
|
||||
};
|
||||
let caps = caps.unwrap_or(Capabilities {
|
||||
supports_step_back: Some(false),
|
||||
..Default::default()
|
||||
});
|
||||
self.dap_store
|
||||
.update(cx, |dap_store, cx| {
|
||||
dap_store.new_fake_session(config, worktree, None, caps, fails, cx)
|
||||
})
|
||||
.1
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn example(
|
||||
root_paths: impl IntoIterator<Item = &Path>,
|
||||
|
@ -1478,6 +1511,7 @@ impl Project {
|
|||
|
||||
let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
|
||||
let languages = LanguageRegistry::test(cx.background_executor().clone());
|
||||
let debug_adapters = DapRegistry::default().into();
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let http_client = http_client::FakeHttpClient::with_404_response();
|
||||
let client = cx
|
||||
|
@ -1491,6 +1525,7 @@ impl Project {
|
|||
node_runtime::NodeRuntime::unavailable(),
|
||||
user_store,
|
||||
Arc::new(languages),
|
||||
debug_adapters,
|
||||
fs,
|
||||
None,
|
||||
cx,
|
||||
|
@ -1521,6 +1556,7 @@ impl Project {
|
|||
use clock::FakeSystemClock;
|
||||
|
||||
let languages = LanguageRegistry::test(cx.executor());
|
||||
let debug_adapters = DapRegistry::fake();
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
let http_client = http_client::FakeHttpClient::with_404_response();
|
||||
let client = cx.update(|cx| client::Client::new(clock, http_client.clone(), cx));
|
||||
|
@ -1531,6 +1567,7 @@ impl Project {
|
|||
node_runtime::NodeRuntime::unavailable(),
|
||||
user_store,
|
||||
Arc::new(languages),
|
||||
Arc::new(debug_adapters),
|
||||
fs,
|
||||
None,
|
||||
cx,
|
||||
|
@ -1574,6 +1611,10 @@ impl Project {
|
|||
&self.languages
|
||||
}
|
||||
|
||||
pub fn debug_adapters(&self) -> &Arc<DapRegistry> {
|
||||
&self.debug_adapters
|
||||
}
|
||||
|
||||
pub fn client(&self) -> Arc<Client> {
|
||||
self.client.clone()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue