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
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -3042,6 +3042,7 @@ dependencies = [
|
||||||
"strum 0.27.1",
|
"strum 0.27.1",
|
||||||
"subtle",
|
"subtle",
|
||||||
"supermaven_api",
|
"supermaven_api",
|
||||||
|
"task",
|
||||||
"telemetry_events",
|
"telemetry_events",
|
||||||
"text",
|
"text",
|
||||||
"theme",
|
"theme",
|
||||||
|
@ -4189,6 +4190,7 @@ dependencies = [
|
||||||
"command_palette_hooks",
|
"command_palette_hooks",
|
||||||
"dap",
|
"dap",
|
||||||
"db",
|
"db",
|
||||||
|
"debugger_tools",
|
||||||
"editor",
|
"editor",
|
||||||
"env_logger 0.11.8",
|
"env_logger 0.11.8",
|
||||||
"feature_flags",
|
"feature_flags",
|
||||||
|
@ -4198,6 +4200,7 @@ dependencies = [
|
||||||
"language",
|
"language",
|
||||||
"log",
|
"log",
|
||||||
"menu",
|
"menu",
|
||||||
|
"parking_lot",
|
||||||
"picker",
|
"picker",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"project",
|
"project",
|
||||||
|
|
|
@ -128,6 +128,7 @@ serde_json.workspace = true
|
||||||
session = { workspace = true, features = ["test-support"] }
|
session = { workspace = true, features = ["test-support"] }
|
||||||
settings = { workspace = true, features = ["test-support"] }
|
settings = { workspace = true, features = ["test-support"] }
|
||||||
sqlx = { version = "0.8", features = ["sqlite"] }
|
sqlx = { version = "0.8", features = ["sqlite"] }
|
||||||
|
task.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
unindent.workspace = true
|
unindent.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use dap::requests::{Initialize, Launch, StackTrace};
|
|
||||||
use dap::DebugRequestType;
|
use dap::DebugRequestType;
|
||||||
use dap::{requests::SetBreakpoints, SourceBreakpoint};
|
use dap::requests::{Initialize, Launch, StackTrace};
|
||||||
|
use dap::{SourceBreakpoint, requests::SetBreakpoints};
|
||||||
use debugger_ui::debugger_panel::DebugPanel;
|
use debugger_ui::debugger_panel::DebugPanel;
|
||||||
use debugger_ui::session::DebugSession;
|
use debugger_ui::session::DebugSession;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
|
@ -13,7 +13,7 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::atomic::{AtomicBool, Ordering},
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
};
|
};
|
||||||
use workspace::{dock::Panel, Workspace};
|
use workspace::{Workspace, dock::Panel};
|
||||||
|
|
||||||
use super::{TestClient, TestServer};
|
use super::{TestClient, TestServer};
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,13 @@ use crate::tests::TestServer;
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use debugger_ui::debugger_panel::DebugPanel;
|
||||||
use extension::ExtensionHostProxy;
|
use extension::ExtensionHostProxy;
|
||||||
use fs::{FakeFs, Fs as _, RemoveOptions};
|
use fs::{FakeFs, Fs as _, RemoveOptions};
|
||||||
use futures::StreamExt as _;
|
use futures::StreamExt as _;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext as _, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal as _,
|
AppContext as _, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal as _,
|
||||||
|
VisualContext,
|
||||||
};
|
};
|
||||||
use http_client::BlockedHttpClient;
|
use http_client::BlockedHttpClient;
|
||||||
use language::{
|
use language::{
|
||||||
|
@ -24,6 +26,7 @@ use project::{
|
||||||
};
|
};
|
||||||
use remote::SshRemoteClient;
|
use remote::SshRemoteClient;
|
||||||
use remote_server::{HeadlessAppState, HeadlessProject};
|
use remote_server::{HeadlessAppState, HeadlessProject};
|
||||||
|
use rpc::proto;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use std::{path::Path, sync::Arc};
|
use std::{path::Path, sync::Arc};
|
||||||
|
@ -576,3 +579,108 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
||||||
"Prettier formatting was not applied to client buffer after host's request"
|
"Prettier formatting was not applied to client buffer after host's request"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_remote_server_debugger(cx_a: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
||||||
|
cx_a.update(|cx| {
|
||||||
|
release_channel::init(SemanticVersion::default(), cx);
|
||||||
|
command_palette_hooks::init(cx);
|
||||||
|
if std::env::var("RUST_LOG").is_ok() {
|
||||||
|
env_logger::try_init().ok();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server_cx.update(|cx| {
|
||||||
|
release_channel::init(SemanticVersion::default(), cx);
|
||||||
|
});
|
||||||
|
let (opts, server_ssh) = SshRemoteClient::fake_server(cx_a, server_cx);
|
||||||
|
let remote_fs = FakeFs::new(server_cx.executor());
|
||||||
|
remote_fs
|
||||||
|
.insert_tree(
|
||||||
|
path!("/code"),
|
||||||
|
json!({
|
||||||
|
"lib.rs": "fn one() -> usize { 1 }"
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// User A connects to the remote project via SSH.
|
||||||
|
server_cx.update(HeadlessProject::init);
|
||||||
|
let remote_http_client = Arc::new(BlockedHttpClient);
|
||||||
|
let node = NodeRuntime::unavailable();
|
||||||
|
let languages = Arc::new(LanguageRegistry::new(server_cx.executor()));
|
||||||
|
let _headless_project = server_cx.new(|cx| {
|
||||||
|
client::init_settings(cx);
|
||||||
|
HeadlessProject::new(
|
||||||
|
HeadlessAppState {
|
||||||
|
session: server_ssh,
|
||||||
|
fs: remote_fs.clone(),
|
||||||
|
http_client: remote_http_client,
|
||||||
|
node_runtime: node,
|
||||||
|
languages,
|
||||||
|
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let client_ssh = SshRemoteClient::fake_client(opts, cx_a).await;
|
||||||
|
let mut server = TestServer::start(server_cx.executor()).await;
|
||||||
|
let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
|
cx_a.update(|cx| {
|
||||||
|
debugger_ui::init(cx);
|
||||||
|
command_palette_hooks::init(cx);
|
||||||
|
});
|
||||||
|
let (project_a, _) = client_a
|
||||||
|
.build_ssh_project(path!("/code"), client_ssh.clone(), cx_a)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (workspace, cx_a) = client_a.build_workspace(&project_a, cx_a);
|
||||||
|
|
||||||
|
let debugger_panel = workspace
|
||||||
|
.update_in(cx_a, |_workspace, window, cx| {
|
||||||
|
cx.spawn_in(window, DebugPanel::load)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
workspace.update_in(cx_a, |workspace, window, cx| {
|
||||||
|
workspace.add_panel(debugger_panel, window, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
cx_a.run_until_parked();
|
||||||
|
let debug_panel = workspace
|
||||||
|
.update(cx_a, |workspace, cx| workspace.panel::<DebugPanel>(cx))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let workspace_window = cx_a
|
||||||
|
.window_handle()
|
||||||
|
.downcast::<workspace::Workspace>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let session = debugger_ui::tests::start_debug_session(&workspace_window, cx_a, |_| {}).unwrap();
|
||||||
|
cx_a.run_until_parked();
|
||||||
|
debug_panel.update(cx_a, |debug_panel, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
debug_panel.active_session().unwrap().read(cx).session(cx),
|
||||||
|
session
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
session.update(cx_a, |session, _| {
|
||||||
|
assert_eq!(session.binary().command, "ssh");
|
||||||
|
});
|
||||||
|
|
||||||
|
let shutdown_session = workspace.update(cx_a, |workspace, cx| {
|
||||||
|
workspace.project().update(cx, |project, cx| {
|
||||||
|
project.dap_store().update(cx, |dap_store, cx| {
|
||||||
|
dap_store.shutdown_session(session.read(cx).session_id(), cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
client_ssh.update(cx_a, |a, _| {
|
||||||
|
a.shutdown_processes(Some(proto::ShutdownRemoteServer {}))
|
||||||
|
});
|
||||||
|
|
||||||
|
shutdown_session.await.unwrap();
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use task::TcpArgumentsTemplate;
|
use task::TcpArgumentsTemplate;
|
||||||
use util::ResultExt as _;
|
use util::{ResultExt as _, TryFutureExt};
|
||||||
|
|
||||||
use crate::{adapters::DebugAdapterBinary, debugger_settings::DebuggerSettings};
|
use crate::{adapters::DebugAdapterBinary, debugger_settings::DebuggerSettings};
|
||||||
|
|
||||||
|
@ -126,6 +126,7 @@ pub(crate) struct TransportDelegate {
|
||||||
pending_requests: Requests,
|
pending_requests: Requests,
|
||||||
transport: Transport,
|
transport: Transport,
|
||||||
server_tx: Arc<Mutex<Option<Sender<Message>>>>,
|
server_tx: Arc<Mutex<Option<Sender<Message>>>>,
|
||||||
|
_tasks: Vec<gpui::Task<Option<()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransportDelegate {
|
impl TransportDelegate {
|
||||||
|
@ -140,6 +141,7 @@ impl TransportDelegate {
|
||||||
log_handlers: Default::default(),
|
log_handlers: Default::default(),
|
||||||
current_requests: Default::default(),
|
current_requests: Default::default(),
|
||||||
pending_requests: Default::default(),
|
pending_requests: Default::default(),
|
||||||
|
_tasks: Default::default(),
|
||||||
};
|
};
|
||||||
let messages = this.start_handlers(transport_pipes, cx).await?;
|
let messages = this.start_handlers(transport_pipes, cx).await?;
|
||||||
Ok((messages, this))
|
Ok((messages, this))
|
||||||
|
@ -166,35 +168,43 @@ impl TransportDelegate {
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
if let Some(stdout) = params.stdout.take() {
|
if let Some(stdout) = params.stdout.take() {
|
||||||
cx.background_executor()
|
self._tasks.push(
|
||||||
.spawn(Self::handle_adapter_log(stdout, log_handler.clone()))
|
cx.background_executor()
|
||||||
.detach_and_log_err(cx);
|
.spawn(Self::handle_adapter_log(stdout, log_handler.clone()).log_err()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.background_executor()
|
self._tasks.push(
|
||||||
.spawn(Self::handle_output(
|
cx.background_executor().spawn(
|
||||||
params.output,
|
Self::handle_output(
|
||||||
client_tx,
|
params.output,
|
||||||
self.pending_requests.clone(),
|
client_tx,
|
||||||
log_handler.clone(),
|
self.pending_requests.clone(),
|
||||||
))
|
log_handler.clone(),
|
||||||
.detach_and_log_err(cx);
|
)
|
||||||
|
.log_err(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(stderr) = params.stderr.take() {
|
if let Some(stderr) = params.stderr.take() {
|
||||||
cx.background_executor()
|
self._tasks.push(
|
||||||
.spawn(Self::handle_error(stderr, self.log_handlers.clone()))
|
cx.background_executor()
|
||||||
.detach_and_log_err(cx);
|
.spawn(Self::handle_error(stderr, self.log_handlers.clone()).log_err()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.background_executor()
|
self._tasks.push(
|
||||||
.spawn(Self::handle_input(
|
cx.background_executor().spawn(
|
||||||
params.input,
|
Self::handle_input(
|
||||||
client_rx,
|
params.input,
|
||||||
self.current_requests.clone(),
|
client_rx,
|
||||||
self.pending_requests.clone(),
|
self.current_requests.clone(),
|
||||||
log_handler.clone(),
|
self.pending_requests.clone(),
|
||||||
))
|
log_handler.clone(),
|
||||||
.detach_and_log_err(cx);
|
)
|
||||||
|
.log_err(),
|
||||||
|
),
|
||||||
|
);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -367,6 +377,7 @@ impl TransportDelegate {
|
||||||
where
|
where
|
||||||
Stderr: AsyncRead + Unpin + Send + 'static,
|
Stderr: AsyncRead + Unpin + Send + 'static,
|
||||||
{
|
{
|
||||||
|
log::debug!("Handle error started");
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
|
|
||||||
let mut reader = BufReader::new(stderr);
|
let mut reader = BufReader::new(stderr);
|
||||||
|
|
|
@ -12,6 +12,9 @@ workspace = true
|
||||||
path = "src/debugger_tools.rs"
|
path = "src/debugger_tools.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test-support = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
dap.workspace = true
|
dap.workspace = true
|
||||||
|
|
|
@ -41,7 +41,7 @@ struct DapLogView {
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LogStore {
|
pub struct LogStore {
|
||||||
projects: HashMap<WeakEntity<Project>, ProjectState>,
|
projects: HashMap<WeakEntity<Project>, ProjectState>,
|
||||||
debug_clients: HashMap<SessionId, DebugAdapterState>,
|
debug_clients: HashMap<SessionId, DebugAdapterState>,
|
||||||
rpc_tx: UnboundedSender<(SessionId, IoKind, String)>,
|
rpc_tx: UnboundedSender<(SessionId, IoKind, String)>,
|
||||||
|
@ -101,7 +101,7 @@ impl DebugAdapterState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogStore {
|
impl LogStore {
|
||||||
fn new(cx: &Context<Self>) -> Self {
|
pub fn new(cx: &Context<Self>) -> Self {
|
||||||
let (rpc_tx, mut rpc_rx) = unbounded::<(SessionId, IoKind, String)>();
|
let (rpc_tx, mut rpc_rx) = unbounded::<(SessionId, IoKind, String)>();
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
while let Some((client_id, io_kind, message)) = rpc_rx.next().await {
|
while let Some((client_id, io_kind, message)) = rpc_rx.next().await {
|
||||||
|
@ -845,3 +845,29 @@ impl EventEmitter<Event> for LogStore {}
|
||||||
impl EventEmitter<Event> for DapLogView {}
|
impl EventEmitter<Event> for DapLogView {}
|
||||||
impl EventEmitter<EditorEvent> for DapLogView {}
|
impl EventEmitter<EditorEvent> for DapLogView {}
|
||||||
impl EventEmitter<SearchEvent> for DapLogView {}
|
impl EventEmitter<SearchEvent> for DapLogView {}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
impl LogStore {
|
||||||
|
pub fn contained_session_ids(&self) -> Vec<SessionId> {
|
||||||
|
self.debug_clients.keys().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rpc_messages_for_session_id(&self, session_id: SessionId) -> Vec<String> {
|
||||||
|
self.debug_clients
|
||||||
|
.get(&session_id)
|
||||||
|
.expect("This session should exist if a test is calling")
|
||||||
|
.rpc_messages
|
||||||
|
.messages
|
||||||
|
.clone()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_messages_for_session_id(&self, session_id: SessionId) -> Vec<String> {
|
||||||
|
self.debug_clients
|
||||||
|
.get(&session_id)
|
||||||
|
.expect("This session should exist if a test is calling")
|
||||||
|
.log_messages
|
||||||
|
.clone()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ test-support = [
|
||||||
"project/test-support",
|
"project/test-support",
|
||||||
"util/test-support",
|
"util/test-support",
|
||||||
"workspace/test-support",
|
"workspace/test-support",
|
||||||
|
"env_logger",
|
||||||
|
"unindent",
|
||||||
|
"debugger_tools"
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -37,6 +40,7 @@ gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
|
parking_lot.workspace = true
|
||||||
picker.workspace = true
|
picker.workspace = true
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
|
@ -53,9 +57,13 @@ ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
env_logger = { workspace = true, optional = true }
|
||||||
|
debugger_tools = { workspace = true, optional = true }
|
||||||
|
unindent = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dap = { workspace = true, features = ["test-support"] }
|
dap = { workspace = true, features = ["test-support"] }
|
||||||
|
debugger_tools = { workspace = true, features = ["test-support"] }
|
||||||
editor = { workspace = true, features = ["test-support"] }
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use dap::DebugRequest;
|
use dap::DebugRequest;
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::Subscription;
|
|
||||||
use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render};
|
use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render};
|
||||||
|
use gpui::{Subscription, WeakEntity};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -9,7 +9,9 @@ use sysinfo::System;
|
||||||
use ui::{Context, Tooltip, prelude::*};
|
use ui::{Context, Tooltip, prelude::*};
|
||||||
use ui::{ListItem, ListItemSpacing};
|
use ui::{ListItem, ListItemSpacing};
|
||||||
use util::debug_panic;
|
use util::debug_panic;
|
||||||
use workspace::ModalView;
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
|
use crate::debugger_panel::DebugPanel;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(super) struct Candidate {
|
pub(super) struct Candidate {
|
||||||
|
@ -22,19 +24,19 @@ pub(crate) struct AttachModalDelegate {
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
matches: Vec<StringMatch>,
|
matches: Vec<StringMatch>,
|
||||||
placeholder_text: Arc<str>,
|
placeholder_text: Arc<str>,
|
||||||
project: Entity<project::Project>,
|
workspace: WeakEntity<Workspace>,
|
||||||
pub(crate) debug_config: task::DebugTaskDefinition,
|
pub(crate) debug_config: task::DebugTaskDefinition,
|
||||||
candidates: Arc<[Candidate]>,
|
candidates: Arc<[Candidate]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AttachModalDelegate {
|
impl AttachModalDelegate {
|
||||||
fn new(
|
fn new(
|
||||||
project: Entity<project::Project>,
|
workspace: Entity<Workspace>,
|
||||||
debug_config: task::DebugTaskDefinition,
|
debug_config: task::DebugTaskDefinition,
|
||||||
candidates: Arc<[Candidate]>,
|
candidates: Arc<[Candidate]>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
project,
|
workspace: workspace.downgrade(),
|
||||||
debug_config,
|
debug_config,
|
||||||
candidates,
|
candidates,
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
|
@ -51,7 +53,7 @@ pub struct AttachModal {
|
||||||
|
|
||||||
impl AttachModal {
|
impl AttachModal {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
project: Entity<project::Project>,
|
workspace: Entity<Workspace>,
|
||||||
debug_config: task::DebugTaskDefinition,
|
debug_config: task::DebugTaskDefinition,
|
||||||
modal: bool,
|
modal: bool,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
|
@ -75,11 +77,11 @@ impl AttachModal {
|
||||||
.collect();
|
.collect();
|
||||||
processes.sort_by_key(|k| k.name.clone());
|
processes.sort_by_key(|k| k.name.clone());
|
||||||
let processes = processes.into_iter().collect();
|
let processes = processes.into_iter().collect();
|
||||||
Self::with_processes(project, debug_config, processes, modal, window, cx)
|
Self::with_processes(workspace, debug_config, processes, modal, window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn with_processes(
|
pub(super) fn with_processes(
|
||||||
project: Entity<project::Project>,
|
workspace: Entity<Workspace>,
|
||||||
debug_config: task::DebugTaskDefinition,
|
debug_config: task::DebugTaskDefinition,
|
||||||
processes: Arc<[Candidate]>,
|
processes: Arc<[Candidate]>,
|
||||||
modal: bool,
|
modal: bool,
|
||||||
|
@ -88,7 +90,7 @@ impl AttachModal {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let picker = cx.new(|cx| {
|
let picker = cx.new(|cx| {
|
||||||
Picker::uniform_list(
|
Picker::uniform_list(
|
||||||
AttachModalDelegate::new(project, debug_config, processes),
|
AttachModalDelegate::new(workspace, debug_config, processes),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -202,7 +204,7 @@ impl PickerDelegate for AttachModalDelegate {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
let candidate = self
|
let candidate = self
|
||||||
.matches
|
.matches
|
||||||
.get(self.selected_index())
|
.get(self.selected_index())
|
||||||
|
@ -225,14 +227,17 @@ impl PickerDelegate for AttachModalDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = self.debug_config.clone();
|
let definition = self.debug_config.clone();
|
||||||
self.project
|
let panel = self
|
||||||
.update(cx, |project, cx| {
|
.workspace
|
||||||
let ret = project.start_debug_session(config, cx);
|
.update(cx, |workspace, cx| workspace.panel::<DebugPanel>(cx))
|
||||||
ret
|
.ok()
|
||||||
})
|
.flatten();
|
||||||
.detach_and_log_err(cx);
|
if let Some(panel) = panel {
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.start_session(definition, window, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::{new_session_modal::NewSessionModal, session::DebugSession};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
|
use dap::StartDebuggingRequestArguments;
|
||||||
use dap::{
|
use dap::{
|
||||||
ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
|
ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
|
||||||
client::SessionId, debugger_settings::DebuggerSettings,
|
client::SessionId, debugger_settings::DebuggerSettings,
|
||||||
|
@ -17,6 +18,7 @@ use gpui::{
|
||||||
actions, anchored, deferred,
|
actions, anchored, deferred,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use project::debugger::session::{Session, SessionStateEvent};
|
||||||
use project::{
|
use project::{
|
||||||
Project,
|
Project,
|
||||||
debugger::{
|
debugger::{
|
||||||
|
@ -30,10 +32,9 @@ use settings::Settings;
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use task::DebugTaskDefinition;
|
use task::{DebugTaskDefinition, DebugTaskTemplate};
|
||||||
use terminal_view::terminal_panel::TerminalPanel;
|
use terminal_view::terminal_panel::TerminalPanel;
|
||||||
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
|
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
|
||||||
use util::debug_panic;
|
|
||||||
use workspace::{
|
use workspace::{
|
||||||
Workspace,
|
Workspace,
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
|
@ -63,7 +64,7 @@ pub struct DebugPanel {
|
||||||
active_session: Option<Entity<DebugSession>>,
|
active_session: Option<Entity<DebugSession>>,
|
||||||
/// This represents the last debug definition that was created in the new session modal
|
/// This represents the last debug definition that was created in the new session modal
|
||||||
pub(crate) past_debug_definition: Option<DebugTaskDefinition>,
|
pub(crate) past_debug_definition: Option<DebugTaskDefinition>,
|
||||||
project: WeakEntity<Project>,
|
project: Entity<Project>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||||
|
@ -97,10 +98,10 @@ impl DebugPanel {
|
||||||
window,
|
window,
|
||||||
|panel, _, event: &tasks_ui::ShowAttachModal, window, cx| {
|
|panel, _, event: &tasks_ui::ShowAttachModal, window, cx| {
|
||||||
panel.workspace.update(cx, |workspace, cx| {
|
panel.workspace.update(cx, |workspace, cx| {
|
||||||
let project = workspace.project().clone();
|
let workspace_handle = cx.entity().clone();
|
||||||
workspace.toggle_modal(window, cx, |window, cx| {
|
workspace.toggle_modal(window, cx, |window, cx| {
|
||||||
crate::attach_modal::AttachModal::new(
|
crate::attach_modal::AttachModal::new(
|
||||||
project,
|
workspace_handle,
|
||||||
event.debug_config.clone(),
|
event.debug_config.clone(),
|
||||||
true,
|
true,
|
||||||
window,
|
window,
|
||||||
|
@ -127,7 +128,7 @@ impl DebugPanel {
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
past_debug_definition: None,
|
past_debug_definition: None,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
project: project.downgrade(),
|
project,
|
||||||
workspace: workspace.weak_handle(),
|
workspace: workspace.weak_handle(),
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
};
|
};
|
||||||
|
@ -219,7 +220,7 @@ impl DebugPanel {
|
||||||
|
|
||||||
pub fn load(
|
pub fn load(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
cx: AsyncWindowContext,
|
cx: &mut AsyncWindowContext,
|
||||||
) -> Task<Result<Entity<Self>>> {
|
) -> Task<Result<Entity<Self>>> {
|
||||||
cx.spawn(async move |cx| {
|
cx.spawn(async move |cx| {
|
||||||
workspace.update_in(cx, |workspace, window, cx| {
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
|
@ -245,114 +246,226 @@ impl DebugPanel {
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));
|
||||||
|
|
||||||
debug_panel
|
debug_panel
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_session(
|
||||||
|
&mut self,
|
||||||
|
definition: DebugTaskDefinition,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let task_contexts = self
|
||||||
|
.workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
tasks_ui::task_contexts(workspace, window, cx)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
let dap_store = self.project.read(cx).dap_store().clone();
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let task_context = if let Some(task) = task_contexts {
|
||||||
|
task.await
|
||||||
|
.active_worktree_context
|
||||||
|
.map_or(task::TaskContext::default(), |context| context.1)
|
||||||
|
} else {
|
||||||
|
task::TaskContext::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (session, task) = dap_store.update(cx, |dap_store, cx| {
|
||||||
|
let template = DebugTaskTemplate {
|
||||||
|
locator: None,
|
||||||
|
definition: definition.clone(),
|
||||||
|
};
|
||||||
|
let session = if let Some(debug_config) = template
|
||||||
|
.to_zed_format()
|
||||||
|
.resolve_task("debug_task", &task_context)
|
||||||
|
.and_then(|resolved_task| resolved_task.resolved_debug_adapter_config())
|
||||||
|
{
|
||||||
|
dap_store.new_session(debug_config.definition, None, cx)
|
||||||
|
} else {
|
||||||
|
dap_store.new_session(definition.clone(), None, cx)
|
||||||
|
};
|
||||||
|
|
||||||
|
(session.clone(), dap_store.boot_session(session, cx))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match task.await {
|
||||||
|
Err(e) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
workspace.show_error(&e, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
session
|
||||||
|
.update(cx, |session, cx| session.shutdown(cx))?
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Ok(_) => Self::register_session(this, session, cx).await?,
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn register_session(
|
||||||
|
this: WeakEntity<Self>,
|
||||||
|
session: Entity<Session>,
|
||||||
|
cx: &mut AsyncWindowContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let adapter_name = session.update(cx, |session, _| session.adapter_name())?;
|
||||||
|
this.update_in(cx, |_, window, cx| {
|
||||||
|
cx.subscribe_in(
|
||||||
|
&session,
|
||||||
|
window,
|
||||||
|
move |_, session, event: &SessionStateEvent, window, cx| match event {
|
||||||
|
SessionStateEvent::Restart => {
|
||||||
|
let mut curr_session = session.clone();
|
||||||
|
while let Some(parent_session) = curr_session
|
||||||
|
.read_with(cx, |session, _| session.parent_session().cloned())
|
||||||
|
{
|
||||||
|
curr_session = parent_session;
|
||||||
|
}
|
||||||
|
|
||||||
|
let definition = curr_session.update(cx, |session, _| session.definition());
|
||||||
|
let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
|
||||||
|
|
||||||
|
let definition = definition.clone();
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
task.await;
|
||||||
|
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
this.start_session(definition, window, cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.detach();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
let serialized_layout = persistence::get_serialized_pane_layout(adapter_name).await;
|
||||||
|
|
||||||
|
let workspace = this.update_in(cx, |this, window, cx| {
|
||||||
|
this.sessions.retain(|session| {
|
||||||
|
session
|
||||||
|
.read(cx)
|
||||||
|
.mode()
|
||||||
|
.as_running()
|
||||||
|
.map_or(false, |running_state| {
|
||||||
|
!running_state.read(cx).session().read(cx).is_terminated()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let session_item = DebugSession::running(
|
||||||
|
this.project.clone(),
|
||||||
|
this.workspace.clone(),
|
||||||
|
session,
|
||||||
|
cx.weak_entity(),
|
||||||
|
serialized_layout,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(running) = session_item.read(cx).mode().as_running().cloned() {
|
||||||
|
// We might want to make this an event subscription and only notify when a new thread is selected
|
||||||
|
// This is used to filter the command menu correctly
|
||||||
|
cx.observe(&running, |_, _, cx| cx.notify()).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sessions.push(session_item.clone());
|
||||||
|
this.activate_session(session_item, window, cx);
|
||||||
|
this.workspace.clone()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
|
workspace.focus_panel::<Self>(window, cx);
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_child_session(
|
||||||
|
&mut self,
|
||||||
|
request: &StartDebuggingRequestArguments,
|
||||||
|
parent_session: Entity<Session>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let Some(worktree) = parent_session.read(cx).worktree() else {
|
||||||
|
log::error!("Attempted to start a child session from non local debug session");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let dap_store_handle = self.project.read(cx).dap_store().clone();
|
||||||
|
let breakpoint_store = self.project.read(cx).breakpoint_store();
|
||||||
|
let definition = parent_session.read(cx).definition().clone();
|
||||||
|
let mut binary = parent_session.read(cx).binary().clone();
|
||||||
|
binary.request_args = request.clone();
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
|
||||||
|
let session =
|
||||||
|
dap_store.new_session(definition.clone(), Some(parent_session.clone()), cx);
|
||||||
|
|
||||||
|
let task = session.update(cx, |session, cx| {
|
||||||
|
session.boot(
|
||||||
|
binary,
|
||||||
|
worktree,
|
||||||
|
breakpoint_store,
|
||||||
|
dap_store_handle.downgrade(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
(session, task)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match task.await {
|
||||||
|
Err(e) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.workspace
|
||||||
|
.update(cx, |workspace, cx| {
|
||||||
|
workspace.show_error(&e, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
session
|
||||||
|
.update(cx, |session, cx| session.shutdown(cx))?
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Ok(_) => Self::register_session(this, session, cx).await?,
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
|
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
|
||||||
self.active_session.clone()
|
self.active_session.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_panel_items_by_client(
|
|
||||||
&self,
|
|
||||||
client_id: &SessionId,
|
|
||||||
cx: &Context<Self>,
|
|
||||||
) -> Vec<Entity<DebugSession>> {
|
|
||||||
self.sessions
|
|
||||||
.iter()
|
|
||||||
.filter(|item| item.read(cx).session_id(cx) == *client_id)
|
|
||||||
.map(|item| item.clone())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn debug_panel_item_by_client(
|
|
||||||
&self,
|
|
||||||
client_id: SessionId,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Option<Entity<DebugSession>> {
|
|
||||||
self.sessions
|
|
||||||
.iter()
|
|
||||||
.find(|item| {
|
|
||||||
let item = item.read(cx);
|
|
||||||
|
|
||||||
item.session_id(cx) == client_id
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_dap_store_event(
|
fn handle_dap_store_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
dap_store: &Entity<DapStore>,
|
_dap_store: &Entity<DapStore>,
|
||||||
event: &dap_store::DapStoreEvent,
|
event: &dap_store::DapStoreEvent,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
dap_store::DapStoreEvent::DebugSessionInitialized(session_id) => {
|
|
||||||
let Some(session) = dap_store.read(cx).session_by_id(session_id) else {
|
|
||||||
return log::error!(
|
|
||||||
"Couldn't get session with id: {session_id:?} from DebugClientStarted event"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let adapter_name = session.read(cx).adapter_name();
|
|
||||||
|
|
||||||
let session_id = *session_id;
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
|
||||||
let serialized_layout =
|
|
||||||
persistence::get_serialized_pane_layout(adapter_name).await;
|
|
||||||
|
|
||||||
this.update_in(cx, |this, window, cx| {
|
|
||||||
let Some(project) = this.project.upgrade() else {
|
|
||||||
return log::error!(
|
|
||||||
"Debug Panel out lived it's weak reference to Project"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if this
|
|
||||||
.sessions
|
|
||||||
.iter()
|
|
||||||
.any(|item| item.read(cx).session_id(cx) == session_id)
|
|
||||||
{
|
|
||||||
// We already have an item for this session.
|
|
||||||
debug_panic!("We should never reuse session ids");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sessions.retain(|session| {
|
|
||||||
session
|
|
||||||
.read(cx)
|
|
||||||
.mode()
|
|
||||||
.as_running()
|
|
||||||
.map_or(false, |running_state| {
|
|
||||||
!running_state.read(cx).session().read(cx).is_terminated()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let session_item = DebugSession::running(
|
|
||||||
project,
|
|
||||||
this.workspace.clone(),
|
|
||||||
session,
|
|
||||||
cx.weak_entity(),
|
|
||||||
serialized_layout,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(running) = session_item.read(cx).mode().as_running().cloned() {
|
|
||||||
// We might want to make this an event subscription and only notify when a new thread is selected
|
|
||||||
// This is used to filter the command menu correctly
|
|
||||||
cx.observe(&running, |_, _, cx| cx.notify()).detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sessions.push(session_item.clone());
|
|
||||||
this.activate_session(session_item, window, cx);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
dap_store::DapStoreEvent::RunInTerminal {
|
dap_store::DapStoreEvent::RunInTerminal {
|
||||||
title,
|
title,
|
||||||
cwd,
|
cwd,
|
||||||
|
@ -374,6 +487,12 @@ impl DebugPanel {
|
||||||
)
|
)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
dap_store::DapStoreEvent::SpawnChildSession {
|
||||||
|
request,
|
||||||
|
parent_session,
|
||||||
|
} => {
|
||||||
|
self.start_child_session(request, parent_session.clone(), window, cx);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -408,7 +527,7 @@ impl DebugPanel {
|
||||||
cwd,
|
cwd,
|
||||||
title,
|
title,
|
||||||
},
|
},
|
||||||
task::RevealStrategy::Always,
|
task::RevealStrategy::Never,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -468,8 +587,6 @@ impl DebugPanel {
|
||||||
let session = this.dap_store().read(cx).session_by_id(session_id);
|
let session = this.dap_store().read(cx).session_by_id(session_id);
|
||||||
session.map(|session| !session.read(cx).is_terminated())
|
session.map(|session| !session.read(cx).is_terminated())
|
||||||
})
|
})
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
@ -893,7 +1010,6 @@ impl DebugPanel {
|
||||||
|
|
||||||
impl EventEmitter<PanelEvent> for DebugPanel {}
|
impl EventEmitter<PanelEvent> for DebugPanel {}
|
||||||
impl EventEmitter<DebugPanelEvent> for DebugPanel {}
|
impl EventEmitter<DebugPanelEvent> for DebugPanel {}
|
||||||
impl EventEmitter<project::Event> for DebugPanel {}
|
|
||||||
|
|
||||||
impl Focusable for DebugPanel {
|
impl Focusable for DebugPanel {
|
||||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||||
|
@ -1039,3 +1155,15 @@ impl Render for DebugPanel {
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DebuggerProvider(Entity<DebugPanel>);
|
||||||
|
|
||||||
|
impl workspace::DebuggerProvider for DebuggerProvider {
|
||||||
|
fn start_session(&self, definition: DebugTaskDefinition, window: &mut Window, cx: &mut App) {
|
||||||
|
self.0.update(cx, |_, cx| {
|
||||||
|
cx.defer_in(window, |this, window, cx| {
|
||||||
|
this.start_session(definition, window, cx);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ mod new_session_modal;
|
||||||
mod persistence;
|
mod persistence;
|
||||||
pub(crate) mod session;
|
pub(crate) mod session;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
|
|
|
@ -4,14 +4,12 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Result, anyhow};
|
|
||||||
use dap::{DapRegistry, DebugRequest};
|
use dap::{DapRegistry, DebugRequest};
|
||||||
use editor::{Editor, EditorElement, EditorStyle};
|
use editor::{Editor, EditorElement, EditorStyle};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, TextStyle,
|
App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, TextStyle,
|
||||||
WeakEntity,
|
WeakEntity,
|
||||||
};
|
};
|
||||||
use project::Project;
|
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use task::{DebugTaskDefinition, DebugTaskTemplate, LaunchRequest};
|
use task::{DebugTaskDefinition, DebugTaskTemplate, LaunchRequest};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
|
@ -21,7 +19,6 @@ use ui::{
|
||||||
LabelCommon as _, ParentElement, RenderOnce, SharedString, Styled, StyledExt, ToggleButton,
|
LabelCommon as _, ParentElement, RenderOnce, SharedString, Styled, StyledExt, ToggleButton,
|
||||||
ToggleState, Toggleable, Window, div, h_flex, relative, rems, v_flex,
|
ToggleState, Toggleable, Window, div, h_flex, relative, rems, v_flex,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
|
||||||
use workspace::{ModalView, Workspace};
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
|
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
|
||||||
|
@ -88,11 +85,11 @@ impl NewSessionModal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_config(&self, cx: &App) -> Option<DebugTaskDefinition> {
|
fn debug_config(&self, cx: &App, debugger: &str) -> DebugTaskDefinition {
|
||||||
let request = self.mode.debug_task(cx);
|
let request = self.mode.debug_task(cx);
|
||||||
Some(DebugTaskDefinition {
|
DebugTaskDefinition {
|
||||||
adapter: self.debugger.clone()?.to_string(),
|
adapter: debugger.to_owned(),
|
||||||
label: suggested_label(&request, self.debugger.as_deref()?),
|
label: suggested_label(&request, debugger),
|
||||||
request,
|
request,
|
||||||
initialize_args: self.initialize_args.clone(),
|
initialize_args: self.initialize_args.clone(),
|
||||||
tcp_connection: None,
|
tcp_connection: None,
|
||||||
|
@ -100,26 +97,26 @@ impl NewSessionModal {
|
||||||
ToggleState::Selected => Some(true),
|
ToggleState::Selected => Some(true),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_new_session(&self, window: &mut Window, cx: &mut Context<Self>) -> Result<()> {
|
fn start_new_session(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let workspace = self.workspace.clone();
|
let Some(debugger) = self.debugger.as_ref() else {
|
||||||
let config = self
|
// todo: show in UI.
|
||||||
.debug_config(cx)
|
log::error!("No debugger selected");
|
||||||
.ok_or_else(|| anyhow!("Failed to create a debug config"))?;
|
return;
|
||||||
|
};
|
||||||
|
let config = self.debug_config(cx, debugger);
|
||||||
|
let debug_panel = self.debug_panel.clone();
|
||||||
|
|
||||||
let _ = self.debug_panel.update(cx, |panel, _| {
|
let task_contexts = self
|
||||||
panel.past_debug_definition = Some(config.clone());
|
.workspace
|
||||||
});
|
|
||||||
|
|
||||||
let task_contexts = workspace
|
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
tasks_ui::task_contexts(workspace, window, cx)
|
tasks_ui::task_contexts(workspace, window, cx)
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let task_context = if let Some(task) = task_contexts {
|
let task_context = if let Some(task) = task_contexts {
|
||||||
task.await
|
task.await
|
||||||
.active_worktree_context
|
.active_worktree_context
|
||||||
|
@ -127,9 +124,8 @@ impl NewSessionModal {
|
||||||
} else {
|
} else {
|
||||||
task::TaskContext::default()
|
task::TaskContext::default()
|
||||||
};
|
};
|
||||||
let project = workspace.update(cx, |workspace, _| workspace.project().clone())?;
|
|
||||||
|
|
||||||
let task = project.update(cx, |this, cx| {
|
debug_panel.update_in(cx, |debug_panel, window, cx| {
|
||||||
let template = DebugTaskTemplate {
|
let template = DebugTaskTemplate {
|
||||||
locator: None,
|
locator: None,
|
||||||
definition: config.clone(),
|
definition: config.clone(),
|
||||||
|
@ -139,23 +135,18 @@ impl NewSessionModal {
|
||||||
.resolve_task("debug_task", &task_context)
|
.resolve_task("debug_task", &task_context)
|
||||||
.and_then(|resolved_task| resolved_task.resolved_debug_adapter_config())
|
.and_then(|resolved_task| resolved_task.resolved_debug_adapter_config())
|
||||||
{
|
{
|
||||||
this.start_debug_session(debug_config.definition, cx)
|
debug_panel.start_session(debug_config.definition, window, cx)
|
||||||
} else {
|
} else {
|
||||||
this.start_debug_session(config, cx)
|
debug_panel.start_session(config, window, cx)
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
let spawn_result = task.await;
|
this.update(cx, |_, cx| {
|
||||||
if spawn_result.is_ok() {
|
cx.emit(DismissEvent);
|
||||||
this.update(cx, |_, cx| {
|
})
|
||||||
cx.emit(DismissEvent);
|
.ok();
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
spawn_result?;
|
|
||||||
anyhow::Result::<_, anyhow::Error>::Ok(())
|
anyhow::Result::<_, anyhow::Error>::Ok(())
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_attach_picker(
|
fn update_attach_picker(
|
||||||
|
@ -249,15 +240,12 @@ impl NewSessionModal {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DebugRequest::Attach(_) => {
|
DebugRequest::Attach(_) => {
|
||||||
let Ok(project) = this
|
let Some(workspace) = this.workspace.upgrade() else {
|
||||||
.workspace
|
|
||||||
.read_with(cx, |this, _| this.project().clone())
|
|
||||||
else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
this.mode = NewSessionMode::attach(
|
this.mode = NewSessionMode::attach(
|
||||||
this.debugger.clone(),
|
this.debugger.clone(),
|
||||||
project,
|
workspace,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -357,7 +345,7 @@ struct AttachMode {
|
||||||
impl AttachMode {
|
impl AttachMode {
|
||||||
fn new(
|
fn new(
|
||||||
debugger: Option<SharedString>,
|
debugger: Option<SharedString>,
|
||||||
project: Entity<Project>,
|
workspace: Entity<Workspace>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<NewSessionModal>,
|
cx: &mut Context<NewSessionModal>,
|
||||||
) -> Entity<Self> {
|
) -> Entity<Self> {
|
||||||
|
@ -370,7 +358,7 @@ impl AttachMode {
|
||||||
stop_on_entry: Some(false),
|
stop_on_entry: Some(false),
|
||||||
};
|
};
|
||||||
let attach_picker = cx.new(|cx| {
|
let attach_picker = cx.new(|cx| {
|
||||||
let modal = AttachModal::new(project, debug_definition.clone(), false, window, cx);
|
let modal = AttachModal::new(workspace, debug_definition.clone(), false, window, cx);
|
||||||
window.focus(&modal.focus_handle(cx));
|
window.focus(&modal.focus_handle(cx));
|
||||||
|
|
||||||
modal
|
modal
|
||||||
|
@ -470,11 +458,11 @@ impl RenderOnce for NewSessionMode {
|
||||||
impl NewSessionMode {
|
impl NewSessionMode {
|
||||||
fn attach(
|
fn attach(
|
||||||
debugger: Option<SharedString>,
|
debugger: Option<SharedString>,
|
||||||
project: Entity<Project>,
|
workspace: Entity<Workspace>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<NewSessionModal>,
|
cx: &mut Context<NewSessionModal>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::Attach(AttachMode::new(debugger, project, window, cx))
|
Self::Attach(AttachMode::new(debugger, workspace, window, cx))
|
||||||
}
|
}
|
||||||
fn launch(
|
fn launch(
|
||||||
past_launch_config: Option<LaunchRequest>,
|
past_launch_config: Option<LaunchRequest>,
|
||||||
|
@ -569,15 +557,12 @@ impl Render for NewSessionModal {
|
||||||
.toggle_state(matches!(self.mode, NewSessionMode::Attach(_)))
|
.toggle_state(matches!(self.mode, NewSessionMode::Attach(_)))
|
||||||
.style(ui::ButtonStyle::Subtle)
|
.style(ui::ButtonStyle::Subtle)
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
let Ok(project) = this
|
let Some(workspace) = this.workspace.upgrade() else {
|
||||||
.workspace
|
|
||||||
.read_with(cx, |this, _| this.project().clone())
|
|
||||||
else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
this.mode = NewSessionMode::attach(
|
this.mode = NewSessionMode::attach(
|
||||||
this.debugger.clone(),
|
this.debugger.clone(),
|
||||||
project,
|
workspace,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -631,7 +616,7 @@ impl Render for NewSessionModal {
|
||||||
.child(
|
.child(
|
||||||
Button::new("debugger-spawn", "Start")
|
Button::new("debugger-spawn", "Start")
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
this.start_new_session(window, cx).log_err();
|
this.start_new_session(window, cx);
|
||||||
}))
|
}))
|
||||||
.disabled(self.debugger.is_none()),
|
.disabled(self.debugger.is_none()),
|
||||||
),
|
),
|
||||||
|
|
|
@ -88,6 +88,12 @@ impl DebugSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session(&self, cx: &App) -> Entity<Session> {
|
||||||
|
match &self.mode {
|
||||||
|
DebugSessionState::Running(entity) => entity.read(cx).session().clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
|
pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
DebugSessionState::Running(state) => state.update(cx, |state, cx| state.shutdown(cx)),
|
DebugSessionState::Running(state) => state.update(cx, |state, cx| state.shutdown(cx)),
|
||||||
|
@ -115,13 +121,7 @@ impl DebugSession {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.label
|
self.label
|
||||||
.get_or_init(|| {
|
.get_or_init(|| session.read(cx).label())
|
||||||
session
|
|
||||||
.read(cx)
|
|
||||||
.as_local()
|
|
||||||
.expect("Remote Debug Sessions are not implemented yet")
|
|
||||||
.label()
|
|
||||||
})
|
|
||||||
.to_owned()
|
.to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -418,6 +418,19 @@ impl RunningState {
|
||||||
let threads = this.session.update(cx, |this, cx| this.threads(cx));
|
let threads = this.session.update(cx, |this, cx| this.threads(cx));
|
||||||
this.select_current_thread(&threads, cx);
|
this.select_current_thread(&threads, cx);
|
||||||
}
|
}
|
||||||
|
SessionEvent::CapabilitiesLoaded => {
|
||||||
|
let capabilities = this.capabilities(cx);
|
||||||
|
if !capabilities.supports_modules_request.unwrap_or(false) {
|
||||||
|
this.remove_pane_item(DebuggerPaneItem::Modules, window, cx);
|
||||||
|
}
|
||||||
|
if !capabilities
|
||||||
|
.supports_loaded_sources_request
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
this.remove_pane_item(DebuggerPaneItem::LoadedSources, window, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
cx.notify()
|
cx.notify()
|
||||||
|
@ -447,35 +460,14 @@ impl RunningState {
|
||||||
workspace::PaneGroup::with_root(root)
|
workspace::PaneGroup::with_root(root)
|
||||||
} else {
|
} else {
|
||||||
pane_close_subscriptions.clear();
|
pane_close_subscriptions.clear();
|
||||||
let module_list = if session
|
|
||||||
.read(cx)
|
|
||||||
.capabilities()
|
|
||||||
.supports_modules_request
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
Some(&module_list)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let loaded_source_list = if session
|
|
||||||
.read(cx)
|
|
||||||
.capabilities()
|
|
||||||
.supports_loaded_sources_request
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
Some(&loaded_source_list)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let root = Self::default_pane_layout(
|
let root = Self::default_pane_layout(
|
||||||
project,
|
project,
|
||||||
&workspace,
|
&workspace,
|
||||||
&stack_frame_list,
|
&stack_frame_list,
|
||||||
&variable_list,
|
&variable_list,
|
||||||
module_list,
|
&module_list,
|
||||||
loaded_source_list,
|
&loaded_source_list,
|
||||||
&console,
|
&console,
|
||||||
&breakpoint_list,
|
&breakpoint_list,
|
||||||
&mut pane_close_subscriptions,
|
&mut pane_close_subscriptions,
|
||||||
|
@ -512,11 +504,6 @@ impl RunningState {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
debug_assert!(
|
|
||||||
item_kind.is_supported(self.session.read(cx).capabilities()),
|
|
||||||
"We should only allow removing supported item kinds"
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
|
if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
|
||||||
Some(pane).zip(
|
Some(pane).zip(
|
||||||
pane.read(cx)
|
pane.read(cx)
|
||||||
|
@ -946,8 +933,8 @@ impl RunningState {
|
||||||
workspace: &WeakEntity<Workspace>,
|
workspace: &WeakEntity<Workspace>,
|
||||||
stack_frame_list: &Entity<StackFrameList>,
|
stack_frame_list: &Entity<StackFrameList>,
|
||||||
variable_list: &Entity<VariableList>,
|
variable_list: &Entity<VariableList>,
|
||||||
module_list: Option<&Entity<ModuleList>>,
|
module_list: &Entity<ModuleList>,
|
||||||
loaded_source_list: Option<&Entity<LoadedSourceList>>,
|
loaded_source_list: &Entity<LoadedSourceList>,
|
||||||
console: &Entity<Console>,
|
console: &Entity<Console>,
|
||||||
breakpoints: &Entity<BreakpointList>,
|
breakpoints: &Entity<BreakpointList>,
|
||||||
subscriptions: &mut HashMap<EntityId, Subscription>,
|
subscriptions: &mut HashMap<EntityId, Subscription>,
|
||||||
|
@ -1003,41 +990,36 @@ impl RunningState {
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
if let Some(module_list) = module_list {
|
this.add_item(
|
||||||
this.add_item(
|
Box::new(SubView::new(
|
||||||
Box::new(SubView::new(
|
module_list.focus_handle(cx),
|
||||||
module_list.focus_handle(cx),
|
module_list.clone().into(),
|
||||||
module_list.clone().into(),
|
DebuggerPaneItem::Modules,
|
||||||
DebuggerPaneItem::Modules,
|
|
||||||
None,
|
|
||||||
cx,
|
|
||||||
)),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
None,
|
None,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
)),
|
||||||
this.activate_item(0, false, false, window, cx);
|
false,
|
||||||
}
|
false,
|
||||||
|
None,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(loaded_source_list) = loaded_source_list {
|
this.add_item(
|
||||||
this.add_item(
|
Box::new(SubView::new(
|
||||||
Box::new(SubView::new(
|
loaded_source_list.focus_handle(cx),
|
||||||
loaded_source_list.focus_handle(cx),
|
loaded_source_list.clone().into(),
|
||||||
loaded_source_list.clone().into(),
|
DebuggerPaneItem::LoadedSources,
|
||||||
DebuggerPaneItem::LoadedSources,
|
|
||||||
None,
|
|
||||||
cx,
|
|
||||||
)),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
None,
|
None,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
)),
|
||||||
this.activate_item(1, false, false, window, cx);
|
false,
|
||||||
}
|
false,
|
||||||
|
None,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
this.activate_item(0, false, false, window, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||||
|
|
|
@ -1,16 +1,29 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
use dap::{DebugRequest, client::DebugAdapterClient};
|
||||||
use gpui::{Entity, TestAppContext, WindowHandle};
|
use gpui::{Entity, TestAppContext, WindowHandle};
|
||||||
use project::Project;
|
use project::{Project, debugger::session::Session};
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
|
use task::DebugTaskDefinition;
|
||||||
use terminal_view::terminal_panel::TerminalPanel;
|
use terminal_view::terminal_panel::TerminalPanel;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::{debugger_panel::DebugPanel, session::DebugSession};
|
use crate::{debugger_panel::DebugPanel, session::DebugSession};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
mod attach_modal;
|
mod attach_modal;
|
||||||
|
#[cfg(test)]
|
||||||
mod console;
|
mod console;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod dap_logger;
|
||||||
|
#[cfg(test)]
|
||||||
mod debugger_panel;
|
mod debugger_panel;
|
||||||
|
#[cfg(test)]
|
||||||
mod module_list;
|
mod module_list;
|
||||||
|
#[cfg(test)]
|
||||||
mod stack_frame_list;
|
mod stack_frame_list;
|
||||||
|
#[cfg(test)]
|
||||||
mod variable_list;
|
mod variable_list;
|
||||||
|
|
||||||
pub fn init_test(cx: &mut gpui::TestAppContext) {
|
pub fn init_test(cx: &mut gpui::TestAppContext) {
|
||||||
|
@ -42,7 +55,7 @@ pub async fn init_test_workspace(
|
||||||
let debugger_panel = workspace_handle
|
let debugger_panel = workspace_handle
|
||||||
.update(cx, |_, window, cx| {
|
.update(cx, |_, window, cx| {
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
DebugPanel::load(this, cx.clone()).await
|
DebugPanel::load(this, cx).await
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -82,3 +95,46 @@ pub fn active_debug_session_panel(
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_debug_session_with<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||||
|
workspace: &WindowHandle<Workspace>,
|
||||||
|
cx: &mut gpui::TestAppContext,
|
||||||
|
config: DebugTaskDefinition,
|
||||||
|
configure: T,
|
||||||
|
) -> Result<Entity<Session>> {
|
||||||
|
let _subscription = project::debugger::test::intercept_debug_sessions(cx, configure);
|
||||||
|
workspace.update(cx, |workspace, window, cx| {
|
||||||
|
workspace.start_debug_session(config, window, cx)
|
||||||
|
})?;
|
||||||
|
cx.run_until_parked();
|
||||||
|
let session = workspace.read_with(cx, |workspace, cx| {
|
||||||
|
workspace
|
||||||
|
.panel::<DebugPanel>(cx)
|
||||||
|
.and_then(|panel| panel.read(cx).active_session())
|
||||||
|
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||||
|
.map(|running| running.read(cx).session().clone())
|
||||||
|
.ok_or_else(|| anyhow!("Failed to get active session"))
|
||||||
|
})??;
|
||||||
|
|
||||||
|
Ok(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_debug_session<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||||
|
workspace: &WindowHandle<Workspace>,
|
||||||
|
cx: &mut gpui::TestAppContext,
|
||||||
|
configure: T,
|
||||||
|
) -> Result<Entity<Session>> {
|
||||||
|
start_debug_session_with(
|
||||||
|
workspace,
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{attach_modal::Candidate, *};
|
use crate::{attach_modal::Candidate, tests::start_debug_session_with, *};
|
||||||
use attach_modal::AttachModal;
|
use attach_modal::AttachModal;
|
||||||
use dap::{FakeAdapter, client::SessionId};
|
use dap::{FakeAdapter, client::SessionId};
|
||||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||||
|
@ -26,8 +26,8 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session_with(
|
let session = start_debug_session_with(
|
||||||
&project,
|
&workspace,
|
||||||
cx,
|
cx,
|
||||||
DebugTaskDefinition {
|
DebugTaskDefinition {
|
||||||
adapter: "fake-adapter".to_string(),
|
adapter: "fake-adapter".to_string(),
|
||||||
|
@ -47,7 +47,6 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
@ -99,9 +98,10 @@ async fn test_show_attach_modal_and_select_process(
|
||||||
});
|
});
|
||||||
let attach_modal = workspace
|
let attach_modal = workspace
|
||||||
.update(cx, |workspace, window, cx| {
|
.update(cx, |workspace, window, cx| {
|
||||||
|
let workspace_handle = cx.entity();
|
||||||
workspace.toggle_modal(window, cx, |window, cx| {
|
workspace.toggle_modal(window, cx, |window, cx| {
|
||||||
AttachModal::with_processes(
|
AttachModal::with_processes(
|
||||||
project.clone(),
|
workspace_handle,
|
||||||
DebugTaskDefinition {
|
DebugTaskDefinition {
|
||||||
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
||||||
request: dap::DebugRequest::Attach(AttachRequest::default()),
|
request: dap::DebugRequest::Attach(AttachRequest::default()),
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::{tests::active_debug_session_panel, *};
|
use crate::{
|
||||||
|
tests::{active_debug_session_panel, start_debug_session},
|
||||||
|
*,
|
||||||
|
};
|
||||||
use dap::requests::StackTrace;
|
use dap::requests::StackTrace;
|
||||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||||
use project::{FakeFs, Project};
|
use project::{FakeFs, Project};
|
||||||
|
@ -28,9 +31,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<StackTrace, _>(move |_, _| {
|
client.on_request::<StackTrace, _>(move |_, _| {
|
||||||
|
|
118
crates/debugger_ui/src/tests/dap_logger.rs
Normal file
118
crates/debugger_ui/src/tests/dap_logger.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
use crate::tests::{init_test, init_test_workspace, start_debug_session};
|
||||||
|
use dap::requests::{StackTrace, Threads};
|
||||||
|
use debugger_tools::LogStore;
|
||||||
|
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||||
|
use project::Project;
|
||||||
|
use serde_json::json;
|
||||||
|
use std::cell::OnceCell;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_dap_logger_captures_all_session_rpc_messages(
|
||||||
|
executor: BackgroundExecutor,
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
let log_store_cell = std::rc::Rc::new(OnceCell::new());
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
let log_store_cell = log_store_cell.clone();
|
||||||
|
cx.observe_new::<LogStore>(move |_, _, cx| {
|
||||||
|
log_store_cell.set(cx.entity()).unwrap();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
debugger_tools::init(cx);
|
||||||
|
});
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let log_store = log_store_cell.get().unwrap().clone();
|
||||||
|
|
||||||
|
// Create a filesystem with a simple project
|
||||||
|
let fs = project::FakeFs::new(executor.clone());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/project",
|
||||||
|
json!({
|
||||||
|
"main.rs": "fn main() {\n println!(\"Hello, world!\");\n}"
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
log_store.read_with(cx, |log_store, _| log_store
|
||||||
|
.contained_session_ids()
|
||||||
|
.is_empty()),
|
||||||
|
"log_store shouldn't contain any session IDs before any sessions were created"
|
||||||
|
);
|
||||||
|
|
||||||
|
let project = Project::test(fs, ["/project".as_ref()], cx).await;
|
||||||
|
|
||||||
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
|
// Start a debug session
|
||||||
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
|
let session_id = session.read_with(cx, |session, _| session.session_id());
|
||||||
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
log_store.read_with(cx, |log_store, _| log_store.contained_session_ids().len()),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
log_store.read_with(cx, |log_store, _| log_store
|
||||||
|
.contained_session_ids()
|
||||||
|
.contains(&session_id)),
|
||||||
|
"log_store should contain the session IDs of the started session"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!log_store.read_with(cx, |log_store, _| log_store
|
||||||
|
.rpc_messages_for_session_id(session_id)
|
||||||
|
.is_empty()),
|
||||||
|
"We should have the initialization sequence in the log store"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up basic responses for common requests
|
||||||
|
client.on_request::<Threads, _>(move |_, _| {
|
||||||
|
Ok(dap::ThreadsResponse {
|
||||||
|
threads: vec![dap::Thread {
|
||||||
|
id: 1,
|
||||||
|
name: "Thread 1".into(),
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on_request::<StackTrace, _>(move |_, _| {
|
||||||
|
Ok(dap::StackTraceResponse {
|
||||||
|
stack_frames: Vec::default(),
|
||||||
|
total_frames: None,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run until all pending tasks are executed
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
// Simulate a stopped event to generate more DAP messages
|
||||||
|
client
|
||||||
|
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
|
||||||
|
reason: dap::StoppedEventReason::Pause,
|
||||||
|
description: None,
|
||||||
|
thread_id: Some(1),
|
||||||
|
preserve_focus_hint: None,
|
||||||
|
text: None,
|
||||||
|
all_threads_stopped: None,
|
||||||
|
hit_breakpoint_ids: None,
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
// Shutdown the debug session
|
||||||
|
let shutdown_session = project.update(cx, |project, cx| {
|
||||||
|
project.dap_store().update(cx, |dap_store, cx| {
|
||||||
|
dap_store.shutdown_session(session.read(cx).session_id(), cx)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
shutdown_session.await.unwrap();
|
||||||
|
cx.run_until_parked();
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::*;
|
use crate::{tests::start_debug_session, *};
|
||||||
use dap::{
|
use dap::{
|
||||||
ErrorResponse, Message, RunInTerminalRequestArguments, SourceBreakpoint,
|
ErrorResponse, Message, RunInTerminalRequestArguments, SourceBreakpoint,
|
||||||
StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
|
StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
|
||||||
|
@ -48,9 +48,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<Threads, _>(move |_, _| {
|
client.on_request::<Threads, _>(move |_, _| {
|
||||||
|
@ -187,9 +185,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<Threads, _>(move |_, _| {
|
client.on_request::<Threads, _>(move |_, _| {
|
||||||
|
@ -354,9 +350,7 @@ async fn test_handle_successful_run_in_terminal_reverse_request(
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client
|
client
|
||||||
|
@ -419,6 +413,86 @@ async fn test_handle_successful_run_in_terminal_reverse_request(
|
||||||
shutdown_session.await.unwrap();
|
shutdown_session.await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_handle_start_debugging_request(
|
||||||
|
executor: BackgroundExecutor,
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(executor.clone());
|
||||||
|
|
||||||
|
fs.insert_tree(
|
||||||
|
"/project",
|
||||||
|
json!({
|
||||||
|
"main.rs": "First line\nSecond line\nThird line\nFourth line",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs, ["/project".as_ref()], cx).await;
|
||||||
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
|
let fake_config = json!({"one": "two"});
|
||||||
|
let launched_with = Arc::new(parking_lot::Mutex::new(None));
|
||||||
|
|
||||||
|
let _subscription = project::debugger::test::intercept_debug_sessions(cx, {
|
||||||
|
let launched_with = launched_with.clone();
|
||||||
|
move |client| {
|
||||||
|
let launched_with = launched_with.clone();
|
||||||
|
client.on_request::<dap::requests::Launch, _>(move |_, args| {
|
||||||
|
launched_with.lock().replace(args.raw);
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
client.on_request::<dap::requests::Attach, _>(move |_, _| {
|
||||||
|
assert!(false, "should not get attach request");
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client
|
||||||
|
.fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
|
||||||
|
request: StartDebuggingRequestArgumentsRequest::Launch,
|
||||||
|
configuration: fake_config.clone(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, _window, cx| {
|
||||||
|
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||||
|
let active_session = debug_panel
|
||||||
|
.read(cx)
|
||||||
|
.active_session()
|
||||||
|
.unwrap()
|
||||||
|
.read(cx)
|
||||||
|
.session(cx);
|
||||||
|
let parent_session = active_session.read(cx).parent_session().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
active_session.read(cx).definition(),
|
||||||
|
parent_session.read(cx).definition()
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(&fake_config, launched_with.lock().as_ref().unwrap());
|
||||||
|
|
||||||
|
let shutdown_session = project.update(cx, |project, cx| {
|
||||||
|
project.dap_store().update(cx, |dap_store, cx| {
|
||||||
|
dap_store.shutdown_session(session.read(cx).session_id(), cx)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
shutdown_session.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// // covers that we always send a response back, if something when wrong,
|
// // covers that we always send a response back, if something when wrong,
|
||||||
// // while spawning the terminal
|
// // while spawning the terminal
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -444,9 +518,7 @@ async fn test_handle_error_run_in_terminal_reverse_request(
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client
|
client
|
||||||
|
@ -522,9 +594,7 @@ async fn test_handle_start_debugging_reverse_request(
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||||
|
@ -629,9 +699,7 @@ async fn test_shutdown_children_when_parent_session_shutdown(
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let parent_session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||||
|
@ -737,9 +805,7 @@ async fn test_shutdown_parent_session_if_all_children_are_shutdown(
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let parent_session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_response::<StartDebugging, _>(move |_| {}).await;
|
client.on_response::<StartDebugging, _>(move |_| {}).await;
|
||||||
|
@ -858,7 +924,7 @@ async fn test_debug_panel_item_thread_status_reset_on_failure(
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |client| {
|
let session = start_debug_session(&workspace, cx, |client| {
|
||||||
client.on_request::<dap::requests::Initialize, _>(move |_, _| {
|
client.on_request::<dap::requests::Initialize, _>(move |_, _| {
|
||||||
Ok(dap::Capabilities {
|
Ok(dap::Capabilities {
|
||||||
supports_step_back: Some(true),
|
supports_step_back: Some(true),
|
||||||
|
@ -866,7 +932,6 @@ async fn test_debug_panel_item_thread_status_reset_on_failure(
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.await
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
@ -1073,9 +1138,7 @@ async fn test_send_breakpoints_when_editor_has_been_saved(
|
||||||
.update(cx, |_, _, cx| worktree.read(cx).id())
|
.update(cx, |_, _, cx| worktree.read(cx).id())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
let buffer = project
|
let buffer = project
|
||||||
|
@ -1290,9 +1353,7 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
|
||||||
editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
|
editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
let called_set_breakpoints = Arc::new(AtomicBool::new(false));
|
let called_set_breakpoints = Arc::new(AtomicBool::new(false));
|
||||||
|
@ -1358,7 +1419,7 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let task = project::debugger::test::start_debug_session(&project, cx, |client| {
|
start_debug_session(&workspace, cx, |client| {
|
||||||
client.on_request::<dap::requests::Initialize, _>(|_, _| {
|
client.on_request::<dap::requests::Initialize, _>(|_, _| {
|
||||||
Err(ErrorResponse {
|
Err(ErrorResponse {
|
||||||
error: Some(Message {
|
error: Some(Message {
|
||||||
|
@ -1372,12 +1433,8 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
|
.ok();
|
||||||
assert!(
|
|
||||||
task.await.is_err(),
|
|
||||||
"Session should failed to start if launch request fails"
|
|
||||||
);
|
|
||||||
|
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
debugger_panel::DebugPanel,
|
debugger_panel::DebugPanel,
|
||||||
tests::{active_debug_session_panel, init_test, init_test_workspace},
|
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
|
||||||
};
|
};
|
||||||
use dap::{
|
use dap::{
|
||||||
StoppedEvent,
|
StoppedEvent,
|
||||||
requests::{Initialize, Modules},
|
requests::{Initialize, Modules},
|
||||||
};
|
};
|
||||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||||
use project::{
|
use project::{FakeFs, Project};
|
||||||
FakeFs, Project,
|
|
||||||
debugger::{self},
|
|
||||||
};
|
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
Arc,
|
Arc,
|
||||||
atomic::{AtomicBool, AtomicI32, Ordering},
|
atomic::{AtomicBool, AtomicI32, Ordering},
|
||||||
|
@ -31,7 +28,7 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |client| {
|
let session = start_debug_session(&workspace, cx, |client| {
|
||||||
client.on_request::<Initialize, _>(move |_, _| {
|
client.on_request::<Initialize, _>(move |_, _| {
|
||||||
Ok(dap::Capabilities {
|
Ok(dap::Capabilities {
|
||||||
supports_modules_request: Some(true),
|
supports_modules_request: Some(true),
|
||||||
|
@ -39,7 +36,6 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.await
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
debugger_panel::DebugPanel,
|
debugger_panel::DebugPanel,
|
||||||
session::running::stack_frame_list::StackFrameEntry,
|
session::running::stack_frame_list::StackFrameEntry,
|
||||||
tests::{active_debug_session_panel, init_test, init_test_workspace},
|
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
|
||||||
};
|
};
|
||||||
use dap::{
|
use dap::{
|
||||||
StackFrame,
|
StackFrame,
|
||||||
|
@ -9,7 +9,7 @@ use dap::{
|
||||||
};
|
};
|
||||||
use editor::{Editor, ToPoint as _};
|
use editor::{Editor, ToPoint as _};
|
||||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||||
use project::{FakeFs, Project, debugger};
|
use project::{FakeFs, Project};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use unindent::Unindent as _;
|
use unindent::Unindent as _;
|
||||||
|
@ -50,9 +50,7 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
|
||||||
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
|
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
client.on_request::<Scopes, _>(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] }));
|
client.on_request::<Scopes, _>(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] }));
|
||||||
|
|
||||||
|
@ -229,9 +227,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
|
||||||
});
|
});
|
||||||
|
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<Threads, _>(move |_, _| {
|
client.on_request::<Threads, _>(move |_, _| {
|
||||||
|
@ -495,9 +491,7 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<Threads, _>(move |_, _| {
|
client.on_request::<Threads, _>(move |_, _| {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::sync::{
|
||||||
use crate::{
|
use crate::{
|
||||||
DebugPanel,
|
DebugPanel,
|
||||||
session::running::variable_list::{CollapseSelectedEntry, ExpandSelectedEntry},
|
session::running::variable_list::{CollapseSelectedEntry, ExpandSelectedEntry},
|
||||||
tests::{active_debug_session_panel, init_test, init_test_workspace},
|
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use dap::{
|
use dap::{
|
||||||
|
@ -15,7 +15,7 @@ use dap::{
|
||||||
};
|
};
|
||||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||||
use menu::{SelectFirst, SelectNext, SelectPrevious};
|
use menu::{SelectFirst, SelectNext, SelectPrevious};
|
||||||
use project::{FakeFs, Project, debugger};
|
use project::{FakeFs, Project};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use unindent::Unindent as _;
|
use unindent::Unindent as _;
|
||||||
use util::path;
|
use util::path;
|
||||||
|
@ -54,9 +54,7 @@ async fn test_basic_fetch_initial_scope_and_variables(
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||||
|
@ -266,9 +264,7 @@ async fn test_fetch_variables_for_multiple_scopes(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||||
|
@ -528,9 +524,7 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||||
|
@ -1313,9 +1307,7 @@ async fn test_variable_list_only_sends_requests_when_rendering(
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||||
|
@ -1560,9 +1552,7 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||||
|
|
|
@ -4,33 +4,35 @@ use super::{
|
||||||
session::{self, Session, SessionStateEvent},
|
session::{self, Session, SessionStateEvent},
|
||||||
};
|
};
|
||||||
use crate::{
|
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 anyhow::{Result, anyhow};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use dap::{
|
use dap::{
|
||||||
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse,
|
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, EvaluateArguments,
|
||||||
EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
|
EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source,
|
||||||
Source, StartDebuggingRequestArguments,
|
StartDebuggingRequestArguments,
|
||||||
adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName},
|
adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName, TcpArguments},
|
||||||
client::SessionId,
|
client::SessionId,
|
||||||
messages::Message,
|
messages::Message,
|
||||||
requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},
|
requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},
|
||||||
};
|
};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::{mpsc, oneshot},
|
channel::mpsc,
|
||||||
future::{Shared, join_all},
|
future::{Shared, join_all},
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};
|
||||||
App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
|
|
||||||
};
|
|
||||||
use http_client::HttpClient;
|
use http_client::HttpClient;
|
||||||
use language::{BinaryStatus, LanguageRegistry, LanguageToolchainStore};
|
use language::{BinaryStatus, LanguageRegistry, LanguageToolchainStore};
|
||||||
use lsp::LanguageServerName;
|
use lsp::LanguageServerName;
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
|
|
||||||
|
use remote::SshRemoteClient;
|
||||||
use rpc::{
|
use rpc::{
|
||||||
AnyProtoClient, TypedEnvelope,
|
AnyProtoClient, TypedEnvelope,
|
||||||
proto::{self},
|
proto::{self},
|
||||||
|
@ -42,6 +44,7 @@ use std::{
|
||||||
borrow::Borrow,
|
borrow::Borrow,
|
||||||
collections::{BTreeMap, HashSet},
|
collections::{BTreeMap, HashSet},
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
|
net::Ipv4Addr,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
@ -66,6 +69,10 @@ pub enum DapStoreEvent {
|
||||||
envs: HashMap<String, String>,
|
envs: HashMap<String, String>,
|
||||||
sender: mpsc::Sender<Result<u32>>,
|
sender: mpsc::Sender<Result<u32>>,
|
||||||
},
|
},
|
||||||
|
SpawnChildSession {
|
||||||
|
request: StartDebuggingRequestArguments,
|
||||||
|
parent_session: Entity<Session>,
|
||||||
|
},
|
||||||
Notification(String),
|
Notification(String),
|
||||||
RemoteHasInitialized,
|
RemoteHasInitialized,
|
||||||
}
|
}
|
||||||
|
@ -83,12 +90,12 @@ pub struct LocalDapStore {
|
||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
environment: Entity<ProjectEnvironment>,
|
environment: Entity<ProjectEnvironment>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
worktree_store: Entity<WorktreeStore>,
|
|
||||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||||
locators: HashMap<String, Arc<dyn DapLocator>>,
|
locators: HashMap<String, Arc<dyn DapLocator>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SshDapStore {
|
pub struct SshDapStore {
|
||||||
|
ssh_client: Entity<SshRemoteClient>,
|
||||||
upstream_client: AnyProtoClient,
|
upstream_client: AnyProtoClient,
|
||||||
upstream_project_id: u64,
|
upstream_project_id: u64,
|
||||||
}
|
}
|
||||||
|
@ -97,6 +104,7 @@ pub struct DapStore {
|
||||||
mode: DapStoreMode,
|
mode: DapStoreMode,
|
||||||
downstream_client: Option<(AnyProtoClient, u64)>,
|
downstream_client: Option<(AnyProtoClient, u64)>,
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
|
worktree_store: Entity<WorktreeStore>,
|
||||||
sessions: BTreeMap<SessionId, Entity<Session>>,
|
sessions: BTreeMap<SessionId, Entity<Session>>,
|
||||||
next_session_id: u32,
|
next_session_id: u32,
|
||||||
start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||||
|
@ -136,40 +144,43 @@ impl DapStore {
|
||||||
http_client,
|
http_client,
|
||||||
node_runtime,
|
node_runtime,
|
||||||
toolchain_store,
|
toolchain_store,
|
||||||
worktree_store,
|
|
||||||
language_registry,
|
language_registry,
|
||||||
locators,
|
locators,
|
||||||
});
|
});
|
||||||
|
|
||||||
Self::new(mode, breakpoint_store, cx)
|
Self::new(mode, breakpoint_store, worktree_store, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_ssh(
|
pub fn new_ssh(
|
||||||
project_id: u64,
|
project_id: u64,
|
||||||
upstream_client: AnyProtoClient,
|
ssh_client: Entity<SshRemoteClient>,
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
|
worktree_store: Entity<WorktreeStore>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mode = DapStoreMode::Ssh(SshDapStore {
|
let mode = DapStoreMode::Ssh(SshDapStore {
|
||||||
upstream_client,
|
upstream_client: ssh_client.read(cx).proto_client(),
|
||||||
|
ssh_client,
|
||||||
upstream_project_id: project_id,
|
upstream_project_id: project_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
Self::new(mode, breakpoint_store, cx)
|
Self::new(mode, breakpoint_store, worktree_store, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_collab(
|
pub fn new_collab(
|
||||||
_project_id: u64,
|
_project_id: u64,
|
||||||
_upstream_client: AnyProtoClient,
|
_upstream_client: AnyProtoClient,
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
|
worktree_store: Entity<WorktreeStore>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new(DapStoreMode::Collab, breakpoint_store, cx)
|
Self::new(DapStoreMode::Collab, breakpoint_store, worktree_store, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(
|
fn new(
|
||||||
mode: DapStoreMode,
|
mode: DapStoreMode,
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
|
worktree_store: Entity<WorktreeStore>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (start_debugging_tx, mut message_rx) =
|
let (start_debugging_tx, mut message_rx) =
|
||||||
|
@ -202,6 +213,7 @@ impl DapStore {
|
||||||
next_session_id: 0,
|
next_session_id: 0,
|
||||||
downstream_client: None,
|
downstream_client: None,
|
||||||
breakpoint_store,
|
breakpoint_store,
|
||||||
|
worktree_store,
|
||||||
sessions: Default::default(),
|
sessions: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,8 +224,8 @@ impl DapStore {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<DebugAdapterBinary>> {
|
) -> Task<Result<DebugAdapterBinary>> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
DapStoreMode::Local(local) => {
|
DapStoreMode::Local(_) => {
|
||||||
let Some(worktree) = local.worktree_store.read(cx).visible_worktrees(cx).next()
|
let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next()
|
||||||
else {
|
else {
|
||||||
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
||||||
};
|
};
|
||||||
|
@ -261,10 +273,49 @@ impl DapStore {
|
||||||
project_id: ssh.upstream_project_id,
|
project_id: ssh.upstream_project_id,
|
||||||
task: Some(definition.to_proto()),
|
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?;
|
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 => {
|
DapStoreMode::Collab => {
|
||||||
|
@ -316,27 +367,79 @@ impl DapStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_remote_client(
|
pub fn new_session(
|
||||||
&mut self,
|
&mut self,
|
||||||
session_id: SessionId,
|
template: DebugTaskDefinition,
|
||||||
ignore: Option<bool>,
|
parent_session: Option<Entity<Session>>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) -> Entity<Session> {
|
||||||
if let DapStoreMode::Ssh(remote) = &self.mode {
|
let session_id = SessionId(util::post_inc(&mut self.next_session_id));
|
||||||
self.sessions.insert(
|
|
||||||
session_id,
|
if let Some(session) = &parent_session {
|
||||||
cx.new(|_| {
|
session.update(cx, |session, _| {
|
||||||
debugger::session::Session::remote(
|
session.add_child_session_id(session_id);
|
||||||
session_id,
|
});
|
||||||
remote.upstream_client.clone(),
|
|
||||||
remote.upstream_project_id,
|
|
||||||
ignore.unwrap_or(false),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
debug_assert!(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
pub fn session_by_id(
|
||||||
|
@ -367,6 +470,10 @@ impl DapStore {
|
||||||
&self.breakpoint_store
|
&self.breakpoint_store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn worktree_store(&self) -> &Entity<WorktreeStore> {
|
||||||
|
&self.worktree_store
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
async fn handle_ignore_breakpoint_state(
|
async fn handle_ignore_breakpoint_state(
|
||||||
this: Entity<Self>,
|
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(
|
fn handle_start_debugging_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
session_id: SessionId,
|
session_id: SessionId,
|
||||||
|
@ -462,56 +523,35 @@ impl DapStore {
|
||||||
let Some(parent_session) = self.session_by_id(session_id) else {
|
let Some(parent_session) = self.session_by_id(session_id) else {
|
||||||
return Task::ready(Err(anyhow!("Session not found")));
|
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;
|
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
|
parent_session
|
||||||
.update(cx, |session, cx| {
|
.update(cx, |session, cx| {
|
||||||
session.respond_to_client(
|
session.respond_to_client(
|
||||||
request_seq,
|
request_seq,
|
||||||
success,
|
success,
|
||||||
StartDebugging::COMMAND.to_string(),
|
StartDebugging::COMMAND.to_string(),
|
||||||
body,
|
None,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
|
@ -752,7 +792,7 @@ impl DapStore {
|
||||||
|
|
||||||
let shutdown_parent_task = if let Some(parent_session) = session
|
let shutdown_parent_task = if let Some(parent_session) = session
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.parent_id()
|
.parent_id(cx)
|
||||||
.and_then(|session_id| self.session_by_id(session_id))
|
.and_then(|session_id| self.session_by_id(session_id))
|
||||||
{
|
{
|
||||||
let shutdown_id = parent_session.update(cx, |parent_session, _| {
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct DapAdapterDelegate {
|
pub struct DapAdapterDelegate {
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
|
|
|
@ -28,7 +28,6 @@ use gpui::{
|
||||||
Task, WeakEntity,
|
Task, WeakEntity,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rpc::AnyProtoClient;
|
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use std::any::TypeId;
|
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 {
|
enum Mode {
|
||||||
Local(LocalMode),
|
Building,
|
||||||
Remote(RemoteConnection),
|
Running(LocalMode),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LocalMode {
|
pub struct LocalMode {
|
||||||
client: Arc<DebugAdapterClient>,
|
client: Arc<DebugAdapterClient>,
|
||||||
definition: DebugTaskDefinition,
|
|
||||||
binary: DebugAdapterBinary,
|
binary: DebugAdapterBinary,
|
||||||
root_binary: Option<Arc<DebugAdapterBinary>>,
|
root_binary: Option<Arc<DebugAdapterBinary>>,
|
||||||
pub(crate) breakpoint_store: Entity<BreakpointStore>,
|
pub(crate) breakpoint_store: Entity<BreakpointStore>,
|
||||||
|
@ -186,56 +145,47 @@ fn client_source(abs_path: &Path) -> dap::Source {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalMode {
|
impl LocalMode {
|
||||||
fn new(
|
async fn new(
|
||||||
session_id: SessionId,
|
session_id: SessionId,
|
||||||
parent_session: Option<Entity<Session>>,
|
parent_session: Option<Entity<Session>>,
|
||||||
worktree: WeakEntity<Worktree>,
|
worktree: WeakEntity<Worktree>,
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
config: DebugTaskDefinition,
|
|
||||||
binary: DebugAdapterBinary,
|
binary: DebugAdapterBinary,
|
||||||
messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
|
messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
|
||||||
cx: AsyncApp,
|
cx: AsyncApp,
|
||||||
) -> Task<Result<Self>> {
|
) -> Result<Self> {
|
||||||
cx.spawn(async move |cx| {
|
let message_handler = Box::new(move |message| {
|
||||||
let message_handler = Box::new(move |message| {
|
messages_tx.unbounded_send(message).ok();
|
||||||
messages_tx.unbounded_send(message).ok();
|
});
|
||||||
});
|
|
||||||
|
|
||||||
let root_binary = if let Some(parent_session) = parent_session.as_ref() {
|
let root_binary = if let Some(parent_session) = parent_session.as_ref() {
|
||||||
Some(parent_session.read_with(cx, |session, _| session.root_binary().clone())?)
|
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 {
|
} else {
|
||||||
None
|
DebugAdapterClient::start(session_id, binary.clone(), message_handler, cx.clone())
|
||||||
};
|
|
||||||
|
|
||||||
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(),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.with_context(|| "Failed to start communication with debug adapter")?
|
.with_context(|| "Failed to start communication with debug adapter")?
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client,
|
client,
|
||||||
breakpoint_store,
|
breakpoint_store,
|
||||||
worktree,
|
worktree,
|
||||||
tmp_breakpoint: None,
|
tmp_breakpoint: None,
|
||||||
definition: config,
|
root_binary,
|
||||||
root_binary,
|
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(
|
fn initialize_sequence(
|
||||||
&self,
|
&self,
|
||||||
capabilities: &Capabilities,
|
capabilities: &Capabilities,
|
||||||
|
definition: &DebugTaskDefinition,
|
||||||
initialized_rx: oneshot::Receiver<()>,
|
initialized_rx: oneshot::Receiver<()>,
|
||||||
dap_store: WeakEntity<DapStore>,
|
dap_store: WeakEntity<DapStore>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
|
@ -391,7 +332,7 @@ impl LocalMode {
|
||||||
let mut raw = self.binary.request_args.clone();
|
let mut raw = self.binary.request_args.clone();
|
||||||
|
|
||||||
merge_json_value_into(
|
merge_json_value_into(
|
||||||
self.definition.initialize_args.clone().unwrap_or(json!({})),
|
definition.initialize_args.clone().unwrap_or(json!({})),
|
||||||
&mut raw.configuration,
|
&mut raw.configuration,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -426,9 +367,9 @@ impl LocalMode {
|
||||||
let supports_exception_filters = capabilities
|
let supports_exception_filters = capabilities
|
||||||
.supports_exception_filter_options
|
.supports_exception_filter_options
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let this = self.clone();
|
||||||
|
let worktree = self.worktree().clone();
|
||||||
let configuration_sequence = cx.spawn({
|
let configuration_sequence = cx.spawn({
|
||||||
let this = self.clone();
|
|
||||||
let worktree = self.worktree().clone();
|
|
||||||
async move |cx| {
|
async move |cx| {
|
||||||
initialized_rx.await?;
|
initialized_rx.await?;
|
||||||
let errors_by_path = cx
|
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 {
|
impl Mode {
|
||||||
fn request_dap<R: DapCommand>(
|
fn request_dap<R: DapCommand>(
|
||||||
&self,
|
&self,
|
||||||
session_id: SessionId,
|
|
||||||
request: R,
|
request: R,
|
||||||
cx: &mut Context<Session>,
|
cx: &mut Context<Session>,
|
||||||
) -> Task<Result<R::Response>>
|
) -> Task<Result<R::Response>>
|
||||||
|
@ -529,10 +464,13 @@ impl Mode {
|
||||||
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
|
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Mode::Local(debug_adapter_client) => {
|
Mode::Running(debug_adapter_client) => {
|
||||||
debug_adapter_client.request(request, cx.background_executor().clone())
|
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.
|
/// Represents a current state of a single debug adapter and provides ways to mutate it.
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
definition: DebugTaskDefinition,
|
||||||
pub(super) capabilities: Capabilities,
|
pub(super) capabilities: Capabilities,
|
||||||
id: SessionId,
|
id: SessionId,
|
||||||
child_session_ids: HashSet<SessionId>,
|
child_session_ids: HashSet<SessionId>,
|
||||||
parent_id: Option<SessionId>,
|
parent_session: Option<Entity<Session>>,
|
||||||
ignore_breakpoints: bool,
|
ignore_breakpoints: bool,
|
||||||
modules: Vec<dap::Module>,
|
modules: Vec<dap::Module>,
|
||||||
loaded_sources: Vec<dap::Source>,
|
loaded_sources: Vec<dap::Source>,
|
||||||
|
@ -626,7 +565,8 @@ pub struct Session {
|
||||||
is_session_terminated: bool,
|
is_session_terminated: bool,
|
||||||
requests: HashMap<TypeId, HashMap<RequestSlot, Shared<Task<Option<()>>>>>,
|
requests: HashMap<TypeId, HashMap<RequestSlot, Shared<Task<Option<()>>>>>,
|
||||||
exception_breakpoints: BTreeMap<String, (ExceptionBreakpointsFilter, IsEnabled)>,
|
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 {
|
trait CacheableCommand: Any + Send + Sync {
|
||||||
|
@ -708,9 +648,12 @@ pub enum SessionEvent {
|
||||||
StackTrace,
|
StackTrace,
|
||||||
Variables,
|
Variables,
|
||||||
Threads,
|
Threads,
|
||||||
|
CapabilitiesLoaded,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) enum SessionStateEvent {
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum SessionStateEvent {
|
||||||
|
Running,
|
||||||
Shutdown,
|
Shutdown,
|
||||||
Restart,
|
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
|
// remote side will only send breakpoint updates when it is a breakpoint created by that peer
|
||||||
// BreakpointStore notifies session on breakpoint changes
|
// BreakpointStore notifies session on breakpoint changes
|
||||||
impl Session {
|
impl Session {
|
||||||
pub(crate) fn local(
|
pub(crate) fn new(
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
worktree: WeakEntity<Worktree>,
|
|
||||||
session_id: SessionId,
|
session_id: SessionId,
|
||||||
parent_session: Option<Entity<Session>>,
|
parent_session: Option<Entity<Session>>,
|
||||||
binary: DebugAdapterBinary,
|
template: DebugTaskDefinition,
|
||||||
config: DebugTaskDefinition,
|
|
||||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||||
initialized_tx: oneshot::Sender<()>,
|
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<Entity<Self>>> {
|
) -> Entity<Self> {
|
||||||
let (message_tx, message_rx) = futures::channel::mpsc::unbounded();
|
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(
|
let mode = LocalMode::new(
|
||||||
session_id,
|
id,
|
||||||
parent_session.clone(),
|
parent_session,
|
||||||
worktree,
|
worktree.downgrade(),
|
||||||
breakpoint_store.clone(),
|
breakpoint_store.clone(),
|
||||||
config.clone(),
|
|
||||||
binary,
|
binary,
|
||||||
message_tx,
|
message_tx,
|
||||||
cx.clone(),
|
cx.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.mode = Mode::Running(mode);
|
||||||
|
cx.emit(SessionStateEvent::Running);
|
||||||
|
})?;
|
||||||
|
|
||||||
cx.new(|cx| {
|
this.update(cx, |session, cx| session.request_initialize(cx))?
|
||||||
create_local_session(
|
.await?;
|
||||||
breakpoint_store,
|
|
||||||
session_id,
|
this.update(cx, |session, cx| {
|
||||||
parent_session,
|
session.initialize_sequence(initialized_rx, dap_store.clone(), cx)
|
||||||
start_debugging_requests_tx,
|
})?
|
||||||
initialized_tx,
|
.await
|
||||||
message_rx,
|
|
||||||
mode,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
pub fn session_id(&self) -> SessionId {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
@ -812,8 +815,14 @@ impl Session {
|
||||||
self.child_session_ids.remove(&session_id);
|
self.child_session_ids.remove(&session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parent_id(&self) -> Option<SessionId> {
|
pub fn parent_id(&self, cx: &App) -> Option<SessionId> {
|
||||||
self.parent_id
|
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 {
|
pub fn capabilities(&self) -> &Capabilities {
|
||||||
|
@ -821,35 +830,35 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn root_binary(&self) -> Arc<DebugAdapterBinary> {
|
pub(crate) fn root_binary(&self) -> Arc<DebugAdapterBinary> {
|
||||||
let Mode::Local(local_mode) = &self.mode else {
|
match &self.mode {
|
||||||
panic!("Session is not local");
|
Mode::Building => {
|
||||||
};
|
// todo(debugger): Implement root_binary for building mode
|
||||||
local_mode
|
unimplemented!()
|
||||||
.root_binary
|
}
|
||||||
.clone()
|
Mode::Running(running) => running
|
||||||
.unwrap_or_else(|| Arc::new(local_mode.binary.clone()))
|
.root_binary
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| Arc::new(running.binary.clone())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn binary(&self) -> &DebugAdapterBinary {
|
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");
|
panic!("Session is not local");
|
||||||
};
|
};
|
||||||
&local_mode.binary
|
&local_mode.binary
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn adapter_name(&self) -> SharedString {
|
pub fn adapter_name(&self) -> SharedString {
|
||||||
match &self.mode {
|
self.definition.adapter.clone().into()
|
||||||
Mode::Local(local_mode) => local_mode.definition.adapter.clone().into(),
|
|
||||||
Mode::Remote(remote_mode) => remote_mode._adapter_name.clone(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configuration(&self) -> Option<DebugTaskDefinition> {
|
pub fn label(&self) -> String {
|
||||||
if let Mode::Local(local_mode) = &self.mode {
|
self.definition.label.clone()
|
||||||
Some(local_mode.definition.clone())
|
}
|
||||||
} else {
|
|
||||||
None
|
pub fn definition(&self) -> DebugTaskDefinition {
|
||||||
}
|
self.definition.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_terminated(&self) -> bool {
|
pub fn is_terminated(&self) -> bool {
|
||||||
|
@ -857,31 +866,33 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_local(&self) -> bool {
|
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> {
|
pub fn as_local_mut(&mut self) -> Option<&mut LocalMode> {
|
||||||
match &mut self.mode {
|
match &mut self.mode {
|
||||||
Mode::Local(local_mode) => Some(local_mode),
|
Mode::Running(local_mode) => Some(local_mode),
|
||||||
Mode::Remote(_) => None,
|
Mode::Building => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_local(&self) -> Option<&LocalMode> {
|
pub fn as_local(&self) -> Option<&LocalMode> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Local(local_mode) => Some(local_mode),
|
Mode::Running(local_mode) => Some(local_mode),
|
||||||
Mode::Remote(_) => None,
|
Mode::Building => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn request_initialize(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
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 {
|
match &self.mode {
|
||||||
Mode::Local(local_mode) => {
|
Mode::Running(local_mode) => {
|
||||||
let capabilities = local_mode.clone().request_initialization(cx);
|
let capabilities = local_mode.request(request, cx.background_executor().clone());
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let capabilities = capabilities.await?;
|
let capabilities = capabilities.await?;
|
||||||
this.update(cx, |session, _| {
|
this.update(cx, |session, cx| {
|
||||||
session.capabilities = capabilities;
|
session.capabilities = capabilities;
|
||||||
let filters = session
|
let filters = session
|
||||||
.capabilities
|
.capabilities
|
||||||
|
@ -895,12 +906,13 @@ impl Session {
|
||||||
.entry(filter.filter.clone())
|
.entry(filter.filter.clone())
|
||||||
.or_insert_with(|| (filter, default));
|
.or_insert_with(|| (filter, default));
|
||||||
}
|
}
|
||||||
|
cx.emit(SessionEvent::CapabilitiesLoaded);
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Mode::Remote(_) => Task::ready(Err(anyhow!(
|
Mode::Building => Task::ready(Err(anyhow!(
|
||||||
"Cannot send initialize request from remote session"
|
"Cannot send initialize request, task still building"
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -912,10 +924,14 @@ impl Session {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Local(local_mode) => {
|
Mode::Running(local_mode) => local_mode.initialize_sequence(
|
||||||
local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx)
|
&self.capabilities,
|
||||||
}
|
&self.definition,
|
||||||
Mode::Remote(_) => Task::ready(Err(anyhow!("cannot initialize remote session"))),
|
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>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
match &mut self.mode {
|
match &mut self.mode {
|
||||||
Mode::Local(local_mode) => {
|
Mode::Running(local_mode) => {
|
||||||
if !matches!(
|
if !matches!(
|
||||||
self.thread_states.thread_state(active_thread_id),
|
self.thread_states.thread_state(active_thread_id),
|
||||||
Some(ThreadStatus::Stopped)
|
Some(ThreadStatus::Stopped)
|
||||||
|
@ -949,7 +965,7 @@ impl Session {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
Mode::Remote(_) => {}
|
Mode::Building => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -983,13 +999,13 @@ impl Session {
|
||||||
body: Option<serde_json::Value>,
|
body: Option<serde_json::Value>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> 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");
|
unreachable!("Cannot respond to remote client");
|
||||||
};
|
};
|
||||||
|
let client = local_session.client.clone();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
local_session
|
client
|
||||||
.client
|
|
||||||
.send_message(Message::Response(Response {
|
.send_message(Message::Response(Response {
|
||||||
body,
|
body,
|
||||||
success,
|
success,
|
||||||
|
@ -1178,7 +1194,6 @@ impl Session {
|
||||||
|
|
||||||
let task = Self::request_inner::<Arc<T>>(
|
let task = Self::request_inner::<Arc<T>>(
|
||||||
&self.capabilities,
|
&self.capabilities,
|
||||||
self.id,
|
|
||||||
&self.mode,
|
&self.mode,
|
||||||
command,
|
command,
|
||||||
process_result,
|
process_result,
|
||||||
|
@ -1199,7 +1214,6 @@ impl Session {
|
||||||
|
|
||||||
fn request_inner<T: DapCommand + PartialEq + Eq + Hash>(
|
fn request_inner<T: DapCommand + PartialEq + Eq + Hash>(
|
||||||
capabilities: &Capabilities,
|
capabilities: &Capabilities,
|
||||||
session_id: SessionId,
|
|
||||||
mode: &Mode,
|
mode: &Mode,
|
||||||
request: T,
|
request: T,
|
||||||
process_result: impl FnOnce(
|
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| {
|
cx.spawn(async move |this, cx| {
|
||||||
let result = request.await;
|
let result = request.await;
|
||||||
this.update(cx, |this, cx| process_result(this, result, cx))
|
this.update(cx, |this, cx| process_result(this, result, cx))
|
||||||
|
@ -1245,14 +1259,7 @@ impl Session {
|
||||||
+ 'static,
|
+ 'static,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Option<T::Response>> {
|
) -> Task<Option<T::Response>> {
|
||||||
Self::request_inner(
|
Self::request_inner(&self.capabilities, &self.mode, request, process_result, cx)
|
||||||
&self.capabilities,
|
|
||||||
self.id,
|
|
||||||
&self.mode,
|
|
||||||
request,
|
|
||||||
process_result,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invalidate_command_type<Command: DapCommand>(&mut self) {
|
fn invalidate_command_type<Command: DapCommand>(&mut self) {
|
||||||
|
@ -1569,8 +1576,8 @@ impl Session {
|
||||||
|
|
||||||
pub fn adapter_client(&self) -> Option<Arc<DebugAdapterClient>> {
|
pub fn adapter_client(&self) -> Option<Arc<DebugAdapterClient>> {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Local(ref local) => Some(local.client.clone()),
|
Mode::Running(ref local) => Some(local.client.clone()),
|
||||||
Mode::Remote(_) => None,
|
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 std::{path::Path, sync::Arc};
|
||||||
|
|
||||||
use anyhow::Result;
|
use dap::client::DebugAdapterClient;
|
||||||
use dap::{DebugRequest, client::DebugAdapterClient};
|
use gpui::{App, AppContext, Subscription};
|
||||||
use gpui::{App, AppContext, Entity, Subscription, Task};
|
|
||||||
use task::DebugTaskDefinition;
|
|
||||||
|
|
||||||
use crate::Project;
|
use super::session::{Session, SessionStateEvent};
|
||||||
|
|
||||||
use super::session::Session;
|
|
||||||
|
|
||||||
pub fn intercept_debug_sessions<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
pub fn intercept_debug_sessions<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||||
cx: &mut gpui::TestAppContext,
|
cx: &mut gpui::TestAppContext,
|
||||||
configure: T,
|
configure: T,
|
||||||
) -> Subscription {
|
) -> Subscription {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
cx.observe_new::<Session>(move |session, _, cx| {
|
let configure = Arc::new(configure);
|
||||||
let client = session.adapter_client().unwrap();
|
cx.observe_new::<Session>(move |_, _, cx| {
|
||||||
register_default_handlers(session, &client, cx);
|
let configure = configure.clone();
|
||||||
configure(&client);
|
cx.subscribe_self(move |session, event, cx| {
|
||||||
cx.background_spawn(async move {
|
let configure = configure.clone();
|
||||||
client
|
if matches!(event, SessionStateEvent::Running) {
|
||||||
.fake_event(dap::messages::Events::Initialized(Some(Default::default())))
|
let client = session.adapter_client().unwrap();
|
||||||
.await
|
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();
|
.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) {
|
fn register_default_handlers(session: &Session, client: &Arc<DebugAdapterClient>, cx: &mut App) {
|
||||||
client.on_request::<dap::requests::Initialize, _>(move |_, _| Ok(Default::default()));
|
client.on_request::<dap::requests::Initialize, _>(move |_, _| Ok(Default::default()));
|
||||||
let paths = session
|
let paths = session
|
||||||
|
|
|
@ -25,7 +25,6 @@ mod environment;
|
||||||
use buffer_diff::BufferDiff;
|
use buffer_diff::BufferDiff;
|
||||||
pub use environment::{EnvironmentErrorMessage, ProjectEnvironmentEvent};
|
pub use environment::{EnvironmentErrorMessage, ProjectEnvironmentEvent};
|
||||||
use git_store::{Repository, RepositoryId};
|
use git_store::{Repository, RepositoryId};
|
||||||
use task::DebugTaskDefinition;
|
|
||||||
pub mod search_history;
|
pub mod search_history;
|
||||||
mod yarn;
|
mod yarn;
|
||||||
|
|
||||||
|
@ -39,17 +38,13 @@ use client::{
|
||||||
};
|
};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
|
|
||||||
use dap::{
|
use dap::client::DebugAdapterClient;
|
||||||
adapters::{DebugAdapterBinary, TcpArguments},
|
|
||||||
client::DebugAdapterClient,
|
|
||||||
};
|
|
||||||
|
|
||||||
use collections::{BTreeSet, HashMap, HashSet};
|
use collections::{BTreeSet, HashMap, HashSet};
|
||||||
use debounced_delay::DebouncedDelay;
|
use debounced_delay::DebouncedDelay;
|
||||||
use debugger::{
|
use debugger::{
|
||||||
breakpoint_store::BreakpointStore,
|
breakpoint_store::BreakpointStore,
|
||||||
dap_store::{DapStore, DapStoreEvent},
|
dap_store::{DapStore, DapStoreEvent},
|
||||||
session::Session,
|
|
||||||
};
|
};
|
||||||
pub use environment::ProjectEnvironment;
|
pub use environment::ProjectEnvironment;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -97,7 +92,6 @@ use snippet::Snippet;
|
||||||
use snippet_provider::SnippetProvider;
|
use snippet_provider::SnippetProvider;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
net::Ipv4Addr,
|
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::{Component, Path, PathBuf},
|
path::{Component, Path, PathBuf},
|
||||||
pin::pin,
|
pin::pin,
|
||||||
|
@ -107,7 +101,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use task_store::TaskStore;
|
use task_store::TaskStore;
|
||||||
use terminals::{SshCommand, Terminals, wrap_for_ssh};
|
use terminals::Terminals;
|
||||||
use text::{Anchor, BufferId};
|
use text::{Anchor, BufferId};
|
||||||
use toolchain_store::EmptyToolchainStore;
|
use toolchain_store::EmptyToolchainStore;
|
||||||
use util::{
|
use util::{
|
||||||
|
@ -1072,8 +1066,9 @@ impl Project {
|
||||||
let dap_store = cx.new(|cx| {
|
let dap_store = cx.new(|cx| {
|
||||||
DapStore::new_ssh(
|
DapStore::new_ssh(
|
||||||
SSH_PROJECT_ID,
|
SSH_PROJECT_ID,
|
||||||
ssh_proto.clone(),
|
ssh.clone(),
|
||||||
breakpoint_store.clone(),
|
breakpoint_store.clone(),
|
||||||
|
worktree_store.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -1258,6 +1253,7 @@ impl Project {
|
||||||
remote_id,
|
remote_id,
|
||||||
client.clone().into(),
|
client.clone().into(),
|
||||||
breakpoint_store.clone(),
|
breakpoint_store.clone(),
|
||||||
|
worktree_store.clone(),
|
||||||
cx,
|
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"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub async fn example(
|
pub async fn example(
|
||||||
root_paths: impl IntoIterator<Item = &Path>,
|
root_paths: impl IntoIterator<Item = &Path>,
|
||||||
|
|
|
@ -4,7 +4,7 @@ use anyhow::{Result, anyhow};
|
||||||
use gpui::{Context, Task};
|
use gpui::{Context, Task};
|
||||||
use project::TaskSourceKind;
|
use project::TaskSourceKind;
|
||||||
use remote::ConnectionState;
|
use remote::ConnectionState;
|
||||||
use task::{ResolvedTask, SpawnInTerminal, TaskContext, TaskTemplate};
|
use task::{DebugTaskDefinition, ResolvedTask, SpawnInTerminal, TaskContext, TaskTemplate};
|
||||||
use ui::Window;
|
use ui::Window;
|
||||||
|
|
||||||
use crate::Workspace;
|
use crate::Workspace;
|
||||||
|
@ -109,14 +109,26 @@ impl Workspace {
|
||||||
debug_config.definition
|
debug_config.definition
|
||||||
};
|
};
|
||||||
|
|
||||||
project
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
.update(cx, |project, cx| project.start_debug_session(config, cx))?
|
workspace.start_debug_session(config, window, cx);
|
||||||
.await?;
|
})?;
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_debug_session(
|
||||||
|
&mut self,
|
||||||
|
definition: DebugTaskDefinition,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(provider) = self.debugger_provider.as_mut() {
|
||||||
|
provider.start_session(definition, window, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn_in_terminal(
|
pub fn spawn_in_terminal(
|
||||||
self: &mut Workspace,
|
self: &mut Workspace,
|
||||||
spawn_in_terminal: SpawnInTerminal,
|
spawn_in_terminal: SpawnInTerminal,
|
||||||
|
|
|
@ -96,7 +96,7 @@ use std::{
|
||||||
sync::{Arc, LazyLock, Weak, atomic::AtomicUsize},
|
sync::{Arc, LazyLock, Weak, atomic::AtomicUsize},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use task::SpawnInTerminal;
|
use task::{DebugTaskDefinition, SpawnInTerminal};
|
||||||
use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
|
use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
|
||||||
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
||||||
pub use ui;
|
pub use ui;
|
||||||
|
@ -139,6 +139,10 @@ pub trait TerminalProvider {
|
||||||
) -> Task<Result<ExitStatus>>;
|
) -> Task<Result<ExitStatus>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait DebuggerProvider {
|
||||||
|
fn start_session(&self, definition: DebugTaskDefinition, window: &mut Window, cx: &mut App);
|
||||||
|
}
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
workspace,
|
workspace,
|
||||||
[
|
[
|
||||||
|
@ -860,6 +864,7 @@ pub struct Workspace {
|
||||||
on_prompt_for_new_path: Option<PromptForNewPath>,
|
on_prompt_for_new_path: Option<PromptForNewPath>,
|
||||||
on_prompt_for_open_path: Option<PromptForOpenPath>,
|
on_prompt_for_open_path: Option<PromptForOpenPath>,
|
||||||
terminal_provider: Option<Box<dyn TerminalProvider>>,
|
terminal_provider: Option<Box<dyn TerminalProvider>>,
|
||||||
|
debugger_provider: Option<Box<dyn DebuggerProvider>>,
|
||||||
serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
|
serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
|
||||||
serialized_ssh_project: Option<SerializedSshProject>,
|
serialized_ssh_project: Option<SerializedSshProject>,
|
||||||
_items_serializer: Task<Result<()>>,
|
_items_serializer: Task<Result<()>>,
|
||||||
|
@ -1186,6 +1191,7 @@ impl Workspace {
|
||||||
on_prompt_for_new_path: None,
|
on_prompt_for_new_path: None,
|
||||||
on_prompt_for_open_path: None,
|
on_prompt_for_open_path: None,
|
||||||
terminal_provider: None,
|
terminal_provider: None,
|
||||||
|
debugger_provider: None,
|
||||||
serializable_items_tx,
|
serializable_items_tx,
|
||||||
_items_serializer,
|
_items_serializer,
|
||||||
session_id: Some(session_id),
|
session_id: Some(session_id),
|
||||||
|
@ -1705,6 +1711,10 @@ impl Workspace {
|
||||||
self.terminal_provider = Some(Box::new(provider));
|
self.terminal_provider = Some(Box::new(provider));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_debugger_provider(&mut self, provider: impl DebuggerProvider + 'static) {
|
||||||
|
self.debugger_provider = Some(Box::new(provider));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn serialized_ssh_project(&self) -> Option<SerializedSshProject> {
|
pub fn serialized_ssh_project(&self) -> Option<SerializedSshProject> {
|
||||||
self.serialized_ssh_project.clone()
|
self.serialized_ssh_project.clone()
|
||||||
}
|
}
|
||||||
|
|
|
@ -444,7 +444,7 @@ fn initialize_panels(
|
||||||
window,
|
window,
|
||||||
async move |workspace: gpui::WeakEntity<Workspace>,
|
async move |workspace: gpui::WeakEntity<Workspace>,
|
||||||
cx: &mut AsyncWindowContext| {
|
cx: &mut AsyncWindowContext| {
|
||||||
let debug_panel = DebugPanel::load(workspace.clone(), cx.clone()).await?;
|
let debug_panel = DebugPanel::load(workspace.clone(), cx).await?;
|
||||||
workspace.update_in(cx, |workspace, window, cx| {
|
workspace.update_in(cx, |workspace, window, cx| {
|
||||||
workspace.add_panel(debug_panel, window, cx);
|
workspace.add_panel(debug_panel, window, cx);
|
||||||
})?;
|
})?;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue