Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send synced breakpoints to active DAP servers in collab #89

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 270 additions & 5 deletions crates/collab/src/tests/debug_panel_tests.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
use call::ActiveCall;
use dap::{
requests::{Disconnect, Initialize, Launch, RestartFrame, StackTrace},
StackFrame,
requests::{Disconnect, Initialize, Launch, RestartFrame, SetBreakpoints, StackTrace},
SourceBreakpoint, StackFrame,
};
use debugger_ui::debugger_panel::DebugPanel;
use editor::Editor;
use gpui::{TestAppContext, View, VisualTestContext};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
use project::ProjectPath;
use serde_json::json;
use std::{
path::Path,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use workspace::{dock::Panel, Workspace};

Expand Down Expand Up @@ -827,3 +833,262 @@ async fn test_restart_stack_frame(cx_a: &mut TestAppContext, cx_b: &mut TestAppC

shutdown_client.await.unwrap();
}

#[gpui::test]
async fn test_updated_breakpoints_send_to_dap(
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
let executor = cx_a.executor();
let mut server = TestServer::start(executor.clone()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;

client_a
.fs()
.insert_tree(
"/a",
json!({
"test.txt": "one\ntwo\nthree\nfour\nfive",
}),
)
.await;

init_test(cx_a);
init_test(cx_b);

server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global);

let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
active_call_a
.update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
.await
.unwrap();

let project_path = ProjectPath {
worktree_id,
path: Arc::from(Path::new(&"test.txt")),
};

let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await
.unwrap();

let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);

add_debugger_panel(&workspace_a, cx_a).await;
add_debugger_panel(&workspace_b, cx_b).await;

let task = project_a.update(cx_a, |project, cx| {
project.dap_store().update(cx, |store, cx| {
store.start_debug_session(
dap::DebugAdapterConfig {
label: "test config".into(),
kind: dap::DebugAdapterKind::Fake,
request: dap::DebugRequestType::Launch,
program: None,
cwd: None,
initialize_args: None,
},
cx,
)
})
});

let (session, client) = task.await.unwrap();

client
.on_request::<Initialize, _>(move |_, _| {
Ok(dap::Capabilities {
supports_restart_frame: Some(true),
..Default::default()
})
})
.await;

client.on_request::<Launch, _>(move |_, _| Ok(())).await;
client
.on_request::<StackTrace, _>(move |_, _| {
Ok(dap::StackTraceResponse {
stack_frames: Vec::default(),
total_frames: None,
})
})
.await;

let called_set_breakpoints = Arc::new(AtomicBool::new(false));
client
.on_request::<SetBreakpoints, _>({
let called_set_breakpoints = called_set_breakpoints.clone();
move |_, args| {
assert_eq!("/a/test.txt", args.source.path.unwrap());
assert_eq!(
vec![SourceBreakpoint {
line: 3,
column: None,
condition: None,
hit_condition: None,
log_message: None,
mode: None
}],
args.breakpoints.unwrap()
);

called_set_breakpoints.store(true, Ordering::SeqCst);

Ok(dap::SetBreakpointsResponse {
breakpoints: Vec::default(),
})
}
})
.await;

client.on_request::<Disconnect, _>(move |_, _| Ok(())).await;

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_a.run_until_parked();
cx_b.run_until_parked();

// Client B opens an editor.
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path(project_path.clone(), None, true, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();

editor_b.update(cx_b, |editor, cx| {
editor.move_down(&editor::actions::MoveDown, cx);
editor.move_down(&editor::actions::MoveDown, cx);
editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, cx);
});

// Client A opens an editor.
let editor_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path(project_path.clone(), None, true, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();

cx_a.run_until_parked();
cx_b.run_until_parked();

let called_set_breakpoints = Arc::new(AtomicBool::new(false));
client
.on_request::<SetBreakpoints, _>({
let called_set_breakpoints = called_set_breakpoints.clone();
move |_, args| {
assert_eq!("/a/test.txt", args.source.path.unwrap());
assert!(args.breakpoints.unwrap().is_empty());

called_set_breakpoints.store(true, Ordering::SeqCst);

Ok(dap::SetBreakpointsResponse {
breakpoints: Vec::default(),
})
}
})
.await;

// remove the breakpoint that client B added
editor_a.update(cx_a, |editor, cx| {
editor.move_down(&editor::actions::MoveDown, cx);
editor.move_down(&editor::actions::MoveDown, cx);
editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, cx);
});

cx_a.run_until_parked();
cx_b.run_until_parked();

assert!(
called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
"SetBreakpoint request must be called"
);

let called_set_breakpoints = Arc::new(AtomicBool::new(false));
client
.on_request::<SetBreakpoints, _>({
let called_set_breakpoints = called_set_breakpoints.clone();
move |_, args| {
assert_eq!("/a/test.txt", args.source.path.unwrap());
assert_eq!(
vec![
SourceBreakpoint {
line: 3,
column: None,
condition: None,
hit_condition: None,
log_message: None,
mode: None
},
SourceBreakpoint {
line: 2,
column: None,
condition: None,
hit_condition: None,
log_message: None,
mode: None
}
],
args.breakpoints.unwrap()
);

called_set_breakpoints.store(true, Ordering::SeqCst);

Ok(dap::SetBreakpointsResponse {
breakpoints: Vec::default(),
})
}
})
.await;

// Add our own breakpoint now
editor_a.update(cx_a, |editor, cx| {
editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, cx);
editor.move_up(&editor::actions::MoveUp, cx);
editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, cx);
});

cx_a.run_until_parked();
cx_b.run_until_parked();

assert!(
called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst),
"SetBreakpoint request must be called"
);

let shutdown_client = project_a.update(cx_a, |project, cx| {
project.dap_store().update(cx, |dap_store, cx| {
dap_store.shutdown_session(&session.read(cx).id(), cx)
})
});

shutdown_client.await.unwrap();
}
1 change: 1 addition & 0 deletions crates/collab/src/tests/editor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2466,6 +2466,7 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
.unwrap()
.downcast::<Editor>()
.unwrap();

cx_a.run_until_parked();
cx_b.run_until_parked();

Expand Down
4 changes: 4 additions & 0 deletions crates/dap/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,8 @@ impl DebugSession {
pub fn clients(&self) -> impl Iterator<Item = Arc<DebugAdapterClient>> + '_ {
self.clients.values().cloned()
}

pub fn client_ids(&self) -> impl Iterator<Item = DebugAdapterClientId> + '_ {
self.clients.keys().cloned()
}
}
2 changes: 1 addition & 1 deletion crates/debugger_ui/src/debugger_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ impl DebugPanel {
cx.notify();
}
project::Event::SetDebugClient(set_debug_client) => {
let _res = this.handle_set_debug_panel_item(set_debug_client, cx);
this.handle_set_debug_panel_item(set_debug_client, cx);
}
_ => {}
}
Expand Down
23 changes: 10 additions & 13 deletions crates/project/src/dap_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub enum DapStoreEvent {
message: Message,
},
Notification(String),
BreakpointsChanged,
BreakpointsChanged(ProjectPath),
ActiveDebugLineChanged,
SetDebugPanelItem(SetDebuggerPanelItem),
UpdateDebugAdapter(UpdateDebugAdapter),
Expand Down Expand Up @@ -278,7 +278,7 @@ impl DapStore {
pub fn client_by_id(
&self,
client_id: &DebugAdapterClientId,
cx: &mut ModelContext<Self>,
cx: &ModelContext<Self>,
) -> Option<(Model<DebugSession>, Arc<DebugAdapterClient>)> {
let session = self.session_by_client_id(client_id)?;
let client = session.read(cx).client_by_id(client_id)?;
Expand Down Expand Up @@ -1687,10 +1687,10 @@ impl DapStore {
if breakpoints.is_empty() {
store.breakpoints.remove(&project_path);
} else {
store.breakpoints.insert(project_path, breakpoints);
store.breakpoints.insert(project_path.clone(), breakpoints);
}

cx.emit(DapStoreEvent::BreakpointsChanged);
cx.emit(DapStoreEvent::BreakpointsChanged(project_path));

cx.notify();
})
Expand Down Expand Up @@ -1797,11 +1797,9 @@ impl DapStore {
&mut self,
project_path: &ProjectPath,
mut breakpoint: Breakpoint,
buffer_path: PathBuf,
buffer_snapshot: BufferSnapshot,
edit_action: BreakpointEditAction,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
) {
let upstream_client = self.upstream_client();

let breakpoint_set = self.breakpoints.entry(project_path.clone()).or_default();
Expand Down Expand Up @@ -1840,9 +1838,8 @@ impl DapStore {
self.breakpoints.remove(project_path);
}

cx.emit(DapStoreEvent::BreakpointsChanged(project_path.clone()));
cx.notify();

self.send_changed_breakpoints(project_path, buffer_path, buffer_snapshot, cx)
}

pub fn send_breakpoints(
Expand All @@ -1851,7 +1848,7 @@ impl DapStore {
absolute_file_path: Arc<Path>,
mut breakpoints: Vec<SourceBreakpoint>,
ignore: bool,
cx: &mut ModelContext<Self>,
cx: &ModelContext<Self>,
) -> Task<Result<()>> {
let Some((_, client)) = self.client_by_id(client_id, cx) else {
return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id)));
Expand Down Expand Up @@ -1889,9 +1886,9 @@ impl DapStore {
pub fn send_changed_breakpoints(
&self,
project_path: &ProjectPath,
buffer_path: PathBuf,
absolute_path: PathBuf,
buffer_snapshot: BufferSnapshot,
cx: &mut ModelContext<Self>,
cx: &ModelContext<Self>,
) -> Task<Result<()>> {
let Some(local_store) = self.as_local() else {
return Task::ready(Err(anyhow!("cannot start session on remote side")));
Expand All @@ -1913,7 +1910,7 @@ impl DapStore {
for client in session.clients().collect::<Vec<_>>() {
tasks.push(self.send_breakpoints(
&client.id(),
Arc::from(buffer_path.clone()),
Arc::from(absolute_path.clone()),
source_breakpoints.clone(),
ignore_breakpoints,
cx,
Expand Down
Loading
Loading