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 workspace.toggle_panel_focus::<DebugPanel>(window, cx);
64 })
65 .register_action(|workspace, _: &Pause, _, cx| {
66 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
67 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
68 panel
69 .active_session()
70 .map(|session| session.read(cx).running_state().clone())
71 }) {
72 active_item.update(cx, |item, cx| item.pause_thread(cx))
73 }
74 }
75 })
76 .register_action(|workspace, _: &Restart, _, cx| {
77 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
78 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
79 panel
80 .active_session()
81 .map(|session| session.read(cx).running_state().clone())
82 }) {
83 active_item.update(cx, |item, cx| item.restart_session(cx))
84 }
85 }
86 })
87 .register_action(|workspace, _: &StepInto, _, cx| {
88 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
89 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
90 panel
91 .active_session()
92 .map(|session| session.read(cx).running_state().clone())
93 }) {
94 active_item.update(cx, |item, cx| item.step_in(cx))
95 }
96 }
97 })
98 .register_action(|workspace, _: &StepOver, _, cx| {
99 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
100 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
101 panel
102 .active_session()
103 .map(|session| session.read(cx).running_state().clone())
104 }) {
105 active_item.update(cx, |item, cx| item.step_over(cx))
106 }
107 }
108 })
109 .register_action(|workspace, _: &StepBack, _, cx| {
110 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
111 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
112 panel
113 .active_session()
114 .map(|session| session.read(cx).running_state().clone())
115 }) {
116 active_item.update(cx, |item, cx| item.step_back(cx))
117 }
118 }
119 })
120 .register_action(|workspace, _: &Stop, _, cx| {
121 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
122 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
123 panel
124 .active_session()
125 .map(|session| session.read(cx).running_state().clone())
126 }) {
127 cx.defer(move |cx| {
128 active_item.update(cx, |item, cx| item.stop_thread(cx))
129 })
130 }
131 }
132 })
133 .register_action(|workspace, _: &ToggleIgnoreBreakpoints, _, cx| {
134 if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
135 if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
136 panel
137 .active_session()
138 .map(|session| session.read(cx).running_state().clone())
139 }) {
140 active_item.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
141 }
142 }
143 })
144 .register_action(
145 |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| {
146 workspace.project().update(cx, |project, cx| {
147 project.dap_store().update(cx, |store, cx| {
148 store.shutdown_sessions(cx).detach();
149 })
150 })
151 },
152 )
153 .register_action(
154 |workspace: &mut Workspace, _: &ShowStackTrace, window, cx| {
155 let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) else {
156 return;
157 };
158
159 if let Some(existing) = workspace.item_of_type::<StackTraceView>(cx) {
160 let is_active = workspace
161 .active_item(cx)
162 .is_some_and(|item| item.item_id() == existing.item_id());
163 workspace.activate_item(&existing, true, !is_active, window, cx);
164 } else {
165 let Some(active_session) = debug_panel.read(cx).active_session() else {
166 return;
167 };
168
169 let project = workspace.project();
170
171 let stack_trace_view = active_session.update(cx, |session, cx| {
172 session.stack_trace_view(project, window, cx).clone()
173 });
174
175 workspace.add_item_to_active_pane(
176 Box::new(stack_trace_view),
177 None,
178 true,
179 window,
180 cx,
181 );
182 }
183 },
184 )
185 .register_action(|workspace: &mut Workspace, _: &Start, window, cx| {
186 NewSessionModal::show(workspace, window, cx);
187 });
188 })
189 })
190 .detach();
191
192 cx.observe_new({
193 move |editor: &mut Editor, _, cx| {
194 editor
195 .register_action(cx.listener(
196 move |editor, _: &editor::actions::DebuggerRunToCursor, _, cx| {
197 maybe!({
198 let debug_panel =
199 editor.workspace()?.read(cx).panel::<DebugPanel>(cx)?;
200 let cursor_point: language::Point = editor.selections.newest(cx).head();
201 let active_session = debug_panel.read(cx).active_session()?;
202
203 let (buffer, position, _) = editor
204 .buffer()
205 .read(cx)
206 .point_to_buffer_point(cursor_point, cx)?;
207
208 let path =
209 debugger::breakpoint_store::BreakpointStore::abs_path_from_buffer(
210 &buffer, cx,
211 )?;
212
213 let source_breakpoint = SourceBreakpoint {
214 row: position.row,
215 path,
216 message: None,
217 condition: None,
218 hit_condition: None,
219 state: debugger::breakpoint_store::BreakpointState::Enabled,
220 };
221
222 active_session.update(cx, |session, cx| {
223 session.running_state().update(cx, |state, cx| {
224 if let Some(thread_id) = state.selected_thread_id() {
225 state.session().update(cx, |session, cx| {
226 session.run_to_position(
227 source_breakpoint,
228 thread_id,
229 cx,
230 );
231 })
232 }
233 });
234 });
235
236 Some(())
237 });
238 },
239 ))
240 .detach();
241
242 editor
243 .register_action(cx.listener(
244 move |editor, _: &editor::actions::DebuggerEvaluateSelectedText, window, cx| {
245 maybe!({
246 let debug_panel =
247 editor.workspace()?.read(cx).panel::<DebugPanel>(cx)?;
248 let active_session = debug_panel.read(cx).active_session()?;
249
250 let text = editor.text_for_range(
251 editor.selections.newest(cx).range(),
252 &mut None,
253 window,
254 cx,
255 )?;
256
257 active_session.update(cx, |session, cx| {
258 session.running_state().update(cx, |state, cx| {
259 let stack_id = state.selected_stack_frame_id(cx);
260
261 state.session().update(cx, |session, cx| {
262 session.evaluate(text, None, stack_id, None, cx).detach();
263 });
264 });
265 });
266
267 Some(())
268 });
269 },
270 ))
271 .detach();
272 }
273 })
274 .detach();
275}