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