1use dap::debugger_settings::DebuggerSettings;
2use debugger_panel::{DebugPanel, ToggleFocus};
3use editor::Editor;
4use feature_flags::{DebuggerFeatureFlag, FeatureFlagViewExt};
5use gpui::{App, EntityInputHandler, actions};
6use new_session_modal::NewSessionModal;
7use project::debugger::{self, breakpoint_store::SourceBreakpoint};
8use session::DebugSession;
9use settings::Settings;
10use stack_trace_view::StackTraceView;
11use util::maybe;
12use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace};
13
14pub mod attach_modal;
15pub mod debugger_panel;
16mod new_session_modal;
17mod persistence;
18pub(crate) mod session;
19mod stack_trace_view;
20
21#[cfg(any(test, feature = "test-support"))]
22pub mod tests;
23
24actions!(
25 debugger,
26 [
27 Start,
28 Continue,
29 Detach,
30 Pause,
31 Restart,
32 StepInto,
33 StepOver,
34 StepOut,
35 StepBack,
36 Stop,
37 ToggleIgnoreBreakpoints,
38 ClearAllBreakpoints,
39 FocusConsole,
40 FocusVariables,
41 FocusBreakpointList,
42 FocusFrames,
43 FocusModules,
44 FocusLoadedSources,
45 FocusTerminal,
46 ShowStackTrace,
47 ]
48);
49
50pub fn init(cx: &mut App) {
51 DebuggerSettings::register(cx);
52 workspace::FollowableViewRegistry::register::<DebugSession>(cx);
53
54 cx.observe_new(|_: &mut Workspace, window, cx| {
55 let Some(window) = window else {
56 return;
57 };
58
59 cx.when_flag_enabled::<DebuggerFeatureFlag>(window, |workspace, _, _| {
60 workspace
61 .register_action(|workspace, _: &ToggleFocus, window, cx| {
62 workspace.toggle_panel_focus::<DebugPanel>(window, cx);
63 })
64 .register_action(|workspace, _: &Pause, _, cx| {
65 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
66 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
67 panel
68 .active_session()
69 .map(|session| session.read(cx).running_state().clone())
70 }) {
71 active_item.update(cx, |item, cx| item.pause_thread(cx))
72 }
73 }
74 })
75 .register_action(|workspace, _: &Restart, _, cx| {
76 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
77 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
78 panel
79 .active_session()
80 .map(|session| session.read(cx).running_state().clone())
81 }) {
82 active_item.update(cx, |item, cx| item.restart_session(cx))
83 }
84 }
85 })
86 .register_action(|workspace, _: &StepInto, _, cx| {
87 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
88 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
89 panel
90 .active_session()
91 .map(|session| session.read(cx).running_state().clone())
92 }) {
93 active_item.update(cx, |item, cx| item.step_in(cx))
94 }
95 }
96 })
97 .register_action(|workspace, _: &StepOver, _, cx| {
98 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
99 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
100 panel
101 .active_session()
102 .map(|session| session.read(cx).running_state().clone())
103 }) {
104 active_item.update(cx, |item, cx| item.step_over(cx))
105 }
106 }
107 })
108 .register_action(|workspace, _: &StepBack, _, cx| {
109 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
110 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
111 panel
112 .active_session()
113 .map(|session| session.read(cx).running_state().clone())
114 }) {
115 active_item.update(cx, |item, cx| item.step_back(cx))
116 }
117 }
118 })
119 .register_action(|workspace, _: &Stop, _, cx| {
120 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
121 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
122 panel
123 .active_session()
124 .map(|session| session.read(cx).running_state().clone())
125 }) {
126 cx.defer(move |cx| {
127 active_item.update(cx, |item, cx| item.stop_thread(cx))
128 })
129 }
130 }
131 })
132 .register_action(|workspace, _: &ToggleIgnoreBreakpoints, _, cx| {
133 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
134 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
135 panel
136 .active_session()
137 .map(|session| session.read(cx).running_state().clone())
138 }) {
139 active_item.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
140 }
141 }
142 })
143 .register_action(
144 |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
145 workspace.project().update(cx, |project, cx| {
146 project.dap_store().update(cx, |store, cx| {
147 store.shutdown_sessions(cx).detach();
148 })
149 })
150 },
151 )
152 .register_action(
153 |workspace: &mut Workspace, _: &ShowStackTrace, window, cx| {
154 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
155 return;
156 };
157
158 if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx) {
159 let is_active = workspace
160 .active_item(cx)
161 .is_some_and(|item| item.item_id() == existing.item_id());
162 workspace.activate_item(&existing, true, !is_active, window, cx);
163 } else {
164 let Some(active_session) = debug_panel.read(cx).active_session() else {
165 return;
166 };
167
168 let project = workspace.project();
169
170 let stack_trace_view = active_session.update(cx, |session, cx| {
171 session.stack_trace_view(project, window, cx).clone()
172 });
173
174 workspace.add_item_to_active_pane(
175 Box::new(stack_trace_view),
176 None,
177 true,
178 window,
179 cx,
180 );
181 }
182 },
183 )
184 .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
185 NewSessionModal::show(workspace, window, cx);
186 });
187 })
188 })
189 .detach();
190
191 cx.observe_new({
192 move |editor: &mut Editor, _, cx| {
193 editor
194 .register_action(cx.listener(
195 move |editor, _: &editor::actions::DebuggerRunToCursor, _, cx| {
196 maybe!({
197 let debug_panel =
198 editor.workspace()?.read(cx).panel::<DebugPanel>(cx)?;
199 let cursor_point: language::Point = editor.selections.newest(cx).head();
200 let active_session = debug_panel.read(cx).active_session()?;
201
202 let (buffer, position, _) = editor
203 .buffer()
204 .read(cx)
205 .point_to_buffer_point(cursor_point, cx)?;
206
207 let path =
208 debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
209 &buffer, cx,
210 )?;
211
212 let source_breakpoint = SourceBreakpoint {
213 row: position.row,
214 path,
215 message: None,
216 condition: None,
217 hit_condition: None,
218 state: debugger::breakpoint_store::BreakpointState::Enabled,
219 };
220
221 active_session.update(cx, |session, cx| {
222 session.running_state().update(cx, |state, cx| {
223 if let Some(thread_id) = state.selected_thread_id() {
224 state.session().update(cx, |session, cx| {
225 session.run_to_position(
226 source_breakpoint,
227 thread_id,
228 cx,
229 );
230 })
231 }
232 });
233 });
234
235 Some(())
236 });
237 },
238 ))
239 .detach();
240
241 editor
242 .register_action(cx.listener(
243 move |editor, _: &editor::actions::DebuggerEvaluateSelectedText, window, cx| {
244 maybe!({
245 let debug_panel =
246 editor.workspace()?.read(cx).panel::<DebugPanel>(cx)?;
247 let active_session = debug_panel.read(cx).active_session()?;
248
249 let text = editor.text_for_range(
250 editor.selections.newest(cx).range(),
251 &mut None,
252 window,
253 cx,
254 )?;
255
256 active_session.update(cx, |session, cx| {
257 session.running_state().update(cx, |state, cx| {
258 let stack_id = state.selected_stack_frame_id(cx);
259
260 state.session().update(cx, |session, cx| {
261 session.evaluate(text, None, stack_id, None, cx).detach();
262 });
263 });
264 });
265
266 Some(())
267 });
268 },
269 ))
270 .detach();
271 }
272 })
273 .detach();
274}