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

Add restart stack frame #85

Merged
merged 5 commits into from
Jan 14, 2025
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
6 changes: 6 additions & 0 deletions crates/collab/src/db/tables/debug_clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const SUPPORTS_SINGLE_THREAD_EXECUTION_REQUESTS_BIT: u32 = 4;
const SUPPORTS_STEP_BACK_BIT: u32 = 5;
const SUPPORTS_STEPPING_GRANULARITY_BIT: u32 = 6;
const SUPPORTS_TERMINATE_THREADS_REQUEST_BIT: u32 = 7;
const SUPPORTS_RESTART_FRAME_REQUEST_BIT: u32 = 8;

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "debug_clients")]
Expand Down Expand Up @@ -49,6 +50,9 @@ impl Model {
supports_terminate_threads_request: (self.capabilities
& (1 << SUPPORTS_TERMINATE_THREADS_REQUEST_BIT))
!= 0,
supports_restart_frame_request: (self.capabilities
& (1 << SUPPORTS_RESTART_FRAME_REQUEST_BIT))
!= 0,
}
}

Expand All @@ -69,6 +73,8 @@ impl Model {
<< SUPPORTS_STEPPING_GRANULARITY_BIT;
capabilities_bit_mask |= (capabilities.supports_terminate_threads_request as i32)
<< SUPPORTS_TERMINATE_THREADS_REQUEST_BIT;
capabilities_bit_mask |= (capabilities.supports_restart_frame_request as i32)
<< SUPPORTS_RESTART_FRAME_REQUEST_BIT;

self.capabilities = capabilities_bit_mask;
}
Expand Down
5 changes: 4 additions & 1 deletion crates/collab/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,10 @@ impl Server {
.add_request_handler(forward_mutating_project_request::<proto::DapRestartRequest>)
.add_request_handler(forward_mutating_project_request::<proto::DapTerminateRequest>)
.add_request_handler(forward_mutating_project_request::<proto::DapShutdownSession>)
.add_message_handler(broadcast_project_message_from_host::<proto::UpdateThreadStatus>);
.add_message_handler(broadcast_project_message_from_host::<proto::UpdateThreadStatus>)
.add_message_handler(
broadcast_project_message_from_host::<proto::DapRestartStackFrameRequest>,
);

Arc::new(server)
}
Expand Down
178 changes: 177 additions & 1 deletion crates/collab/src/tests/debug_panel_tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
use call::ActiveCall;
use dap::requests::{Disconnect, Initialize, Launch, StackTrace};
use dap::{
requests::{Disconnect, Initialize, Launch, RestartFrame, StackTrace},
StackFrame,
};
use debugger_ui::debugger_panel::DebugPanel;
use gpui::{TestAppContext, View, VisualTestContext};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use workspace::{dock::Panel, Workspace};

use super::TestServer;
Expand Down Expand Up @@ -651,3 +658,172 @@ async fn test_debug_panel_remote_button_presses(
});
});
}

#[gpui::test]
async fn test_restart_stack_frame(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;

init_test(cx_a);
init_test(cx_b);

let called_restart_frame = Arc::new(AtomicBool::new(false));

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_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;

let stack_frames = vec![StackFrame {
id: 1,
name: "Stack Frame 1".into(),
source: Some(dap::Source {
name: Some("test.js".into()),
path: Some("/project/src/test.js".into()),
source_reference: None,
presentation_hint: None,
origin: None,
sources: None,
adapter_data: None,
checksums: None,
}),
line: 3,
column: 1,
end_line: None,
end_column: None,
can_restart: None,
instruction_pointer_reference: None,
module_id: None,
presentation_hint: None,
}];

client
.on_request::<StackTrace, _>({
let stack_frames = Arc::new(stack_frames.clone());
move |_, args| {
assert_eq!(1, args.thread_id);

Ok(dap::StackTraceResponse {
stack_frames: (*stack_frames).clone(),
total_frames: None,
})
}
})
.await;

client
.on_request::<RestartFrame, _>({
let called_restart_frame = called_restart_frame.clone();
move |_, args| {
assert_eq!(1, args.frame_id);

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

Ok(())
}
})
.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();

// try to restart stack frame 1 from the guest side
workspace_b.update(cx_b, |workspace, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_debug_panel_item = debug_panel
.update(cx, |this, cx| this.active_debug_panel_item(cx))
.unwrap();

active_debug_panel_item.update(cx, |debug_panel_item, cx| {
debug_panel_item
.stack_frame_list()
.update(cx, |stack_frame_list, cx| {
stack_frame_list.restart_stack_frame(1, cx);
});
});
});

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

assert!(
called_restart_frame.load(std::sync::atomic::Ordering::SeqCst),
"Restart stack frame was not 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();
}
2 changes: 2 additions & 0 deletions crates/dap/src/proto_conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ pub fn capabilities_from_proto(payload: &SetDebugClientCapabilities) -> Capabili
supports_step_back: Some(payload.supports_step_back),
supports_stepping_granularity: Some(payload.supports_stepping_granularity),
supports_terminate_threads_request: Some(payload.supports_terminate_threads_request),
supports_restart_frame: Some(payload.supports_restart_frame_request),
..Default::default()
}
}
Expand Down Expand Up @@ -374,6 +375,7 @@ pub fn capabilities_to_proto(
supports_terminate_threads_request: capabilities
.supports_terminate_threads_request
.unwrap_or_default(),
supports_restart_frame_request: capabilities.supports_restart_frame.unwrap_or_default(),
}
}

Expand Down
1 change: 0 additions & 1 deletion crates/debugger_ui/src/debugger_panel_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,6 @@ impl DebugPanelItem {
});

self.active_thread_item = ThreadItem::from_proto(state.active_thread_item());
// self.update_thread_state_status(ThreadStatus::Stopped, cx); // This is a band aid fix for thread status not being sent correctly all the time

if let Some(stack_frame_list) = state.stack_frame_list.as_ref() {
self.stack_frame_list.update(cx, |this, cx| {
Expand Down
80 changes: 67 additions & 13 deletions crates/debugger_ui/src/stack_frame_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,14 @@ impl StackFrameList {
.ok()?
}

pub fn restart_stack_frame(&mut self, stack_frame_id: u64, cx: &mut ViewContext<Self>) {
self.dap_store.update(cx, |store, cx| {
store
.restart_stack_frame(&self.client_id, stack_frame_id, cx)
.detach_and_log_err(cx);
});
}

fn render_entry(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
let stack_frame = &self.stack_frames[ix];

Expand All @@ -251,8 +259,16 @@ impl StackFrameList {
stack_frame.line,
);

v_flex()
let supports_frame_restart = self
.dap_store
.read(cx)
.capabilities_by_id(&self.client_id)
.supports_restart_frame
.unwrap_or_default();

h_flex()
.rounded_md()
.justify_between()
.w_full()
.group("")
.id(("stack-frame", stack_frame.id))
Expand All @@ -271,20 +287,58 @@ impl StackFrameList {
.detach_and_log_err(cx);
}
}))
.hover(|s| s.bg(cx.theme().colors().element_hover).cursor_pointer())
.hover(|style| style.bg(cx.theme().colors().element_hover).cursor_pointer())
.child(
h_flex()
.gap_0p5()
.text_ui_sm(cx)
.child(stack_frame.name.clone())
.child(formatted_path),
)
.child(
h_flex()
.text_ui_xs(cx)
.text_color(cx.theme().colors().text_muted)
.when_some(source.and_then(|s| s.path), |this, path| this.child(path)),
v_flex()
.child(
h_flex()
.gap_0p5()
.text_ui_sm(cx)
.truncate()
.child(stack_frame.name.clone())
.child(formatted_path),
)
.child(
h_flex()
.text_ui_xs(cx)
.truncate()
.text_color(cx.theme().colors().text_muted)
.when_some(source.and_then(|s| s.path), |this, path| this.child(path)),
),
)
.when(supports_frame_restart, |this| {
this.child(
h_flex()
.id(("restart-stack-frame", stack_frame.id))
.visible_on_hover("")
.absolute()
.right_2()
.overflow_hidden()
.rounded_md()
.border_1()
.border_color(cx.theme().colors().element_selected)
.bg(cx.theme().colors().element_background)
.hover(|style| {
style
.bg(cx.theme().colors().ghost_element_hover)
.cursor_pointer()
})
.child(
IconButton::new(
("restart-stack-frame", stack_frame.id),
IconName::DebugRestart,
)
.icon_size(IconSize::Small)
.on_click(cx.listener({
let stack_frame_id = stack_frame.id;
move |this, _, cx| {
this.restart_stack_frame(stack_frame_id, cx);
}
}))
.tooltip(move |cx| Tooltip::text("Restart Stack Frame", cx)),
),
)
})
.into_any()
}
}
Expand Down
Loading
Loading