debugger: Open debugger panel on session startup (#29186)
Now all debug sessions are routed through the debug panel and are started synchronously instead of by a task that returns a session once the initialization process is finished. A session is `Mode::Booting` while it's starting the debug adapter process and then transitions to `Mode::Running` once this is completed. This PR also added new tests for the dap logger, reverse start debugging request, and debugging over SSH. Release Notes: - N/A --------- Co-authored-by: Anthony Eid <hello@anthonyeid.me> Co-authored-by: Anthony <anthony@zed.dev> Co-authored-by: Cole Miller <m@cole-miller.net> Co-authored-by: Cole Miller <cole@zed.dev> Co-authored-by: Zed AI <ai@zed.dev> Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
parent
75ab8ff9a1
commit
6a009b447a
29 changed files with 1261 additions and 1021 deletions
|
@ -4,33 +4,35 @@ use super::{
|
|||
session::{self, Session, SessionStateEvent},
|
||||
};
|
||||
use crate::{
|
||||
ProjectEnvironment, debugger, project_settings::ProjectSettings, worktree_store::WorktreeStore,
|
||||
ProjectEnvironment,
|
||||
project_settings::ProjectSettings,
|
||||
terminals::{SshCommand, wrap_for_ssh},
|
||||
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, DebugAdapterBinary, DebugAdapterName},
|
||||
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, EvaluateArguments,
|
||||
EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source,
|
||||
StartDebuggingRequestArguments,
|
||||
adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName, TcpArguments},
|
||||
client::SessionId,
|
||||
messages::Message,
|
||||
requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},
|
||||
};
|
||||
use fs::Fs;
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
channel::mpsc,
|
||||
future::{Shared, join_all},
|
||||
};
|
||||
use gpui::{
|
||||
App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
|
||||
};
|
||||
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 remote::SshRemoteClient;
|
||||
use rpc::{
|
||||
AnyProtoClient, TypedEnvelope,
|
||||
proto::{self},
|
||||
|
@ -42,6 +44,7 @@ use std::{
|
|||
borrow::Borrow,
|
||||
collections::{BTreeMap, HashSet},
|
||||
ffi::OsStr,
|
||||
net::Ipv4Addr,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
@ -66,6 +69,10 @@ pub enum DapStoreEvent {
|
|||
envs: HashMap<String, String>,
|
||||
sender: mpsc::Sender<Result<u32>>,
|
||||
},
|
||||
SpawnChildSession {
|
||||
request: StartDebuggingRequestArguments,
|
||||
parent_session: Entity<Session>,
|
||||
},
|
||||
Notification(String),
|
||||
RemoteHasInitialized,
|
||||
}
|
||||
|
@ -83,12 +90,12 @@ pub struct LocalDapStore {
|
|||
http_client: Arc<dyn HttpClient>,
|
||||
environment: Entity<ProjectEnvironment>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
locators: HashMap<String, Arc<dyn DapLocator>>,
|
||||
}
|
||||
|
||||
pub struct SshDapStore {
|
||||
ssh_client: Entity<SshRemoteClient>,
|
||||
upstream_client: AnyProtoClient,
|
||||
upstream_project_id: u64,
|
||||
}
|
||||
|
@ -97,6 +104,7 @@ pub struct DapStore {
|
|||
mode: DapStoreMode,
|
||||
downstream_client: Option<(AnyProtoClient, u64)>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
sessions: BTreeMap<SessionId, Entity<Session>>,
|
||||
next_session_id: u32,
|
||||
start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
|
@ -136,40 +144,43 @@ impl DapStore {
|
|||
http_client,
|
||||
node_runtime,
|
||||
toolchain_store,
|
||||
worktree_store,
|
||||
language_registry,
|
||||
locators,
|
||||
});
|
||||
|
||||
Self::new(mode, breakpoint_store, cx)
|
||||
Self::new(mode, breakpoint_store, worktree_store, cx)
|
||||
}
|
||||
|
||||
pub fn new_ssh(
|
||||
project_id: u64,
|
||||
upstream_client: AnyProtoClient,
|
||||
ssh_client: Entity<SshRemoteClient>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let mode = DapStoreMode::Ssh(SshDapStore {
|
||||
upstream_client,
|
||||
upstream_client: ssh_client.read(cx).proto_client(),
|
||||
ssh_client,
|
||||
upstream_project_id: project_id,
|
||||
});
|
||||
|
||||
Self::new(mode, breakpoint_store, cx)
|
||||
Self::new(mode, breakpoint_store, worktree_store, cx)
|
||||
}
|
||||
|
||||
pub fn new_collab(
|
||||
_project_id: u64,
|
||||
_upstream_client: AnyProtoClient,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new(DapStoreMode::Collab, breakpoint_store, cx)
|
||||
Self::new(DapStoreMode::Collab, breakpoint_store, worktree_store, cx)
|
||||
}
|
||||
|
||||
fn new(
|
||||
mode: DapStoreMode,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let (start_debugging_tx, mut message_rx) =
|
||||
|
@ -202,6 +213,7 @@ impl DapStore {
|
|||
next_session_id: 0,
|
||||
downstream_client: None,
|
||||
breakpoint_store,
|
||||
worktree_store,
|
||||
sessions: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -212,8 +224,8 @@ impl DapStore {
|
|||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<DebugAdapterBinary>> {
|
||||
match &self.mode {
|
||||
DapStoreMode::Local(local) => {
|
||||
let Some(worktree) = local.worktree_store.read(cx).visible_worktrees(cx).next()
|
||||
DapStoreMode::Local(_) => {
|
||||
let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next()
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
||||
};
|
||||
|
@ -261,10 +273,49 @@ impl DapStore {
|
|||
project_id: ssh.upstream_project_id,
|
||||
task: Some(definition.to_proto()),
|
||||
});
|
||||
let ssh_client = ssh.ssh_client.clone();
|
||||
|
||||
cx.background_spawn(async move {
|
||||
cx.spawn(async move |_, cx| {
|
||||
let response = request.await?;
|
||||
DebugAdapterBinary::from_proto(response)
|
||||
let binary = DebugAdapterBinary::from_proto(response)?;
|
||||
let mut ssh_command = ssh_client.update(cx, |ssh, _| {
|
||||
anyhow::Ok(SshCommand {
|
||||
arguments: ssh
|
||||
.ssh_args()
|
||||
.ok_or_else(|| anyhow!("SSH arguments not found"))?,
|
||||
})
|
||||
})??;
|
||||
|
||||
let mut connection = None;
|
||||
if let Some(c) = binary.connection {
|
||||
let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1);
|
||||
let port =
|
||||
dap::transport::TcpTransport::unused_port(local_bind_addr).await?;
|
||||
|
||||
ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);
|
||||
connection = Some(TcpArguments {
|
||||
port: c.port,
|
||||
host: local_bind_addr,
|
||||
timeout: c.timeout,
|
||||
})
|
||||
}
|
||||
|
||||
let (program, args) = wrap_for_ssh(
|
||||
&ssh_command,
|
||||
Some((&binary.command, &binary.arguments)),
|
||||
binary.cwd.as_deref(),
|
||||
binary.envs,
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: program,
|
||||
arguments: args,
|
||||
envs: HashMap::default(),
|
||||
cwd: None,
|
||||
connection,
|
||||
request_args: binary.request_args,
|
||||
})
|
||||
})
|
||||
}
|
||||
DapStoreMode::Collab => {
|
||||
|
@ -316,27 +367,79 @@ impl DapStore {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add_remote_client(
|
||||
pub fn new_session(
|
||||
&mut self,
|
||||
session_id: SessionId,
|
||||
ignore: Option<bool>,
|
||||
template: DebugTaskDefinition,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let DapStoreMode::Ssh(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);
|
||||
) -> Entity<Session> {
|
||||
let session_id = SessionId(util::post_inc(&mut self.next_session_id));
|
||||
|
||||
if let Some(session) = &parent_session {
|
||||
session.update(cx, |session, _| {
|
||||
session.add_child_session_id(session_id);
|
||||
});
|
||||
}
|
||||
|
||||
let start_debugging_tx = self.start_debugging_tx.clone();
|
||||
|
||||
let session = Session::new(
|
||||
self.breakpoint_store.clone(),
|
||||
session_id,
|
||||
parent_session,
|
||||
template.clone(),
|
||||
start_debugging_tx,
|
||||
cx,
|
||||
);
|
||||
|
||||
self.sessions.insert(session_id, session.clone());
|
||||
cx.notify();
|
||||
|
||||
cx.subscribe(&session, {
|
||||
move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event {
|
||||
SessionStateEvent::Shutdown => {
|
||||
this.shutdown_session(session_id, cx).detach_and_log_err(cx);
|
||||
}
|
||||
SessionStateEvent::Restart => {}
|
||||
SessionStateEvent::Running => {
|
||||
cx.emit(DapStoreEvent::DebugClientStarted(session_id));
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
session
|
||||
}
|
||||
|
||||
pub fn boot_session(
|
||||
&self,
|
||||
session: Entity<Session>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else {
|
||||
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
||||
};
|
||||
|
||||
let dap_store = cx.weak_entity();
|
||||
let breakpoint_store = self.breakpoint_store.clone();
|
||||
let definition = session.read(cx).definition();
|
||||
|
||||
cx.spawn({
|
||||
let session = session.clone();
|
||||
async move |this, cx| {
|
||||
let binary = this
|
||||
.update(cx, |this, cx| {
|
||||
this.get_debug_adapter_binary(definition.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
session
|
||||
.update(cx, |session, cx| {
|
||||
session.boot(binary, worktree, breakpoint_store, dap_store, cx)
|
||||
})?
|
||||
.await
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn session_by_id(
|
||||
|
@ -367,6 +470,10 @@ impl DapStore {
|
|||
&self.breakpoint_store
|
||||
}
|
||||
|
||||
pub fn worktree_store(&self) -> &Entity<WorktreeStore> {
|
||||
&self.worktree_store
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn handle_ignore_breakpoint_state(
|
||||
this: Entity<Self>,
|
||||
|
@ -407,52 +514,6 @@ impl DapStore {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn new_session(
|
||||
&mut self,
|
||||
binary: DebugAdapterBinary,
|
||||
config: DebugTaskDefinition,
|
||||
worktree: WeakEntity<Worktree>,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (SessionId, Task<Result<Entity<Session>>>) {
|
||||
let session_id = SessionId(util::post_inc(&mut self.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_debugging_tx = self.start_debugging_tx.clone();
|
||||
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
let start_client_task = this.update(cx, |this, cx| {
|
||||
Session::local(
|
||||
this.breakpoint_store.clone(),
|
||||
worktree.clone(),
|
||||
session_id,
|
||||
parent_session,
|
||||
binary,
|
||||
config,
|
||||
start_debugging_tx.clone(),
|
||||
initialized_tx,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
let ret = this
|
||||
.update(cx, |_, cx| {
|
||||
create_new_session(session_id, initialized_rx, start_client_task, worktree, cx)
|
||||
})?
|
||||
.await;
|
||||
ret
|
||||
});
|
||||
|
||||
(session_id, task)
|
||||
}
|
||||
|
||||
fn handle_start_debugging_request(
|
||||
&mut self,
|
||||
session_id: SessionId,
|
||||
|
@ -462,56 +523,35 @@ impl DapStore {
|
|||
let Some(parent_session) = self.session_by_id(session_id) else {
|
||||
return Task::ready(Err(anyhow!("Session not found")));
|
||||
};
|
||||
|
||||
let Some(worktree) = parent_session
|
||||
.read(cx)
|
||||
.as_local()
|
||||
.map(|local| local.worktree().clone())
|
||||
else {
|
||||
return Task::ready(Err(anyhow!(
|
||||
"Cannot handle start debugging request from remote end"
|
||||
)));
|
||||
};
|
||||
|
||||
let args = serde_json::from_value::<StartDebuggingRequestArguments>(
|
||||
request.arguments.unwrap_or_default(),
|
||||
)
|
||||
.expect("To parse StartDebuggingRequestArguments");
|
||||
let mut binary = parent_session.read(cx).binary().clone();
|
||||
let config = parent_session.read(cx).configuration().unwrap().clone();
|
||||
binary.request_args = args;
|
||||
|
||||
let new_session_task = self
|
||||
.new_session(binary, 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,
|
||||
}),
|
||||
})?),
|
||||
),
|
||||
};
|
||||
|
||||
let launch_request: Option<Result<StartDebuggingRequestArguments, _>> = request
|
||||
.arguments
|
||||
.as_ref()
|
||||
.map(|value| serde_json::from_value(value.clone()));
|
||||
|
||||
let mut success = true;
|
||||
if let Some(Ok(request)) = launch_request {
|
||||
cx.emit(DapStoreEvent::SpawnChildSession {
|
||||
request,
|
||||
parent_session: parent_session.clone(),
|
||||
});
|
||||
} else {
|
||||
log::error!(
|
||||
"Failed to parse launch request arguments: {:?}",
|
||||
request.arguments
|
||||
);
|
||||
success = false;
|
||||
}
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
parent_session
|
||||
.update(cx, |session, cx| {
|
||||
session.respond_to_client(
|
||||
request_seq,
|
||||
success,
|
||||
StartDebugging::COMMAND.to_string(),
|
||||
body,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
|
@ -752,7 +792,7 @@ impl DapStore {
|
|||
|
||||
let shutdown_parent_task = if let Some(parent_session) = session
|
||||
.read(cx)
|
||||
.parent_id()
|
||||
.parent_id(cx)
|
||||
.and_then(|session_id| self.session_by_id(session_id))
|
||||
{
|
||||
let shutdown_id = parent_session.update(cx, |parent_session, _| {
|
||||
|
@ -842,121 +882,6 @@ impl DapStore {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_new_session(
|
||||
session_id: SessionId,
|
||||
initialized_rx: oneshot::Receiver<()>,
|
||||
start_client_task: Task<Result<Entity<Session>, anyhow::Error>>,
|
||||
worktree: WeakEntity<Worktree>,
|
||||
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();
|
||||
})?;
|
||||
let seq_result = async || {
|
||||
session
|
||||
.update(cx, |session, cx| session.request_initialize(cx))?
|
||||
.await?;
|
||||
|
||||
session
|
||||
.update(cx, |session, cx| {
|
||||
session.initialize_sequence(initialized_rx, this.clone(), cx)
|
||||
})?
|
||||
.await
|
||||
};
|
||||
match seq_result().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.subscribe(
|
||||
&session,
|
||||
move |this: &mut DapStore, session, event: &SessionStateEvent, cx| match event {
|
||||
SessionStateEvent::Shutdown => {
|
||||
this.shutdown_session(session_id, cx).detach_and_log_err(cx);
|
||||
}
|
||||
SessionStateEvent::Restart => {
|
||||
let mut curr_session = session;
|
||||
while let Some(parent_id) = curr_session.read(cx).parent_id() {
|
||||
if let Some(parent_session) = this.sessions.get(&parent_id).cloned() {
|
||||
curr_session = parent_session;
|
||||
} else {
|
||||
log::error!("Failed to get parent session from parent session id");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let Some((config, binary)) = curr_session.read_with(cx, |session, _| {
|
||||
session
|
||||
.configuration()
|
||||
.map(|config| (config, session.root_binary().clone()))
|
||||
}) else {
|
||||
log::error!("Failed to get debug config from session");
|
||||
return;
|
||||
};
|
||||
|
||||
let session_id = curr_session.read(cx).session_id();
|
||||
|
||||
let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
|
||||
|
||||
let worktree = worktree.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
task.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.sessions.remove(&session_id);
|
||||
this.new_session(
|
||||
binary.as_ref().clone(),
|
||||
config,
|
||||
worktree,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.1
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
cx.emit(DapStoreEvent::DebugSessionInitialized(session_id));
|
||||
})?;
|
||||
|
||||
Ok(session)
|
||||
});
|
||||
task
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DapAdapterDelegate {
|
||||
fs: Arc<dyn Fs>,
|
||||
|
|
|
@ -28,7 +28,6 @@ use gpui::{
|
|||
Task, WeakEntity,
|
||||
};
|
||||
|
||||
use rpc::AnyProtoClient;
|
||||
use serde_json::{Value, json};
|
||||
use smol::stream::StreamExt;
|
||||
use std::any::TypeId;
|
||||
|
@ -115,54 +114,14 @@ impl From<dap::Thread> for Thread {
|
|||
}
|
||||
}
|
||||
|
||||
type UpstreamProjectId = u64;
|
||||
|
||||
struct RemoteConnection {
|
||||
_client: AnyProtoClient,
|
||||
_upstream_project_id: UpstreamProjectId,
|
||||
_adapter_name: SharedString,
|
||||
}
|
||||
|
||||
impl RemoteConnection {
|
||||
fn send_proto_client_request<R: DapCommand>(
|
||||
&self,
|
||||
_request: R,
|
||||
_session_id: SessionId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<R::Response>> {
|
||||
// let message = request.to_proto(session_id, self.upstream_project_id);
|
||||
// let upstream_client = self.client.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
// debugger(todo): Properly send messages when we wrap dap_commands in envelopes again
|
||||
// let response = upstream_client.request(message).await?;
|
||||
// request.response_from_proto(response)
|
||||
Err(anyhow!("Sending dap commands over RPC isn't supported yet"))
|
||||
})
|
||||
}
|
||||
|
||||
fn request<R: DapCommand>(
|
||||
&self,
|
||||
request: R,
|
||||
session_id: SessionId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<R::Response>>
|
||||
where
|
||||
<R::DapRequest as dap::requests::Request>::Response: 'static,
|
||||
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
|
||||
{
|
||||
return self.send_proto_client_request::<R>(request, session_id, cx);
|
||||
}
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
Local(LocalMode),
|
||||
Remote(RemoteConnection),
|
||||
Building,
|
||||
Running(LocalMode),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LocalMode {
|
||||
client: Arc<DebugAdapterClient>,
|
||||
definition: DebugTaskDefinition,
|
||||
binary: DebugAdapterBinary,
|
||||
root_binary: Option<Arc<DebugAdapterBinary>>,
|
||||
pub(crate) breakpoint_store: Entity<BreakpointStore>,
|
||||
|
@ -186,56 +145,47 @@ fn client_source(abs_path: &Path) -> dap::Source {
|
|||
}
|
||||
|
||||
impl LocalMode {
|
||||
fn new(
|
||||
async fn new(
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
worktree: WeakEntity<Worktree>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
config: DebugTaskDefinition,
|
||||
binary: DebugAdapterBinary,
|
||||
messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
|
||||
cx: AsyncApp,
|
||||
) -> Task<Result<Self>> {
|
||||
cx.spawn(async move |cx| {
|
||||
let message_handler = Box::new(move |message| {
|
||||
messages_tx.unbounded_send(message).ok();
|
||||
});
|
||||
) -> Result<Self> {
|
||||
let message_handler = Box::new(move |message| {
|
||||
messages_tx.unbounded_send(message).ok();
|
||||
});
|
||||
|
||||
let root_binary = if let Some(parent_session) = parent_session.as_ref() {
|
||||
Some(parent_session.read_with(cx, |session, _| session.root_binary().clone())?)
|
||||
let root_binary = if let Some(parent_session) = parent_session.as_ref() {
|
||||
Some(parent_session.read_with(&cx, |session, _| session.root_binary().clone())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let client = Arc::new(
|
||||
if let Some(client) = parent_session
|
||||
.and_then(|session| cx.update(|cx| session.read(cx).adapter_client()).ok())
|
||||
.flatten()
|
||||
{
|
||||
client
|
||||
.reconnect(session_id, binary.clone(), message_handler, cx.clone())
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let client = Arc::new(
|
||||
if let Some(client) = parent_session
|
||||
.and_then(|session| cx.update(|cx| session.read(cx).adapter_client()).ok())
|
||||
.flatten()
|
||||
{
|
||||
client
|
||||
.reconnect(session_id, binary.clone(), message_handler, cx.clone())
|
||||
.await?
|
||||
} else {
|
||||
DebugAdapterClient::start(
|
||||
session_id,
|
||||
binary.clone(),
|
||||
message_handler,
|
||||
cx.clone(),
|
||||
)
|
||||
DebugAdapterClient::start(session_id, binary.clone(), message_handler, cx.clone())
|
||||
.await
|
||||
.with_context(|| "Failed to start communication with debug adapter")?
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
client,
|
||||
breakpoint_store,
|
||||
worktree,
|
||||
tmp_breakpoint: None,
|
||||
definition: config,
|
||||
root_binary,
|
||||
binary,
|
||||
})
|
||||
Ok(Self {
|
||||
client,
|
||||
breakpoint_store,
|
||||
worktree,
|
||||
tmp_breakpoint: None,
|
||||
root_binary,
|
||||
binary,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -371,19 +321,10 @@ impl LocalMode {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn label(&self) -> String {
|
||||
self.definition.label.clone()
|
||||
}
|
||||
|
||||
fn request_initialization(&self, cx: &App) -> Task<Result<Capabilities>> {
|
||||
let adapter_id = self.definition.adapter.clone();
|
||||
|
||||
self.request(Initialize { adapter_id }, cx.background_executor().clone())
|
||||
}
|
||||
|
||||
fn initialize_sequence(
|
||||
&self,
|
||||
capabilities: &Capabilities,
|
||||
definition: &DebugTaskDefinition,
|
||||
initialized_rx: oneshot::Receiver<()>,
|
||||
dap_store: WeakEntity<DapStore>,
|
||||
cx: &App,
|
||||
|
@ -391,7 +332,7 @@ impl LocalMode {
|
|||
let mut raw = self.binary.request_args.clone();
|
||||
|
||||
merge_json_value_into(
|
||||
self.definition.initialize_args.clone().unwrap_or(json!({})),
|
||||
definition.initialize_args.clone().unwrap_or(json!({})),
|
||||
&mut raw.configuration,
|
||||
);
|
||||
|
||||
|
@ -426,9 +367,9 @@ impl LocalMode {
|
|||
let supports_exception_filters = capabilities
|
||||
.supports_exception_filter_options
|
||||
.unwrap_or_default();
|
||||
let this = self.clone();
|
||||
let worktree = self.worktree().clone();
|
||||
let configuration_sequence = cx.spawn({
|
||||
let this = self.clone();
|
||||
let worktree = self.worktree().clone();
|
||||
async move |cx| {
|
||||
initialized_rx.await?;
|
||||
let errors_by_path = cx
|
||||
|
@ -511,16 +452,10 @@ impl LocalMode {
|
|||
})
|
||||
}
|
||||
}
|
||||
impl From<RemoteConnection> for Mode {
|
||||
fn from(value: RemoteConnection) -> Self {
|
||||
Self::Remote(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
fn request_dap<R: DapCommand>(
|
||||
&self,
|
||||
session_id: SessionId,
|
||||
request: R,
|
||||
cx: &mut Context<Session>,
|
||||
) -> Task<Result<R::Response>>
|
||||
|
@ -529,10 +464,13 @@ impl Mode {
|
|||
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
|
||||
{
|
||||
match self {
|
||||
Mode::Local(debug_adapter_client) => {
|
||||
Mode::Running(debug_adapter_client) => {
|
||||
debug_adapter_client.request(request, cx.background_executor().clone())
|
||||
}
|
||||
Mode::Remote(remote_connection) => remote_connection.request(request, session_id, cx),
|
||||
Mode::Building => Task::ready(Err(anyhow!(
|
||||
"no adapter running to send request: {:?}",
|
||||
request
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -609,10 +547,11 @@ pub struct OutputToken(pub usize);
|
|||
/// Represents a current state of a single debug adapter and provides ways to mutate it.
|
||||
pub struct Session {
|
||||
mode: Mode,
|
||||
definition: DebugTaskDefinition,
|
||||
pub(super) capabilities: Capabilities,
|
||||
id: SessionId,
|
||||
child_session_ids: HashSet<SessionId>,
|
||||
parent_id: Option<SessionId>,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
ignore_breakpoints: bool,
|
||||
modules: Vec<dap::Module>,
|
||||
loaded_sources: Vec<dap::Source>,
|
||||
|
@ -626,7 +565,8 @@ pub struct Session {
|
|||
is_session_terminated: bool,
|
||||
requests: HashMap<TypeId, HashMap<RequestSlot, Shared<Task<Option<()>>>>>,
|
||||
exception_breakpoints: BTreeMap<String, (ExceptionBreakpointsFilter, IsEnabled)>,
|
||||
_background_tasks: Vec<Task<()>>,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
background_tasks: Vec<Task<()>>,
|
||||
}
|
||||
|
||||
trait CacheableCommand: Any + Send + Sync {
|
||||
|
@ -708,9 +648,12 @@ pub enum SessionEvent {
|
|||
StackTrace,
|
||||
Variables,
|
||||
Threads,
|
||||
CapabilitiesLoaded,
|
||||
}
|
||||
|
||||
pub(super) enum SessionStateEvent {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SessionStateEvent {
|
||||
Running,
|
||||
Shutdown,
|
||||
Restart,
|
||||
}
|
||||
|
@ -722,80 +665,140 @@ impl EventEmitter<SessionStateEvent> for Session {}
|
|||
// remote side will only send breakpoint updates when it is a breakpoint created by that peer
|
||||
// BreakpointStore notifies session on breakpoint changes
|
||||
impl Session {
|
||||
pub(crate) fn local(
|
||||
pub(crate) fn new(
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree: WeakEntity<Worktree>,
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
binary: DebugAdapterBinary,
|
||||
config: DebugTaskDefinition,
|
||||
template: DebugTaskDefinition,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
initialized_tx: oneshot::Sender<()>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
let (message_tx, message_rx) = futures::channel::mpsc::unbounded();
|
||||
) -> Entity<Self> {
|
||||
cx.new::<Self>(|cx| {
|
||||
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();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let this = Self {
|
||||
mode: Mode::Building,
|
||||
id: session_id,
|
||||
child_session_ids: HashSet::default(),
|
||||
parent_session,
|
||||
capabilities: Capabilities::default(),
|
||||
ignore_breakpoints: false,
|
||||
variables: Default::default(),
|
||||
stack_frames: Default::default(),
|
||||
thread_states: ThreadStates::default(),
|
||||
output_token: OutputToken(0),
|
||||
output: circular_buffer::CircularBuffer::boxed(),
|
||||
requests: HashMap::default(),
|
||||
modules: Vec::default(),
|
||||
loaded_sources: Vec::default(),
|
||||
threads: IndexMap::default(),
|
||||
background_tasks: Vec::default(),
|
||||
locations: Default::default(),
|
||||
is_session_terminated: false,
|
||||
exception_breakpoints: Default::default(),
|
||||
definition: template,
|
||||
start_debugging_requests_tx,
|
||||
};
|
||||
|
||||
this
|
||||
})
|
||||
}
|
||||
|
||||
pub fn worktree(&self) -> Option<Entity<Worktree>> {
|
||||
match &self.mode {
|
||||
Mode::Building => None,
|
||||
Mode::Running(local_mode) => local_mode.worktree.upgrade(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn boot(
|
||||
&mut self,
|
||||
binary: DebugAdapterBinary,
|
||||
worktree: Entity<Worktree>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
dap_store: WeakEntity<DapStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let (message_tx, mut message_rx) = futures::channel::mpsc::unbounded();
|
||||
let (initialized_tx, initialized_rx) = futures::channel::oneshot::channel();
|
||||
let session_id = self.session_id();
|
||||
|
||||
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(Ok(_)) = this.update(cx, |this, _| {
|
||||
this.start_debugging_requests_tx
|
||||
.unbounded_send((session_id, message))
|
||||
}) else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
})];
|
||||
self.background_tasks = background_tasks;
|
||||
let id = self.id;
|
||||
let parent_session = self.parent_session.clone();
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let mode = LocalMode::new(
|
||||
session_id,
|
||||
parent_session.clone(),
|
||||
worktree,
|
||||
id,
|
||||
parent_session,
|
||||
worktree.downgrade(),
|
||||
breakpoint_store.clone(),
|
||||
config.clone(),
|
||||
binary,
|
||||
message_tx,
|
||||
cx.clone(),
|
||||
)
|
||||
.await?;
|
||||
this.update(cx, |this, cx| {
|
||||
this.mode = Mode::Running(mode);
|
||||
cx.emit(SessionStateEvent::Running);
|
||||
})?;
|
||||
|
||||
cx.new(|cx| {
|
||||
create_local_session(
|
||||
breakpoint_store,
|
||||
session_id,
|
||||
parent_session,
|
||||
start_debugging_requests_tx,
|
||||
initialized_tx,
|
||||
message_rx,
|
||||
mode,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
this.update(cx, |session, cx| session.request_initialize(cx))?
|
||||
.await?;
|
||||
|
||||
this.update(cx, |session, cx| {
|
||||
session.initialize_sequence(initialized_rx, dap_store.clone(), cx)
|
||||
})?
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn remote(
|
||||
session_id: SessionId,
|
||||
client: AnyProtoClient,
|
||||
upstream_project_id: u64,
|
||||
ignore_breakpoints: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
mode: Mode::Remote(RemoteConnection {
|
||||
_adapter_name: SharedString::new(""), // todo(debugger) we need to pipe in the right values to deserialize the debugger pane layout
|
||||
_client: client,
|
||||
_upstream_project_id: upstream_project_id,
|
||||
}),
|
||||
id: session_id,
|
||||
child_session_ids: HashSet::default(),
|
||||
parent_id: None,
|
||||
capabilities: Capabilities::default(),
|
||||
ignore_breakpoints,
|
||||
variables: Default::default(),
|
||||
stack_frames: Default::default(),
|
||||
thread_states: ThreadStates::default(),
|
||||
output_token: OutputToken(0),
|
||||
output: circular_buffer::CircularBuffer::boxed(),
|
||||
requests: HashMap::default(),
|
||||
modules: Vec::default(),
|
||||
loaded_sources: Vec::default(),
|
||||
threads: IndexMap::default(),
|
||||
_background_tasks: Vec::default(),
|
||||
locations: Default::default(),
|
||||
is_session_terminated: false,
|
||||
exception_breakpoints: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_id(&self) -> SessionId {
|
||||
self.id
|
||||
}
|
||||
|
@ -812,8 +815,14 @@ impl Session {
|
|||
self.child_session_ids.remove(&session_id);
|
||||
}
|
||||
|
||||
pub fn parent_id(&self) -> Option<SessionId> {
|
||||
self.parent_id
|
||||
pub fn parent_id(&self, cx: &App) -> Option<SessionId> {
|
||||
self.parent_session
|
||||
.as_ref()
|
||||
.map(|session| session.read(cx).id)
|
||||
}
|
||||
|
||||
pub fn parent_session(&self) -> Option<&Entity<Self>> {
|
||||
self.parent_session.as_ref()
|
||||
}
|
||||
|
||||
pub fn capabilities(&self) -> &Capabilities {
|
||||
|
@ -821,35 +830,35 @@ impl Session {
|
|||
}
|
||||
|
||||
pub(crate) fn root_binary(&self) -> Arc<DebugAdapterBinary> {
|
||||
let Mode::Local(local_mode) = &self.mode else {
|
||||
panic!("Session is not local");
|
||||
};
|
||||
local_mode
|
||||
.root_binary
|
||||
.clone()
|
||||
.unwrap_or_else(|| Arc::new(local_mode.binary.clone()))
|
||||
match &self.mode {
|
||||
Mode::Building => {
|
||||
// todo(debugger): Implement root_binary for building mode
|
||||
unimplemented!()
|
||||
}
|
||||
Mode::Running(running) => running
|
||||
.root_binary
|
||||
.clone()
|
||||
.unwrap_or_else(|| Arc::new(running.binary.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn binary(&self) -> &DebugAdapterBinary {
|
||||
let Mode::Local(local_mode) = &self.mode else {
|
||||
let Mode::Running(local_mode) = &self.mode else {
|
||||
panic!("Session is not local");
|
||||
};
|
||||
&local_mode.binary
|
||||
}
|
||||
|
||||
pub fn adapter_name(&self) -> SharedString {
|
||||
match &self.mode {
|
||||
Mode::Local(local_mode) => local_mode.definition.adapter.clone().into(),
|
||||
Mode::Remote(remote_mode) => remote_mode._adapter_name.clone(),
|
||||
}
|
||||
self.definition.adapter.clone().into()
|
||||
}
|
||||
|
||||
pub fn configuration(&self) -> Option<DebugTaskDefinition> {
|
||||
if let Mode::Local(local_mode) = &self.mode {
|
||||
Some(local_mode.definition.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
pub fn label(&self) -> String {
|
||||
self.definition.label.clone()
|
||||
}
|
||||
|
||||
pub fn definition(&self) -> DebugTaskDefinition {
|
||||
self.definition.clone()
|
||||
}
|
||||
|
||||
pub fn is_terminated(&self) -> bool {
|
||||
|
@ -857,31 +866,33 @@ impl Session {
|
|||
}
|
||||
|
||||
pub fn is_local(&self) -> bool {
|
||||
matches!(self.mode, Mode::Local(_))
|
||||
matches!(self.mode, Mode::Running(_))
|
||||
}
|
||||
|
||||
pub fn as_local_mut(&mut self) -> Option<&mut LocalMode> {
|
||||
match &mut self.mode {
|
||||
Mode::Local(local_mode) => Some(local_mode),
|
||||
Mode::Remote(_) => None,
|
||||
Mode::Running(local_mode) => Some(local_mode),
|
||||
Mode::Building => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_local(&self) -> Option<&LocalMode> {
|
||||
match &self.mode {
|
||||
Mode::Local(local_mode) => Some(local_mode),
|
||||
Mode::Remote(_) => None,
|
||||
Mode::Running(local_mode) => Some(local_mode),
|
||||
Mode::Building => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn request_initialize(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
let adapter_id = self.definition.adapter.clone();
|
||||
let request = Initialize { adapter_id };
|
||||
match &self.mode {
|
||||
Mode::Local(local_mode) => {
|
||||
let capabilities = local_mode.clone().request_initialization(cx);
|
||||
Mode::Running(local_mode) => {
|
||||
let capabilities = local_mode.request(request, cx.background_executor().clone());
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let capabilities = capabilities.await?;
|
||||
this.update(cx, |session, _| {
|
||||
this.update(cx, |session, cx| {
|
||||
session.capabilities = capabilities;
|
||||
let filters = session
|
||||
.capabilities
|
||||
|
@ -895,12 +906,13 @@ impl Session {
|
|||
.entry(filter.filter.clone())
|
||||
.or_insert_with(|| (filter, default));
|
||||
}
|
||||
cx.emit(SessionEvent::CapabilitiesLoaded);
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
Mode::Remote(_) => Task::ready(Err(anyhow!(
|
||||
"Cannot send initialize request from remote session"
|
||||
Mode::Building => Task::ready(Err(anyhow!(
|
||||
"Cannot send initialize request, task still building"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
@ -912,10 +924,14 @@ impl Session {
|
|||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
match &self.mode {
|
||||
Mode::Local(local_mode) => {
|
||||
local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx)
|
||||
}
|
||||
Mode::Remote(_) => Task::ready(Err(anyhow!("cannot initialize remote session"))),
|
||||
Mode::Running(local_mode) => local_mode.initialize_sequence(
|
||||
&self.capabilities,
|
||||
&self.definition,
|
||||
initialize_rx,
|
||||
dap_store,
|
||||
cx,
|
||||
),
|
||||
Mode::Building => Task::ready(Err(anyhow!("cannot initialize, still building"))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -926,7 +942,7 @@ impl Session {
|
|||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match &mut self.mode {
|
||||
Mode::Local(local_mode) => {
|
||||
Mode::Running(local_mode) => {
|
||||
if !matches!(
|
||||
self.thread_states.thread_state(active_thread_id),
|
||||
Some(ThreadStatus::Stopped)
|
||||
|
@ -949,7 +965,7 @@ impl Session {
|
|||
})
|
||||
.detach();
|
||||
}
|
||||
Mode::Remote(_) => {}
|
||||
Mode::Building => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -983,13 +999,13 @@ impl Session {
|
|||
body: Option<serde_json::Value>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(local_session) = self.as_local().cloned() else {
|
||||
let Some(local_session) = self.as_local() else {
|
||||
unreachable!("Cannot respond to remote client");
|
||||
};
|
||||
let client = local_session.client.clone();
|
||||
|
||||
cx.background_spawn(async move {
|
||||
local_session
|
||||
.client
|
||||
client
|
||||
.send_message(Message::Response(Response {
|
||||
body,
|
||||
success,
|
||||
|
@ -1178,7 +1194,6 @@ impl Session {
|
|||
|
||||
let task = Self::request_inner::<Arc<T>>(
|
||||
&self.capabilities,
|
||||
self.id,
|
||||
&self.mode,
|
||||
command,
|
||||
process_result,
|
||||
|
@ -1199,7 +1214,6 @@ impl Session {
|
|||
|
||||
fn request_inner<T: DapCommand + PartialEq + Eq + Hash>(
|
||||
capabilities: &Capabilities,
|
||||
session_id: SessionId,
|
||||
mode: &Mode,
|
||||
request: T,
|
||||
process_result: impl FnOnce(
|
||||
|
@ -1225,7 +1239,7 @@ impl Session {
|
|||
});
|
||||
}
|
||||
|
||||
let request = mode.request_dap(session_id, request, cx);
|
||||
let request = mode.request_dap(request, cx);
|
||||
cx.spawn(async move |this, cx| {
|
||||
let result = request.await;
|
||||
this.update(cx, |this, cx| process_result(this, result, cx))
|
||||
|
@ -1245,14 +1259,7 @@ impl Session {
|
|||
+ 'static,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<T::Response>> {
|
||||
Self::request_inner(
|
||||
&self.capabilities,
|
||||
self.id,
|
||||
&self.mode,
|
||||
request,
|
||||
process_result,
|
||||
cx,
|
||||
)
|
||||
Self::request_inner(&self.capabilities, &self.mode, request, process_result, cx)
|
||||
}
|
||||
|
||||
fn invalidate_command_type<Command: DapCommand>(&mut self) {
|
||||
|
@ -1569,8 +1576,8 @@ impl Session {
|
|||
|
||||
pub fn adapter_client(&self) -> Option<Arc<DebugAdapterClient>> {
|
||||
match self.mode {
|
||||
Mode::Local(ref local) => Some(local.client.clone()),
|
||||
Mode::Remote(_) => None,
|
||||
Mode::Running(ref local) => Some(local.client.clone()),
|
||||
Mode::Building => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1936,83 +1943,3 @@ 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,
|
||||
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: Capabilities::default(),
|
||||
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(),
|
||||
exception_breakpoints: Default::default(),
|
||||
_background_tasks,
|
||||
is_session_terminated: false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,68 +1,39 @@
|
|||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use dap::{DebugRequest, client::DebugAdapterClient};
|
||||
use gpui::{App, AppContext, Entity, Subscription, Task};
|
||||
use task::DebugTaskDefinition;
|
||||
use dap::client::DebugAdapterClient;
|
||||
use gpui::{App, AppContext, Subscription};
|
||||
|
||||
use crate::Project;
|
||||
|
||||
use super::session::Session;
|
||||
use super::session::{Session, SessionStateEvent};
|
||||
|
||||
pub fn intercept_debug_sessions<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
configure: T,
|
||||
) -> Subscription {
|
||||
cx.update(|cx| {
|
||||
cx.observe_new::<Session>(move |session, _, cx| {
|
||||
let client = session.adapter_client().unwrap();
|
||||
register_default_handlers(session, &client, cx);
|
||||
configure(&client);
|
||||
cx.background_spawn(async move {
|
||||
client
|
||||
.fake_event(dap::messages::Events::Initialized(Some(Default::default())))
|
||||
.await
|
||||
let configure = Arc::new(configure);
|
||||
cx.observe_new::<Session>(move |_, _, cx| {
|
||||
let configure = configure.clone();
|
||||
cx.subscribe_self(move |session, event, cx| {
|
||||
let configure = configure.clone();
|
||||
if matches!(event, SessionStateEvent::Running) {
|
||||
let client = session.adapter_client().unwrap();
|
||||
register_default_handlers(session, &client, cx);
|
||||
configure(&client);
|
||||
cx.background_spawn(async move {
|
||||
client
|
||||
.fake_event(dap::messages::Events::Initialized(
|
||||
Some(Default::default()),
|
||||
))
|
||||
.await
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_debug_session_with<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||
project: &Entity<Project>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
config: DebugTaskDefinition,
|
||||
configure: T,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
let subscription = intercept_debug_sessions(cx, configure);
|
||||
let task = project.update(cx, |project, cx| project.start_debug_session(config, cx));
|
||||
cx.spawn(async move |_| {
|
||||
let result = task.await;
|
||||
drop(subscription);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_debug_session<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||
project: &Entity<Project>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
configure: T,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
start_debug_session_with(
|
||||
project,
|
||||
cx,
|
||||
DebugTaskDefinition {
|
||||
adapter: "fake-adapter".to_string(),
|
||||
request: DebugRequest::Launch(Default::default()),
|
||||
label: "test".to_string(),
|
||||
initialize_args: None,
|
||||
tcp_connection: None,
|
||||
stop_on_entry: None,
|
||||
},
|
||||
configure,
|
||||
)
|
||||
}
|
||||
|
||||
fn register_default_handlers(session: &Session, client: &Arc<DebugAdapterClient>, cx: &mut App) {
|
||||
client.on_request::<dap::requests::Initialize, _>(move |_, _| Ok(Default::default()));
|
||||
let paths = session
|
||||
|
|
|
@ -25,7 +25,6 @@ mod environment;
|
|||
use buffer_diff::BufferDiff;
|
||||
pub use environment::{EnvironmentErrorMessage, ProjectEnvironmentEvent};
|
||||
use git_store::{Repository, RepositoryId};
|
||||
use task::DebugTaskDefinition;
|
||||
pub mod search_history;
|
||||
mod yarn;
|
||||
|
||||
|
@ -39,17 +38,13 @@ use client::{
|
|||
};
|
||||
use clock::ReplicaId;
|
||||
|
||||
use dap::{
|
||||
adapters::{DebugAdapterBinary, TcpArguments},
|
||||
client::DebugAdapterClient,
|
||||
};
|
||||
use dap::client::DebugAdapterClient;
|
||||
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
use debounced_delay::DebouncedDelay;
|
||||
use debugger::{
|
||||
breakpoint_store::BreakpointStore,
|
||||
dap_store::{DapStore, DapStoreEvent},
|
||||
session::Session,
|
||||
};
|
||||
pub use environment::ProjectEnvironment;
|
||||
#[cfg(test)]
|
||||
|
@ -97,7 +92,6 @@ use snippet::Snippet;
|
|||
use snippet_provider::SnippetProvider;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
net::Ipv4Addr,
|
||||
ops::Range,
|
||||
path::{Component, Path, PathBuf},
|
||||
pin::pin,
|
||||
|
@ -107,7 +101,7 @@ use std::{
|
|||
};
|
||||
|
||||
use task_store::TaskStore;
|
||||
use terminals::{SshCommand, Terminals, wrap_for_ssh};
|
||||
use terminals::Terminals;
|
||||
use text::{Anchor, BufferId};
|
||||
use toolchain_store::EmptyToolchainStore;
|
||||
use util::{
|
||||
|
@ -1072,8 +1066,9 @@ impl Project {
|
|||
let dap_store = cx.new(|cx| {
|
||||
DapStore::new_ssh(
|
||||
SSH_PROJECT_ID,
|
||||
ssh_proto.clone(),
|
||||
ssh.clone(),
|
||||
breakpoint_store.clone(),
|
||||
worktree_store.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -1258,6 +1253,7 @@ impl Project {
|
|||
remote_id,
|
||||
client.clone().into(),
|
||||
breakpoint_store.clone(),
|
||||
worktree_store.clone(),
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
@ -1463,79 +1459,6 @@ impl Project {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn start_debug_session(
|
||||
&mut self,
|
||||
definition: DebugTaskDefinition,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
let Some(worktree) = self.worktrees(cx).find(|tree| tree.read(cx).is_visible()) else {
|
||||
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
||||
};
|
||||
|
||||
let ssh_client = self.ssh_client().clone();
|
||||
|
||||
let result = cx.spawn(async move |this, cx| {
|
||||
let mut binary = this
|
||||
.update(cx, |this, cx| {
|
||||
this.dap_store.update(cx, |dap_store, cx| {
|
||||
dap_store.get_debug_adapter_binary(definition.clone(), cx)
|
||||
})
|
||||
})?
|
||||
.await?;
|
||||
|
||||
if let Some(ssh_client) = ssh_client {
|
||||
let mut ssh_command = ssh_client.update(cx, |ssh, _| {
|
||||
anyhow::Ok(SshCommand {
|
||||
arguments: ssh
|
||||
.ssh_args()
|
||||
.ok_or_else(|| anyhow!("SSH arguments not found"))?,
|
||||
})
|
||||
})??;
|
||||
|
||||
let mut connection = None;
|
||||
if let Some(c) = binary.connection {
|
||||
let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1);
|
||||
let port = dap::transport::TcpTransport::unused_port(local_bind_addr).await?;
|
||||
|
||||
ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);
|
||||
connection = Some(TcpArguments {
|
||||
port: c.port,
|
||||
host: local_bind_addr,
|
||||
timeout: c.timeout,
|
||||
})
|
||||
}
|
||||
|
||||
let (program, args) = wrap_for_ssh(
|
||||
&ssh_command,
|
||||
Some((&binary.command, &binary.arguments)),
|
||||
binary.cwd.as_deref(),
|
||||
binary.envs,
|
||||
None,
|
||||
);
|
||||
|
||||
binary = DebugAdapterBinary {
|
||||
command: program,
|
||||
arguments: args,
|
||||
envs: HashMap::default(),
|
||||
cwd: None,
|
||||
connection,
|
||||
request_args: binary.request_args,
|
||||
}
|
||||
};
|
||||
|
||||
let ret = this
|
||||
.update(cx, |project, cx| {
|
||||
project.dap_store.update(cx, |dap_store, cx| {
|
||||
dap_store.new_session(binary, definition, worktree.downgrade(), None, cx)
|
||||
})
|
||||
})?
|
||||
.1
|
||||
.await;
|
||||
ret
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn example(
|
||||
root_paths: impl IntoIterator<Item = &Path>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue