Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:33:00.114Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

debugger_panel.rs

1924 lines · 79.9 KB · rust
1use crate::persistence::DebuggerPaneItem;
2use crate::session::DebugSession;
3use crate::session::running::RunningState;
4use crate::session::running::breakpoint_list::BreakpointList;
5
6use crate::{
7    ClearAllBreakpoints, Continue, CopyDebugAdapterArguments, Detach, FocusBreakpointList,
8    FocusConsole, FocusFrames, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables,
9    NewProcessModal, NewProcessMode, Pause, RerunSession, StepInto, StepOut, StepOver, Stop,
10    ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
11};
12use anyhow::{Context as _, Result, anyhow};
13use collections::IndexMap;
14use dap::adapters::DebugAdapterName;
15use dap::{DapRegistry, StartDebuggingRequestArguments};
16use dap::{client::SessionId, debugger_settings::DebuggerSettings};
17use editor::Editor;
18use feature_flags::{FeatureFlag, FeatureFlagAppExt as _, PresenceFlag, register_feature_flag};
19use gpui::{
20    Action, Anchor, App, AsyncWindowContext, ClipboardItem, Context, DismissEvent, Entity,
21    EntityId, EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point,
22    Subscription, Task, TaskExt, WeakEntity, anchored, deferred,
23};
24
25use itertools::Itertools as _;
26use language::Buffer;
27use project::debugger::session::{Session, SessionQuirks, SessionState, SessionStateEvent};
28use project::{DebugScenarioContext, Fs, ProjectPath, TaskSourceKind, WorktreeId};
29use project::{Project, debugger::session::ThreadStatus};
30use rpc::proto::{self};
31use settings::Settings;
32use std::sync::Arc;
33use task::{DebugScenario, SharedTaskContext};
34
35use ui::{
36    ButtonLike, ContextMenu, Divider, ElevationIndex, PopoverMenu, PopoverMenuHandle, SplitButton,
37    Tab, TintColor, Tooltip, prelude::*,
38};
39use util::redact::redact_command;
40use util::rel_path::RelPath;
41use util::{ResultExt, debug_panic, maybe};
42use workspace::SplitDirection;
43use workspace::item::SaveOptions;
44use workspace::{
45    Item, Pane, Workspace,
46    dock::{DockPosition, Panel, PanelEvent},
47};
48use zed_actions::debug_panel::ToggleFocus;
49
50pub struct DebuggerHistoryFeatureFlag;
51
52impl FeatureFlag for DebuggerHistoryFeatureFlag {
53    const NAME: &'static str = "debugger-history";
54    type Value = PresenceFlag;
55}
56register_feature_flag!(DebuggerHistoryFeatureFlag);
57
58const DEBUG_PANEL_KEY: &str = "DebugPanel";
59
60pub struct DebugPanel {
61    active_session: Option<Entity<DebugSession>>,
62    project: Entity<Project>,
63    workspace: WeakEntity<Workspace>,
64    focus_handle: FocusHandle,
65    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
66    debug_scenario_scheduled_last: bool,
67    pub(crate) sessions_with_children:
68        IndexMap<Entity<DebugSession>, Vec<WeakEntity<DebugSession>>>,
69    pub(crate) thread_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
70    pub(crate) session_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
71    fs: Arc<dyn Fs>,
72    is_zoomed: bool,
73    _subscriptions: [Subscription; 1],
74    breakpoint_list: Entity<BreakpointList>,
75}
76
77impl DebugPanel {
78    pub fn new(
79        workspace: &Workspace,
80        window: &mut Window,
81        cx: &mut Context<Workspace>,
82    ) -> Entity<Self> {
83        cx.new(|cx| {
84            let project = workspace.project().clone();
85            let focus_handle = cx.focus_handle();
86            let thread_picker_menu_handle = PopoverMenuHandle::default();
87            let session_picker_menu_handle = PopoverMenuHandle::default();
88
89            let focus_subscription = cx.on_focus(
90                &focus_handle,
91                window,
92                |this: &mut DebugPanel, window, cx| {
93                    this.focus_active_item(window, cx);
94                },
95            );
96
97            Self {
98                sessions_with_children: Default::default(),
99                active_session: None,
100                focus_handle,
101                breakpoint_list: BreakpointList::new(
102                    None,
103                    workspace.weak_handle(),
104                    &project,
105                    window,
106                    cx,
107                ),
108                project,
109                workspace: workspace.weak_handle(),
110                context_menu: None,
111                fs: workspace.app_state().fs.clone(),
112                thread_picker_menu_handle,
113                session_picker_menu_handle,
114                is_zoomed: false,
115                _subscriptions: [focus_subscription],
116                debug_scenario_scheduled_last: true,
117            }
118        })
119    }
120
121    pub(crate) fn focus_active_item(&mut self, window: &mut Window, cx: &mut Context<Self>) {
122        let Some(session) = self.active_session.clone() else {
123            return;
124        };
125        let active_pane = session
126            .read(cx)
127            .running_state()
128            .read(cx)
129            .active_pane()
130            .clone();
131        active_pane.update(cx, |pane, cx| {
132            pane.focus_active_item(window, cx);
133        });
134    }
135
136    #[cfg(test)]
137    pub(crate) fn sessions(&self) -> impl Iterator<Item = Entity<DebugSession>> {
138        self.sessions_with_children.keys().cloned()
139    }
140
141    pub fn active_session(&self) -> Option<Entity<DebugSession>> {
142        self.active_session.clone()
143    }
144
145    pub(crate) fn running_state(&self, cx: &mut App) -> Option<Entity<RunningState>> {
146        self.active_session()
147            .map(|session| session.read(cx).running_state().clone())
148    }
149
150    pub fn project(&self) -> &Entity<Project> {
151        &self.project
152    }
153
154    pub fn load(
155        workspace: WeakEntity<Workspace>,
156        cx: &mut AsyncWindowContext,
157    ) -> Task<Result<Entity<Self>>> {
158        cx.spawn(async move |cx| {
159            workspace.update_in(cx, |workspace, window, cx| {
160                let debug_panel = DebugPanel::new(workspace, window, cx);
161
162                workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {
163                    workspace.project().read(cx).breakpoint_store().update(
164                        cx,
165                        |breakpoint_store, cx| {
166                            breakpoint_store.clear_breakpoints(cx);
167                        },
168                    )
169                });
170
171                workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));
172
173                debug_panel
174            })
175        })
176    }
177
178    pub fn start_session(
179        &mut self,
180        scenario: DebugScenario,
181        task_context: SharedTaskContext,
182        active_buffer: Option<Entity<Buffer>>,
183        worktree_id: Option<WorktreeId>,
184        window: &mut Window,
185        cx: &mut Context<Self>,
186    ) {
187        let dap_store = self.project.read(cx).dap_store();
188        let Some(adapter) = DapRegistry::global(cx).adapter(&scenario.adapter) else {
189            return;
190        };
191        let quirks = SessionQuirks {
192            compact: adapter.compact_child_session(),
193            prefer_thread_name: adapter.prefer_thread_name(),
194        };
195        let session = dap_store.update(cx, |dap_store, cx| {
196            dap_store.new_session(
197                Some(scenario.label.clone()),
198                DebugAdapterName(scenario.adapter.clone()),
199                task_context.clone(),
200                None,
201                quirks,
202                cx,
203            )
204        });
205        let worktree = worktree_id.or_else(|| {
206            active_buffer
207                .as_ref()
208                .and_then(|buffer| buffer.read(cx).file())
209                .map(|f| f.worktree_id(cx))
210        });
211
212        let Some(worktree) = worktree
213            .and_then(|id| self.project.read(cx).worktree_for_id(id, cx))
214            .or_else(|| self.project.read(cx).visible_worktrees(cx).next())
215        else {
216            log::debug!("Could not find a worktree to spawn the debug session in");
217            return;
218        };
219
220        self.debug_scenario_scheduled_last = true;
221        if let Some(inventory) = self
222            .project
223            .read(cx)
224            .task_store()
225            .read(cx)
226            .task_inventory()
227            .cloned()
228        {
229            inventory.update(cx, |inventory, _| {
230                inventory.scenario_scheduled(
231                    scenario.clone(),
232                    task_context.clone(),
233                    worktree_id,
234                    active_buffer.as_ref().map(|buffer| buffer.downgrade()),
235                );
236            })
237        }
238        let task = cx.spawn_in(window, {
239            let session = session.clone();
240            async move |this, cx| {
241                let debug_session =
242                    Self::register_session(this.clone(), session.clone(), true, cx).await?;
243                let definition = debug_session
244                    .update_in(cx, |debug_session, window, cx| {
245                        debug_session.running_state().update(cx, |running, cx| {
246                            if scenario.build.is_some() {
247                                running.scenario = Some(scenario.clone());
248                                running.scenario_context = Some(DebugScenarioContext {
249                                    active_buffer: active_buffer
250                                        .as_ref()
251                                        .map(|entity| entity.downgrade()),
252                                    task_context: task_context.clone(),
253                                    worktree_id,
254                                });
255                            };
256                            running.resolve_scenario(
257                                scenario,
258                                task_context,
259                                active_buffer,
260                                worktree_id,
261                                window,
262                                cx,
263                            )
264                        })
265                    })?
266                    .await?;
267                dap_store
268                    .update(cx, |dap_store, cx| {
269                        dap_store.boot_session(session.clone(), definition, worktree, cx)
270                    })
271                    .await
272            }
273        });
274
275        let boot_task = cx.spawn({
276            let session = session.clone();
277
278            async move |_, cx| {
279                if let Err(error) = task.await {
280                    let redacted_error = redact_command(&format!("{error:#}"));
281                    log::error!("{redacted_error}");
282                    session
283                        .update(cx, |session, cx| {
284                            session
285                                .console_output(cx)
286                                .unbounded_send(format!("error: {:#}", redacted_error))
287                                .ok();
288                            session.shutdown(cx)
289                        })
290                        .await;
291                }
292                anyhow::Ok(())
293            }
294        });
295
296        session.update(cx, |session, _| match &mut session.state {
297            SessionState::Booting(state_task) => {
298                *state_task = Some(boot_task);
299            }
300            SessionState::Running(_) => {
301                debug_panic!("Session state should be in building because we are just starting it");
302            }
303        });
304    }
305
306    pub(crate) fn rerun_last_session(
307        &mut self,
308        workspace: &mut Workspace,
309        window: &mut Window,
310        cx: &mut Context<Self>,
311    ) {
312        let task_store = workspace.project().read(cx).task_store().clone();
313        let Some(task_inventory) = task_store.read(cx).task_inventory() else {
314            return;
315        };
316        let workspace = self.workspace.clone();
317        let Some((scenario, context)) = task_inventory.read(cx).last_scheduled_scenario().cloned()
318        else {
319            window.defer(cx, move |window, cx| {
320                workspace
321                    .update(cx, |workspace, cx| {
322                        NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
323                    })
324                    .ok();
325            });
326            return;
327        };
328
329        let DebugScenarioContext {
330            task_context,
331            worktree_id,
332            active_buffer,
333        } = context;
334
335        let active_buffer = active_buffer.and_then(|buffer| buffer.upgrade());
336
337        self.start_session(
338            scenario,
339            task_context,
340            active_buffer,
341            worktree_id,
342            window,
343            cx,
344        );
345    }
346
347    pub(crate) async fn register_session(
348        this: WeakEntity<Self>,
349        session: Entity<Session>,
350        focus: bool,
351        cx: &mut AsyncWindowContext,
352    ) -> Result<Entity<DebugSession>> {
353        let debug_session = register_session_inner(&this, session, cx).await?;
354
355        let workspace = this.update_in(cx, |this, window, cx| {
356            if focus {
357                this.activate_session(debug_session.clone(), window, cx);
358            }
359
360            this.workspace.clone()
361        })?;
362        workspace.update_in(cx, |workspace, window, cx| {
363            workspace.focus_panel::<Self>(window, cx);
364        })?;
365        Ok(debug_session)
366    }
367
368    pub(crate) fn handle_restart_request(
369        &mut self,
370        mut curr_session: Entity<Session>,
371        window: &mut Window,
372        cx: &mut Context<Self>,
373    ) {
374        while let Some(parent_session) = curr_session.read(cx).parent_session().cloned() {
375            curr_session = parent_session;
376        }
377
378        let Some(worktree) = curr_session.read(cx).worktree() else {
379            log::error!("Attempted to restart a non-running session");
380            return;
381        };
382
383        let dap_store_handle = self.project.read(cx).dap_store();
384        let label = curr_session.read(cx).label();
385        let quirks = curr_session.read(cx).quirks();
386        let adapter = curr_session.read(cx).adapter();
387        let binary = curr_session.read(cx).binary().cloned().unwrap();
388        let task_context = curr_session.read(cx).task_context().clone();
389
390        let curr_session_id = curr_session.read(cx).session_id();
391        self.sessions_with_children
392            .retain(|session, _| session.read(cx).session_id(cx) != curr_session_id);
393        let task = dap_store_handle.update(cx, |dap_store, cx| {
394            dap_store.shutdown_session(curr_session_id, cx)
395        });
396
397        cx.spawn_in(window, async move |this, cx| {
398            task.await.log_err();
399
400            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
401                let session = dap_store.new_session(label, adapter, task_context, None, quirks, cx);
402
403                let task = session.update(cx, |session, cx| {
404                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
405                });
406                (session, task)
407            });
408            Self::register_session(this.clone(), session.clone(), true, cx).await?;
409
410            if let Err(error) = task.await {
411                session
412                    .update(cx, |session, cx| {
413                        session
414                            .console_output(cx)
415                            .unbounded_send(format!(
416                                "Session failed to restart with error: {}",
417                                error
418                            ))
419                            .ok();
420                        session.shutdown(cx)
421                    })
422                    .await;
423
424                return Err(error);
425            };
426
427            Ok(())
428        })
429        .detach_and_log_err(cx);
430    }
431
432    pub fn handle_start_debugging_request(
433        &mut self,
434        request: &StartDebuggingRequestArguments,
435        parent_session: Entity<Session>,
436        window: &mut Window,
437        cx: &mut Context<Self>,
438    ) {
439        let Some(worktree) = parent_session.read(cx).worktree() else {
440            log::error!("Attempted to start a child-session from a non-running session");
441            return;
442        };
443
444        let dap_store_handle = self.project.read(cx).dap_store();
445        let label = self.label_for_child_session(&parent_session, request, cx);
446        let adapter = parent_session.read(cx).adapter();
447        let quirks = parent_session.read(cx).quirks();
448        let Some(mut binary) = parent_session.read(cx).binary().cloned() else {
449            log::error!("Attempted to start a child-session without a binary");
450            return;
451        };
452        let task_context = parent_session.read(cx).task_context().clone();
453        binary.request_args = request.clone();
454        cx.spawn_in(window, async move |this, cx| {
455            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
456                let session = dap_store.new_session(
457                    label,
458                    adapter,
459                    task_context,
460                    Some(parent_session.clone()),
461                    quirks,
462                    cx,
463                );
464
465                let task = session.update(cx, |session, cx| {
466                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
467                });
468                (session, task)
469            });
470            // Focus child sessions if the parent has never emitted a stopped event;
471            // this improves our JavaScript experience, as it always spawns a "main" session that then spawns subsessions.
472            let parent_ever_stopped = parent_session.update(cx, |this, _| this.has_ever_stopped());
473            Self::register_session(this, session, !parent_ever_stopped, cx).await?;
474            task.await
475        })
476        .detach_and_log_err(cx);
477    }
478
479    pub(crate) fn close_session(
480        &mut self,
481        entity_id: EntityId,
482        window: &mut Window,
483        cx: &mut Context<Self>,
484    ) {
485        let Some(session) = self
486            .sessions_with_children
487            .keys()
488            .find(|other| entity_id == other.entity_id())
489            .cloned()
490        else {
491            return;
492        };
493        session.update(cx, |this, cx| {
494            this.running_state().update(cx, |this, cx| {
495                this.serialize_layout(window, cx);
496            });
497        });
498        cx.spawn_in(window, async move |this, cx| {
499            session.update(cx, |session, cx| session.shutdown(cx));
500            this.update(cx, |this, cx| {
501                this.retain_sessions(&|other: &Entity<DebugSession>| {
502                    entity_id != other.entity_id()
503                });
504                if let Some(active_session_id) = this
505                    .active_session
506                    .as_ref()
507                    .map(|session| session.entity_id())
508                    && active_session_id == entity_id
509                {
510                    this.active_session = this.sessions_with_children.keys().next().cloned();
511                }
512                cx.notify()
513            })
514            .ok();
515        })
516        .detach();
517    }
518
519    pub(crate) fn deploy_context_menu(
520        &mut self,
521        position: Point<Pixels>,
522        window: &mut Window,
523        cx: &mut Context<Self>,
524    ) {
525        if let Some(running_state) = self
526            .active_session
527            .as_ref()
528            .map(|session| session.read(cx).running_state().clone())
529        {
530            let pane_items_status = running_state.read(cx).pane_items_status(cx);
531            let this = cx.weak_entity();
532
533            let context_menu = ContextMenu::build(window, cx, |mut menu, _window, _cx| {
534                for (item_kind, is_visible) in pane_items_status.into_iter() {
535                    menu = menu.toggleable_entry(item_kind, is_visible, IconPosition::End, None, {
536                        let this = this.clone();
537                        move |window, cx| {
538                            this.update(cx, |this, cx| {
539                                if let Some(running_state) = this
540                                    .active_session
541                                    .as_ref()
542                                    .map(|session| session.read(cx).running_state().clone())
543                                {
544                                    running_state.update(cx, |state, cx| {
545                                        if is_visible {
546                                            state.remove_pane_item(item_kind, window, cx);
547                                        } else {
548                                            state.add_pane_item(item_kind, position, window, cx);
549                                        }
550                                    })
551                                }
552                            })
553                            .ok();
554                        }
555                    });
556                }
557
558                menu
559            });
560
561            window.focus(&context_menu.focus_handle(cx), cx);
562            let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
563                this.context_menu.take();
564                cx.notify();
565            });
566            self.context_menu = Some((context_menu, position, subscription));
567        }
568    }
569
570    fn copy_debug_adapter_arguments(
571        &mut self,
572        _: &CopyDebugAdapterArguments,
573        _window: &mut Window,
574        cx: &mut Context<Self>,
575    ) {
576        let content = maybe!({
577            let mut session = self.active_session()?.read(cx).session(cx);
578            while let Some(parent) = session.read(cx).parent_session().cloned() {
579                session = parent;
580            }
581            let binary = session.read(cx).binary()?;
582            let content = serde_json::to_string_pretty(&binary).ok()?;
583            Some(content)
584        });
585        if let Some(content) = content {
586            cx.write_to_clipboard(ClipboardItem::new_string(content));
587        }
588    }
589
590    pub(crate) fn top_controls_strip(
591        &mut self,
592        window: &mut Window,
593        cx: &mut Context<Self>,
594    ) -> Option<Div> {
595        let active_session = self.active_session.clone();
596        let focus_handle = self.focus_handle.clone();
597        let is_side = self.position(window, cx).axis() == gpui::Axis::Horizontal;
598        let div = if is_side { v_flex() } else { h_flex() };
599
600        let new_session_button = || {
601            IconButton::new("debug-new-session", IconName::Plus)
602                .icon_size(IconSize::Small)
603                .on_click({
604                    move |_, window, cx| window.dispatch_action(crate::Start.boxed_clone(), cx)
605                })
606                .tooltip({
607                    let focus_handle = focus_handle.clone();
608                    move |_window, cx| {
609                        Tooltip::for_action_in(
610                            "Start Debug Session",
611                            &crate::Start,
612                            &focus_handle,
613                            cx,
614                        )
615                    }
616                })
617        };
618
619        let edit_debug_json_button = || {
620            IconButton::new("debug-edit-debug-json", IconName::Code)
621                .icon_size(IconSize::Small)
622                .on_click(|_, window, cx| {
623                    window.dispatch_action(zed_actions::OpenProjectDebugTasks.boxed_clone(), cx);
624                })
625                .tooltip(Tooltip::text("Edit debug.json"))
626        };
627
628        let documentation_button = || {
629            IconButton::new("debug-open-documentation", IconName::CircleHelp)
630                .icon_size(IconSize::Small)
631                .on_click(move |_, _, cx| cx.open_url("https://zed.dev/docs/debugger"))
632                .tooltip(Tooltip::text("Open Documentation"))
633        };
634
635        let logs_button = || {
636            IconButton::new("debug-open-logs", IconName::Notepad)
637                .icon_size(IconSize::Small)
638                .on_click(move |_, window, cx| {
639                    window.dispatch_action(debugger_tools::OpenDebugAdapterLogs.boxed_clone(), cx)
640                })
641                .tooltip(Tooltip::text("Open Debug Adapter Logs"))
642        };
643
644        let close_bottom_panel_button = {
645            h_flex().pl_0p5().gap_1().child(Divider::vertical()).child(
646                IconButton::new("debug-close-panel", IconName::Close)
647                    .icon_size(IconSize::Small)
648                    .on_click(move |_, window, cx| {
649                        window.dispatch_action(workspace::ToggleBottomDock.boxed_clone(), cx)
650                    })
651                    .tooltip(Tooltip::text("Close Panel")),
652            )
653        };
654
655        let thread_status = active_session
656            .as_ref()
657            .map(|session| session.read(cx).running_state())
658            .and_then(|state| state.read(cx).thread_status(cx))
659            .unwrap_or(project::debugger::session::ThreadStatus::Exited);
660
661        Some(
662            div.w_full()
663                .py_1()
664                .px_1p5()
665                .justify_between()
666                .border_b_1()
667                .border_color(cx.theme().colors().border)
668                .when(is_side, |this| this.gap_1().h(Tab::container_height(cx)))
669                .child(
670                    h_flex()
671                        .justify_between()
672                        .child(
673                            h_flex().gap_1().w_full().when_some(
674                                active_session
675                                    .as_ref()
676                                    .map(|session| session.read(cx).running_state()),
677                                |this, running_state| {
678                                    let capabilities = running_state.read(cx).capabilities(cx);
679                                    let supports_detach =
680                                        running_state.read(cx).session().read(cx).is_attached();
681
682                                    this.map(|this| {
683                                        if thread_status == ThreadStatus::Running {
684                                            this.child(
685                                                IconButton::new(
686                                                    "debug-pause",
687                                                    IconName::DebugPause,
688                                                )
689                                                .icon_size(IconSize::Small)
690                                                .on_click(window.listener_for(
691                                                    running_state,
692                                                    |this, _, _window, cx| {
693                                                        this.pause_thread(cx);
694                                                    },
695                                                ))
696                                                .tooltip({
697                                                    let focus_handle = focus_handle.clone();
698                                                    move |_window, cx| {
699                                                        Tooltip::for_action_in(
700                                                            "Pause Program",
701                                                            &Pause,
702                                                            &focus_handle,
703                                                            cx,
704                                                        )
705                                                    }
706                                                }),
707                                            )
708                                        } else {
709                                            this.child(
710                                                IconButton::new(
711                                                    "debug-continue",
712                                                    IconName::DebugContinue,
713                                                )
714                                                .icon_size(IconSize::Small)
715                                                .disabled(thread_status != ThreadStatus::Stopped)
716                                                .on_click(window.listener_for(
717                                                    running_state,
718                                                    |this, _, _window, cx| this.continue_thread(cx),
719                                                ))
720                                                .tooltip({
721                                                    let focus_handle = focus_handle.clone();
722                                                    move |_window, cx| {
723                                                        Tooltip::for_action_in(
724                                                            "Continue Program",
725                                                            &Continue,
726                                                            &focus_handle,
727                                                            cx,
728                                                        )
729                                                    }
730                                                }),
731                                            )
732                                        }
733                                    })
734                                    .child(
735                                        IconButton::new("step-over", IconName::DebugStepOver)
736                                            .icon_size(IconSize::Small)
737                                            .on_click(window.listener_for(
738                                                running_state,
739                                                |this, _, _window, cx| {
740                                                    this.step_over(cx);
741                                                },
742                                            ))
743                                            .disabled(thread_status != ThreadStatus::Stopped)
744                                            .tooltip({
745                                                let focus_handle = focus_handle.clone();
746                                                move |_window, cx| {
747                                                    Tooltip::for_action_in(
748                                                        "Step Over",
749                                                        &StepOver,
750                                                        &focus_handle,
751                                                        cx,
752                                                    )
753                                                }
754                                            }),
755                                    )
756                                    .child(
757                                        IconButton::new("step-into", IconName::DebugStepInto)
758                                            .icon_size(IconSize::Small)
759                                            .on_click(window.listener_for(
760                                                running_state,
761                                                |this, _, _window, cx| {
762                                                    this.step_in(cx);
763                                                },
764                                            ))
765                                            .disabled(thread_status != ThreadStatus::Stopped)
766                                            .tooltip({
767                                                let focus_handle = focus_handle.clone();
768                                                move |_window, cx| {
769                                                    Tooltip::for_action_in(
770                                                        "Step In",
771                                                        &StepInto,
772                                                        &focus_handle,
773                                                        cx,
774                                                    )
775                                                }
776                                            }),
777                                    )
778                                    .child(
779                                        IconButton::new("step-out", IconName::DebugStepOut)
780                                            .icon_size(IconSize::Small)
781                                            .on_click(window.listener_for(
782                                                running_state,
783                                                |this, _, _window, cx| {
784                                                    this.step_out(cx);
785                                                },
786                                            ))
787                                            .disabled(thread_status != ThreadStatus::Stopped)
788                                            .tooltip({
789                                                let focus_handle = focus_handle.clone();
790                                                move |_window, cx| {
791                                                    Tooltip::for_action_in(
792                                                        "Step Out",
793                                                        &StepOut,
794                                                        &focus_handle,
795                                                        cx,
796                                                    )
797                                                }
798                                            }),
799                                    )
800                                    .child(Divider::vertical())
801                                    .child(
802                                        IconButton::new("debug-restart", IconName::RotateCcw)
803                                            .icon_size(IconSize::Small)
804                                            .on_click(window.listener_for(
805                                                running_state,
806                                                |this, _, window, cx| {
807                                                    this.rerun_session(window, cx);
808                                                },
809                                            ))
810                                            .tooltip({
811                                                let focus_handle = focus_handle.clone();
812                                                move |_window, cx| {
813                                                    Tooltip::for_action_in(
814                                                        "Rerun Session",
815                                                        &RerunSession,
816                                                        &focus_handle,
817                                                        cx,
818                                                    )
819                                                }
820                                            }),
821                                    )
822                                    .child(
823                                        IconButton::new("debug-stop", IconName::Power)
824                                            .icon_size(IconSize::Small)
825                                            .on_click(window.listener_for(
826                                                running_state,
827                                                |this, _, _window, cx| {
828                                                    if this.session().read(cx).is_building() {
829                                                        this.session().update(cx, |session, cx| {
830                                                            session.shutdown(cx).detach()
831                                                        });
832                                                    } else {
833                                                        this.stop_thread(cx);
834                                                    }
835                                                },
836                                            ))
837                                            .disabled(active_session.as_ref().is_none_or(
838                                                |session| {
839                                                    session
840                                                        .read(cx)
841                                                        .session(cx)
842                                                        .read(cx)
843                                                        .is_terminated()
844                                                },
845                                            ))
846                                            .tooltip({
847                                                let focus_handle = focus_handle.clone();
848                                                let label = if capabilities
849                                                    .supports_terminate_threads_request
850                                                    .unwrap_or_default()
851                                                {
852                                                    "Terminate Thread"
853                                                } else {
854                                                    "Terminate All Threads"
855                                                };
856                                                move |_window, cx| {
857                                                    Tooltip::for_action_in(
858                                                        label,
859                                                        &Stop,
860                                                        &focus_handle,
861                                                        cx,
862                                                    )
863                                                }
864                                            }),
865                                    )
866                                    .when(supports_detach, |div| {
867                                        div.child(
868                                            IconButton::new(
869                                                "debug-disconnect",
870                                                IconName::DebugDetach,
871                                            )
872                                            .disabled(
873                                                thread_status != ThreadStatus::Stopped
874                                                    && thread_status != ThreadStatus::Running,
875                                            )
876                                            .icon_size(IconSize::Small)
877                                            .on_click(window.listener_for(
878                                                running_state,
879                                                |this, _, _, cx| {
880                                                    this.detach_client(cx);
881                                                },
882                                            ))
883                                            .tooltip({
884                                                let focus_handle = focus_handle.clone();
885                                                move |_window, cx| {
886                                                    Tooltip::for_action_in(
887                                                        "Detach",
888                                                        &Detach,
889                                                        &focus_handle,
890                                                        cx,
891                                                    )
892                                                }
893                                            }),
894                                        )
895                                    })
896                                    .when(
897                                        cx.has_flag::<DebuggerHistoryFeatureFlag>(),
898                                        |this| {
899                                            this.child(Divider::vertical()).child(
900                                                SplitButton::new(
901                                                    self.render_history_button(
902                                                        &running_state,
903                                                        thread_status,
904                                                        window,
905                                                    ),
906                                                    self.render_history_toggle_button(
907                                                        thread_status,
908                                                        &running_state,
909                                                    )
910                                                    .into_any_element(),
911                                                )
912                                                .style(ui::SplitButtonStyle::Outlined),
913                                            )
914                                        },
915                                    )
916                                },
917                            ),
918                        )
919                        .when(is_side, |this| {
920                            this.child(new_session_button())
921                                .child(edit_debug_json_button())
922                                .child(documentation_button())
923                                .child(logs_button())
924                        }),
925                )
926                .child(
927                    h_flex()
928                        .gap_0p5()
929                        .when(is_side, |this| this.justify_between())
930                        .child(
931                            h_flex().when_some(
932                                active_session
933                                    .as_ref()
934                                    .map(|session| session.read(cx).running_state())
935                                    .cloned(),
936                                |this, running_state| {
937                                    let threads = running_state.update(cx, |running_state, cx| {
938                                        let session = running_state.session();
939                                        session.read(cx).is_started().then(|| {
940                                            session.update(cx, |session, cx| session.threads(cx))
941                                        })
942                                    });
943
944                                    let thread_dropdown = threads.and_then(|threads| {
945                                        self.render_thread_dropdown(
946                                            &running_state,
947                                            threads,
948                                            window,
949                                            cx,
950                                        )
951                                    });
952
953                                    this.when_some(thread_dropdown, |this, dropdown| {
954                                        this.child(dropdown).when(!is_side, |this| {
955                                            this.gap_0p5().child(Divider::vertical())
956                                        })
957                                    })
958                                },
959                            ),
960                        )
961                        .child(
962                            h_flex()
963                                .gap_0p5()
964                                .children(self.render_session_menu(
965                                    self.active_session(),
966                                    self.running_state(cx),
967                                    window,
968                                    cx,
969                                ))
970                                .when(!is_side, |this| {
971                                    this.child(new_session_button())
972                                        .child(edit_debug_json_button())
973                                        .child(documentation_button())
974                                        .child(logs_button())
975                                        .child(close_bottom_panel_button)
976                                }),
977                        ),
978                ),
979        )
980    }
981
982    pub(crate) fn activate_pane_in_direction(
983        &mut self,
984        direction: SplitDirection,
985        window: &mut Window,
986        cx: &mut Context<Self>,
987    ) {
988        if let Some(session) = self.active_session() {
989            session.update(cx, |session, cx| {
990                session.running_state().update(cx, |running, cx| {
991                    running.activate_pane_in_direction(direction, window, cx);
992                })
993            });
994        }
995    }
996
997    pub(crate) fn activate_item(
998        &mut self,
999        item: DebuggerPaneItem,
1000        window: &mut Window,
1001        cx: &mut Context<Self>,
1002    ) {
1003        if let Some(session) = self.active_session() {
1004            session.update(cx, |session, cx| {
1005                session.running_state().update(cx, |running, cx| {
1006                    running.activate_item(item, window, cx);
1007                });
1008            });
1009        }
1010    }
1011
1012    pub(crate) fn activate_session_by_id(
1013        &mut self,
1014        session_id: SessionId,
1015        window: &mut Window,
1016        cx: &mut Context<Self>,
1017    ) {
1018        if let Some(session) = self
1019            .sessions_with_children
1020            .keys()
1021            .find(|session| session.read(cx).session_id(cx) == session_id)
1022        {
1023            self.activate_session(session.clone(), window, cx);
1024        }
1025    }
1026
1027    pub(crate) fn activate_session(
1028        &mut self,
1029        session_item: Entity<DebugSession>,
1030        window: &mut Window,
1031        cx: &mut Context<Self>,
1032    ) {
1033        debug_assert!(self.sessions_with_children.contains_key(&session_item));
1034        session_item.focus_handle(cx).focus(window, cx);
1035        session_item.update(cx, |this, cx| {
1036            this.running_state().update(cx, |this, cx| {
1037                this.go_to_selected_stack_frame(window, cx);
1038            });
1039        });
1040        self.active_session = Some(session_item);
1041        cx.notify();
1042    }
1043
1044    pub(crate) fn go_to_scenario_definition(
1045        &self,
1046        kind: TaskSourceKind,
1047        scenario: DebugScenario,
1048        worktree_id: WorktreeId,
1049        window: &mut Window,
1050        cx: &mut Context<Self>,
1051    ) -> Task<Result<()>> {
1052        let Some(workspace) = self.workspace.upgrade() else {
1053            return Task::ready(Ok(()));
1054        };
1055        let project_path = match kind {
1056            TaskSourceKind::AbsPath { abs_path, .. } => {
1057                let Some(project_path) = workspace
1058                    .read(cx)
1059                    .project()
1060                    .read(cx)
1061                    .project_path_for_absolute_path(&abs_path, cx)
1062                else {
1063                    return Task::ready(Err(anyhow!("no abs path")));
1064                };
1065
1066                project_path
1067            }
1068            TaskSourceKind::Worktree {
1069                id,
1070                directory_in_worktree: dir,
1071                ..
1072            } => {
1073                let relative_path = if dir.ends_with(RelPath::from_unix_str(".vscode").unwrap()) {
1074                    dir.join(RelPath::from_unix_str("launch.json").unwrap())
1075                } else {
1076                    dir.join(RelPath::from_unix_str("debug.json").unwrap())
1077                };
1078                ProjectPath {
1079                    worktree_id: id,
1080                    path: relative_path.into(),
1081                }
1082            }
1083            _ => return self.save_scenario(scenario, worktree_id, window, cx),
1084        };
1085
1086        let editor = workspace.update(cx, |workspace, cx| {
1087            workspace.open_path(project_path, None, true, window, cx)
1088        });
1089        cx.spawn_in(window, async move |_, cx| {
1090            let editor = editor.await?;
1091            let editor = cx
1092                .update(|_, cx| editor.act_as::<Editor>(cx))?
1093                .context("expected editor")?;
1094
1095            // unfortunately debug tasks don't have an easy way to globally
1096            // identify them. to jump to the one that you just created or an
1097            // old one that you're choosing to edit we use a heuristic of searching for a line with `label:  <your label>` from the end rather than the start so we bias towards more renctly
1098            editor.update_in(cx, |editor, window, cx| {
1099                let row = editor.text(cx).lines().enumerate().find_map(|(row, text)| {
1100                    if text.contains(scenario.label.as_ref()) && text.contains("\"label\": ") {
1101                        Some(row)
1102                    } else {
1103                        None
1104                    }
1105                });
1106                if let Some(row) = row {
1107                    editor.go_to_singleton_buffer_point(
1108                        text::Point::new(row as u32, 4),
1109                        window,
1110                        cx,
1111                    );
1112                }
1113            })?;
1114
1115            Ok(())
1116        })
1117    }
1118
1119    pub(crate) fn save_scenario(
1120        &self,
1121        scenario: DebugScenario,
1122        worktree_id: WorktreeId,
1123        window: &mut Window,
1124        cx: &mut Context<Self>,
1125    ) -> Task<Result<()>> {
1126        let this = cx.weak_entity();
1127        let project = self.project.clone();
1128        self.workspace
1129            .update(cx, |workspace, cx| {
1130                let Some(mut path) = workspace.absolute_path_of_worktree(worktree_id, cx) else {
1131                    return Task::ready(Err(anyhow!("Couldn't get worktree path")));
1132                };
1133
1134                let serialized_scenario = serde_json::to_value(scenario);
1135
1136                cx.spawn_in(window, async move |workspace, cx| {
1137                    let serialized_scenario = serialized_scenario?;
1138                    let fs =
1139                        workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
1140
1141                    path.push(paths::local_settings_folder_name());
1142                    if !fs.is_dir(path.as_path()).await {
1143                        fs.create_dir(path.as_path()).await?;
1144                    }
1145                    path.pop();
1146
1147                    path.push(paths::local_debug_file_relative_path().as_std_path());
1148                    let path = path.as_path();
1149
1150                    if !fs.is_file(path).await {
1151                        fs.create_file(path, Default::default()).await?;
1152                        fs.write(
1153                            path,
1154                            settings::initial_local_debug_tasks_content()
1155                                .to_string()
1156                                .as_bytes(),
1157                        )
1158                        .await?;
1159                    }
1160                    let project_path = workspace.update(cx, |workspace, cx| {
1161                        workspace
1162                            .project()
1163                            .read(cx)
1164                            .project_path_for_absolute_path(path, cx)
1165                            .context(
1166                                "Couldn't get project path for .zed/debug.json in active worktree",
1167                            )
1168                    })??;
1169
1170                    let editor = this
1171                        .update_in(cx, |this, window, cx| {
1172                            this.workspace.update(cx, |workspace, cx| {
1173                                workspace.open_path(project_path, None, true, window, cx)
1174                            })
1175                        })??
1176                        .await?;
1177                    let editor = cx
1178                        .update(|_, cx| editor.act_as::<Editor>(cx))?
1179                        .context("expected editor")?;
1180
1181                    let new_scenario = serde_json_lenient::to_string_pretty(&serialized_scenario)?
1182                        .lines()
1183                        .map(|l| format!("  {l}"))
1184                        .join("\n");
1185
1186                    editor
1187                        .update_in(cx, |editor, window, cx| {
1188                            Self::insert_task_into_editor(editor, new_scenario, project, window, cx)
1189                        })??
1190                        .await
1191                })
1192            })
1193            .unwrap_or_else(|err| Task::ready(Err(err)))
1194    }
1195
1196    pub fn insert_task_into_editor(
1197        editor: &mut Editor,
1198        new_scenario: String,
1199        project: Entity<Project>,
1200        window: &mut Window,
1201        cx: &mut Context<Editor>,
1202    ) -> Result<Task<Result<()>>> {
1203        tasks_ui::insert_task_json_into_editor(editor, new_scenario, window, cx)?;
1204        Ok(editor.save(SaveOptions::default(), project, window, cx))
1205    }
1206
1207    pub(crate) fn toggle_thread_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1208        self.thread_picker_menu_handle.toggle(window, cx);
1209    }
1210
1211    pub(crate) fn toggle_session_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1212        self.session_picker_menu_handle.toggle(window, cx);
1213    }
1214
1215    fn toggle_zoom(
1216        &mut self,
1217        _: &workspace::ToggleZoom,
1218        window: &mut Window,
1219        cx: &mut Context<Self>,
1220    ) {
1221        if self.is_zoomed {
1222            cx.emit(PanelEvent::ZoomOut);
1223        } else {
1224            if !self.focus_handle(cx).contains_focused(window, cx) {
1225                cx.focus_self(window);
1226            }
1227            cx.emit(PanelEvent::ZoomIn);
1228        }
1229    }
1230
1231    fn label_for_child_session(
1232        &self,
1233        parent_session: &Entity<Session>,
1234        request: &StartDebuggingRequestArguments,
1235        cx: &mut Context<'_, Self>,
1236    ) -> Option<SharedString> {
1237        let adapter = parent_session.read(cx).adapter();
1238        if let Some(adapter) = DapRegistry::global(cx).adapter(&adapter)
1239            && let Some(label) = adapter.label_for_child_session(request)
1240        {
1241            return Some(label.into());
1242        }
1243        None
1244    }
1245
1246    fn retain_sessions(&mut self, keep: &dyn Fn(&Entity<DebugSession>) -> bool) {
1247        self.sessions_with_children
1248            .retain(|session, _| keep(session));
1249        for children in self.sessions_with_children.values_mut() {
1250            children.retain(|child| {
1251                let Some(child) = child.upgrade() else {
1252                    return false;
1253                };
1254                keep(&child)
1255            });
1256        }
1257    }
1258
1259    fn render_history_button(
1260        &self,
1261        running_state: &Entity<RunningState>,
1262        thread_status: ThreadStatus,
1263        window: &mut Window,
1264    ) -> ButtonLike {
1265        ButtonLike::new_rounded_left("debug-back-in-history")
1266            .layer(ElevationIndex::ModalSurface)
1267            .child(Icon::new(IconName::HistoryRerun).size(IconSize::Small))
1268            .disabled(
1269                thread_status == ThreadStatus::Running || thread_status == ThreadStatus::Stepping,
1270            )
1271            .tooltip(Tooltip::text("Step Back in Session History"))
1272            .on_click(window.listener_for(running_state, |this, _, _window, cx| {
1273                this.session().update(cx, |session, cx| {
1274                    let ix = session
1275                        .active_snapshot_index()
1276                        .unwrap_or_else(|| session.historic_snapshots().len());
1277
1278                    session.select_historic_snapshot(Some(ix.saturating_sub(1)), cx);
1279                })
1280            }))
1281    }
1282
1283    fn render_history_toggle_button(
1284        &self,
1285        thread_status: ThreadStatus,
1286        running_state: &Entity<RunningState>,
1287    ) -> impl IntoElement {
1288        let chevron_button_size = rems_from_px(20.);
1289        PopoverMenu::new("debug-back-in-history-menu")
1290            .trigger(
1291                ButtonLike::new_rounded_right("debug-back-in-history-menu-trigger")
1292                    .layer(ElevationIndex::ModalSurface)
1293                    .selected_style(ButtonStyle::Tinted(TintColor::Accent))
1294                    .disabled(
1295                        thread_status == ThreadStatus::Running
1296                            || thread_status == ThreadStatus::Stepping,
1297                    )
1298                    .width(chevron_button_size)
1299                    .height(chevron_button_size.into())
1300                    .child(Icon::new(IconName::ChevronDown).size(IconSize::XSmall)),
1301            )
1302            .menu({
1303                let running_state = running_state.clone();
1304                move |window, cx| {
1305                    let handler =
1306                        |ix: Option<usize>, running_state: Entity<RunningState>, cx: &mut App| {
1307                            running_state.update(cx, |state, cx| {
1308                                state.session().update(cx, |session, cx| {
1309                                    session.select_historic_snapshot(ix, cx);
1310                                })
1311                            })
1312                        };
1313
1314                    let running_state = running_state.clone();
1315                    Some(ContextMenu::build(
1316                        window,
1317                        cx,
1318                        move |mut context_menu, _window, cx| {
1319                            let history = running_state
1320                                .read(cx)
1321                                .session()
1322                                .read(cx)
1323                                .historic_snapshots();
1324
1325                            context_menu = context_menu.entry("Current State", None, {
1326                                let running_state = running_state.clone();
1327                                move |_window, cx| {
1328                                    handler(None, running_state.clone(), cx);
1329                                }
1330                            });
1331
1332                            if !history.is_empty() {
1333                                context_menu = context_menu.separator();
1334                            }
1335
1336                            for (ix, _) in history.iter().enumerate().rev() {
1337                                context_menu =
1338                                    context_menu.entry(format!("history-{}", ix + 1), None, {
1339                                        let running_state = running_state.clone();
1340                                        move |_window, cx| {
1341                                            handler(Some(ix), running_state.clone(), cx);
1342                                        }
1343                                    });
1344                            }
1345
1346                            context_menu
1347                        },
1348                    ))
1349                }
1350            })
1351            .anchor(Anchor::TopRight)
1352    }
1353}
1354
1355async fn register_session_inner(
1356    this: &WeakEntity<DebugPanel>,
1357    session: Entity<Session>,
1358    cx: &mut AsyncWindowContext,
1359) -> Result<Entity<DebugSession>> {
1360    let adapter_name = session.read_with(cx, |session, _| session.adapter());
1361    this.update_in(cx, |_, window, cx| {
1362        cx.subscribe_in(
1363            &session,
1364            window,
1365            move |this, session, event: &SessionStateEvent, window, cx| match event {
1366                SessionStateEvent::Restart => {
1367                    this.handle_restart_request(session.clone(), window, cx);
1368                }
1369                SessionStateEvent::SpawnChildSession { request } => {
1370                    this.handle_start_debugging_request(request, session.clone(), window, cx);
1371                }
1372                _ => {}
1373            },
1374        )
1375        .detach();
1376    })
1377    .ok();
1378    let serialized_layout = this
1379        .update(cx, |_, cx| {
1380            persistence::get_serialized_layout(&adapter_name, &db::kvp::KeyValueStore::global(cx))
1381        })
1382        .ok()
1383        .flatten();
1384    let debug_session = this.update_in(cx, |this, window, cx| {
1385        let parent_session = this
1386            .sessions_with_children
1387            .keys()
1388            .find(|p| Some(p.read(cx).session_id(cx)) == session.read(cx).parent_id(cx))
1389            .cloned();
1390        this.retain_sessions(&|session: &Entity<DebugSession>| {
1391            !session
1392                .read(cx)
1393                .running_state()
1394                .read(cx)
1395                .session()
1396                .read(cx)
1397                .is_terminated()
1398        });
1399
1400        let debug_session = DebugSession::running(
1401            this.project.clone(),
1402            this.workspace.clone(),
1403            parent_session
1404                .as_ref()
1405                .map(|p| p.read(cx).running_state().read(cx).debug_terminal.clone()),
1406            session,
1407            serialized_layout,
1408            this.position(window, cx).axis(),
1409            window,
1410            cx,
1411        );
1412
1413        // We might want to make this an event subscription and only notify when a new thread is selected
1414        // This is used to filter the command menu correctly
1415        cx.observe(
1416            &debug_session.read(cx).running_state().clone(),
1417            |_, _, cx| cx.notify(),
1418        )
1419        .detach();
1420        let insert_position = this
1421            .sessions_with_children
1422            .keys()
1423            .position(|session| Some(session) == parent_session.as_ref())
1424            .map(|position| position + 1)
1425            .unwrap_or(this.sessions_with_children.len());
1426        // Maintain topological sort order of sessions
1427        let (_, old) = this.sessions_with_children.insert_before(
1428            insert_position,
1429            debug_session.clone(),
1430            Default::default(),
1431        );
1432        debug_assert!(old.is_none());
1433        if let Some(parent_session) = parent_session {
1434            this.sessions_with_children
1435                .entry(parent_session)
1436                .and_modify(|children| children.push(debug_session.downgrade()));
1437        }
1438
1439        debug_session
1440    })?;
1441    Ok(debug_session)
1442}
1443
1444impl EventEmitter<PanelEvent> for DebugPanel {}
1445
1446impl Focusable for DebugPanel {
1447    fn focus_handle(&self, _: &App) -> FocusHandle {
1448        self.focus_handle.clone()
1449    }
1450}
1451
1452impl Panel for DebugPanel {
1453    fn persistent_name() -> &'static str {
1454        "DebugPanel"
1455    }
1456
1457    fn panel_key() -> &'static str {
1458        DEBUG_PANEL_KEY
1459    }
1460
1461    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1462        DebuggerSettings::get_global(cx).dock.into()
1463    }
1464
1465    fn position_is_valid(&self, _: DockPosition) -> bool {
1466        true
1467    }
1468
1469    fn set_position(
1470        &mut self,
1471        position: DockPosition,
1472        window: &mut Window,
1473        cx: &mut Context<Self>,
1474    ) {
1475        if position.axis() != self.position(window, cx).axis() {
1476            self.sessions_with_children.keys().for_each(|session_item| {
1477                session_item.update(cx, |item, cx| {
1478                    item.running_state()
1479                        .update(cx, |state, cx| state.invert_axies(cx))
1480                })
1481            });
1482        }
1483
1484        settings::update_settings_file(self.fs.clone(), cx, move |settings, _| {
1485            settings.debugger.get_or_insert_default().dock = Some(position.into());
1486        });
1487    }
1488
1489    fn default_size(&self, _window: &Window, _: &App) -> Pixels {
1490        px(300.)
1491    }
1492
1493    fn remote_id() -> Option<proto::PanelId> {
1494        Some(proto::PanelId::DebugPanel)
1495    }
1496
1497    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
1498        DebuggerSettings::get_global(cx)
1499            .button
1500            .then_some(IconName::Debug)
1501    }
1502
1503    fn icon_tooltip(&self, _window: &Window, cx: &App) -> Option<&'static str> {
1504        if DebuggerSettings::get_global(cx).button {
1505            Some("Debug Panel")
1506        } else {
1507            None
1508        }
1509    }
1510
1511    fn toggle_action(&self) -> Box<dyn Action> {
1512        Box::new(ToggleFocus)
1513    }
1514
1515    fn pane(&self) -> Option<Entity<Pane>> {
1516        None
1517    }
1518
1519    fn activation_priority(&self) -> u32 {
1520        7
1521    }
1522
1523    fn hide_button_setting(&self, _: &App) -> Option<workspace::HideStatusItem> {
1524        Some(workspace::HideStatusItem::new(|settings| {
1525            settings.debugger.get_or_insert_default().button = Some(false);
1526        }))
1527    }
1528
1529    fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
1530
1531    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
1532        self.is_zoomed
1533    }
1534
1535    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
1536        self.is_zoomed = zoomed;
1537        cx.notify();
1538    }
1539}
1540
1541impl Render for DebugPanel {
1542    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1543        let this = cx.weak_entity();
1544
1545        if self
1546            .active_session
1547            .as_ref()
1548            .map(|session| session.read(cx).running_state())
1549            .map(|state| state.read(cx).has_open_context_menu(cx))
1550            .unwrap_or(false)
1551        {
1552            self.context_menu.take();
1553        }
1554
1555        v_flex()
1556            .size_full()
1557            .key_context("DebugPanel")
1558            .child(h_flex().children(self.top_controls_strip(window, cx)))
1559            .track_focus(&self.focus_handle(cx))
1560            .on_action({
1561                let this = this.clone();
1562                move |_: &workspace::ActivatePaneLeft, window, cx| {
1563                    this.update(cx, |this, cx| {
1564                        this.activate_pane_in_direction(SplitDirection::Left, window, cx);
1565                    })
1566                    .ok();
1567                }
1568            })
1569            .on_action({
1570                let this = this.clone();
1571                move |_: &workspace::ActivatePaneRight, window, cx| {
1572                    this.update(cx, |this, cx| {
1573                        this.activate_pane_in_direction(SplitDirection::Right, window, cx);
1574                    })
1575                    .ok();
1576                }
1577            })
1578            .on_action({
1579                let this = this.clone();
1580                move |_: &workspace::ActivatePaneUp, window, cx| {
1581                    this.update(cx, |this, cx| {
1582                        this.activate_pane_in_direction(SplitDirection::Up, window, cx);
1583                    })
1584                    .ok();
1585                }
1586            })
1587            .on_action({
1588                let this = this.clone();
1589                move |_: &workspace::ActivatePaneDown, window, cx| {
1590                    this.update(cx, |this, cx| {
1591                        this.activate_pane_in_direction(SplitDirection::Down, window, cx);
1592                    })
1593                    .ok();
1594                }
1595            })
1596            .on_action({
1597                let this = this.clone();
1598                move |_: &FocusConsole, window, cx| {
1599                    this.update(cx, |this, cx| {
1600                        this.activate_item(DebuggerPaneItem::Console, window, cx);
1601                    })
1602                    .ok();
1603                }
1604            })
1605            .on_action({
1606                let this = this.clone();
1607                move |_: &FocusVariables, window, cx| {
1608                    this.update(cx, |this, cx| {
1609                        this.activate_item(DebuggerPaneItem::Variables, window, cx);
1610                    })
1611                    .ok();
1612                }
1613            })
1614            .on_action({
1615                let this = this.clone();
1616                move |_: &FocusBreakpointList, window, cx| {
1617                    this.update(cx, |this, cx| {
1618                        this.activate_item(DebuggerPaneItem::BreakpointList, window, cx);
1619                    })
1620                    .ok();
1621                }
1622            })
1623            .on_action({
1624                let this = this.clone();
1625                move |_: &FocusFrames, window, cx| {
1626                    this.update(cx, |this, cx| {
1627                        this.activate_item(DebuggerPaneItem::Frames, window, cx);
1628                    })
1629                    .ok();
1630                }
1631            })
1632            .on_action({
1633                let this = this.clone();
1634                move |_: &FocusModules, window, cx| {
1635                    this.update(cx, |this, cx| {
1636                        this.activate_item(DebuggerPaneItem::Modules, window, cx);
1637                    })
1638                    .ok();
1639                }
1640            })
1641            .on_action({
1642                let this = this.clone();
1643                move |_: &FocusLoadedSources, window, cx| {
1644                    this.update(cx, |this, cx| {
1645                        this.activate_item(DebuggerPaneItem::LoadedSources, window, cx);
1646                    })
1647                    .ok();
1648                }
1649            })
1650            .on_action({
1651                let this = this.clone();
1652                move |_: &FocusTerminal, window, cx| {
1653                    this.update(cx, |this, cx| {
1654                        this.activate_item(DebuggerPaneItem::Terminal, window, cx);
1655                    })
1656                    .ok();
1657                }
1658            })
1659            .on_action({
1660                let this = this.clone();
1661                move |_: &ToggleThreadPicker, window, cx| {
1662                    this.update(cx, |this, cx| {
1663                        this.toggle_thread_picker(window, cx);
1664                    })
1665                    .ok();
1666                }
1667            })
1668            .on_action({
1669                move |_: &ToggleSessionPicker, window, cx| {
1670                    this.update(cx, |this, cx| {
1671                        this.toggle_session_picker(window, cx);
1672                    })
1673                    .ok();
1674                }
1675            })
1676            .on_action(cx.listener(Self::toggle_zoom))
1677            .on_action(cx.listener(|panel, _: &ToggleExpandItem, _, cx| {
1678                let Some(session) = panel.active_session() else {
1679                    return;
1680                };
1681                let active_pane = session
1682                    .read(cx)
1683                    .running_state()
1684                    .read(cx)
1685                    .active_pane()
1686                    .clone();
1687                active_pane.update(cx, |pane, cx| {
1688                    let is_zoomed = pane.is_zoomed();
1689                    pane.set_zoomed(!is_zoomed, cx);
1690                });
1691                cx.notify();
1692            }))
1693            .on_action(cx.listener(Self::copy_debug_adapter_arguments))
1694            .when(self.active_session.is_some(), |this| {
1695                this.on_mouse_down(
1696                    MouseButton::Right,
1697                    cx.listener(|this, event: &MouseDownEvent, window, cx| {
1698                        if this
1699                            .active_session
1700                            .as_ref()
1701                            .map(|session| {
1702                                let state = session.read(cx).running_state();
1703                                state.read(cx).has_pane_at_position(event.position)
1704                            })
1705                            .unwrap_or(false)
1706                        {
1707                            this.deploy_context_menu(event.position, window, cx);
1708                        }
1709                    }),
1710                )
1711                .children(self.context_menu.as_ref().map(|(menu, position, _)| {
1712                    deferred(
1713                        anchored()
1714                            .position(*position)
1715                            .anchor(gpui::Anchor::TopLeft)
1716                            .child(menu.clone()),
1717                    )
1718                    .with_priority(1)
1719                }))
1720            })
1721            .map(|this| {
1722                if let Some(active_session) = self.active_session.clone() {
1723                    this.child(active_session)
1724                } else {
1725                    let docked_to_bottom = self.position(window, cx) == DockPosition::Bottom;
1726
1727                    let welcome_experience = v_flex()
1728                        .when_else(
1729                            docked_to_bottom,
1730                            |this| this.w_2_3().h_full().pr_8(),
1731                            |this| this.w_full().h_1_3(),
1732                        )
1733                        .items_center()
1734                        .justify_center()
1735                        .gap_2()
1736                        .child(
1737                            Button::new("spawn-new-session-empty-state", "New Session")
1738                                .start_icon(
1739                                    Icon::new(IconName::Plus)
1740                                        .size(IconSize::Small)
1741                                        .color(Color::Muted),
1742                                )
1743                                .on_click(|_, window, cx| {
1744                                    window.dispatch_action(crate::Start.boxed_clone(), cx);
1745                                }),
1746                        )
1747                        .child(
1748                            Button::new("edit-debug-settings", "Edit debug.json")
1749                                .start_icon(
1750                                    Icon::new(IconName::Code)
1751                                        .size(IconSize::Small)
1752                                        .color(Color::Muted),
1753                                )
1754                                .on_click(|_, window, cx| {
1755                                    window.dispatch_action(
1756                                        zed_actions::OpenProjectDebugTasks.boxed_clone(),
1757                                        cx,
1758                                    );
1759                                }),
1760                        )
1761                        .child(
1762                            Button::new("open-debugger-docs", "Debugger Docs")
1763                                .start_icon(
1764                                    Icon::new(IconName::Book)
1765                                        .size(IconSize::Small)
1766                                        .color(Color::Muted),
1767                                )
1768                                .on_click(|_, _, cx| cx.open_url("https://zed.dev/docs/debugger")),
1769                        )
1770                        .child(
1771                            Button::new(
1772                                "spawn-new-session-install-extensions",
1773                                "Debugger Extensions",
1774                            )
1775                            .start_icon(
1776                                Icon::new(IconName::Blocks)
1777                                    .size(IconSize::Small)
1778                                    .color(Color::Muted),
1779                            )
1780                            .on_click(|_, window, cx| {
1781                                window.dispatch_action(
1782                                    zed_actions::Extensions {
1783                                        category_filter: Some(
1784                                            zed_actions::ExtensionCategoryFilter::DebugAdapters,
1785                                        ),
1786                                        id: None,
1787                                    }
1788                                    .boxed_clone(),
1789                                    cx,
1790                                );
1791                            }),
1792                        );
1793
1794                    let has_breakpoints = self
1795                        .project
1796                        .read(cx)
1797                        .breakpoint_store()
1798                        .read(cx)
1799                        .all_source_breakpoints(cx)
1800                        .values()
1801                        .any(|breakpoints| !breakpoints.is_empty());
1802
1803                    let breakpoint_list = v_flex()
1804                        .group("base-breakpoint-list")
1805                        .when_else(
1806                            docked_to_bottom,
1807                            |this| this.min_w_1_3().h_full(),
1808                            |this| this.size_full().h_2_3(),
1809                        )
1810                        .child(
1811                            h_flex()
1812                                .track_focus(&self.breakpoint_list.focus_handle(cx))
1813                                .h(Tab::container_height(cx))
1814                                .p_1p5()
1815                                .w_full()
1816                                .justify_between()
1817                                .border_b_1()
1818                                .border_color(cx.theme().colors().border_variant)
1819                                .child(Label::new("Breakpoints").size(LabelSize::Small))
1820                                .child(
1821                                    h_flex().visible_on_hover("base-breakpoint-list").child(
1822                                        self.breakpoint_list.read(cx).render_control_strip(),
1823                                    ),
1824                                ),
1825                        )
1826                        .when(has_breakpoints, |this| {
1827                            this.child(self.breakpoint_list.clone())
1828                        })
1829                        .when(!has_breakpoints, |this| {
1830                            this.child(
1831                                v_flex().size_full().items_center().justify_center().child(
1832                                    Label::new("No Breakpoints Set")
1833                                        .size(LabelSize::Small)
1834                                        .color(Color::Muted),
1835                                ),
1836                            )
1837                        });
1838
1839                    this.child(
1840                        v_flex()
1841                            .size_full()
1842                            .overflow_hidden()
1843                            .gap_1()
1844                            .items_center()
1845                            .justify_center()
1846                            .map(|this| {
1847                                if docked_to_bottom {
1848                                    this.child(
1849                                        h_flex()
1850                                            .size_full()
1851                                            .child(breakpoint_list)
1852                                            .child(Divider::vertical().h_full())
1853                                            .child(welcome_experience)
1854                                            .child(Divider::vertical().h_full()),
1855                                    )
1856                                } else {
1857                                    this.child(
1858                                        v_flex()
1859                                            .size_full()
1860                                            .child(welcome_experience)
1861                                            .child(Divider::horizontal())
1862                                            .child(breakpoint_list),
1863                                    )
1864                                }
1865                            }),
1866                    )
1867                }
1868            })
1869            .into_any()
1870    }
1871}
1872
1873struct DebuggerProvider(Entity<DebugPanel>);
1874
1875impl workspace::DebuggerProvider for DebuggerProvider {
1876    fn start_session(
1877        &self,
1878        definition: DebugScenario,
1879        context: SharedTaskContext,
1880        buffer: Option<Entity<Buffer>>,
1881        worktree_id: Option<WorktreeId>,
1882        window: &mut Window,
1883        cx: &mut App,
1884    ) {
1885        self.0.update(cx, |_, cx| {
1886            cx.defer_in(window, move |this, window, cx| {
1887                this.start_session(definition, context, buffer, worktree_id, window, cx);
1888            })
1889        })
1890    }
1891
1892    fn spawn_task_or_modal(
1893        &self,
1894        workspace: &mut Workspace,
1895        action: &tasks_ui::Spawn,
1896        window: &mut Window,
1897        cx: &mut Context<Workspace>,
1898    ) {
1899        spawn_task_or_modal(workspace, action, window, cx);
1900    }
1901
1902    fn debug_scenario_scheduled(&self, cx: &mut App) {
1903        self.0.update(cx, |this, _| {
1904            this.debug_scenario_scheduled_last = true;
1905        });
1906    }
1907
1908    fn task_scheduled(&self, cx: &mut App) {
1909        self.0.update(cx, |this, _| {
1910            this.debug_scenario_scheduled_last = false;
1911        })
1912    }
1913
1914    fn debug_scenario_scheduled_last(&self, cx: &App) -> bool {
1915        self.0.read(cx).debug_scenario_scheduled_last
1916    }
1917
1918    fn active_thread_state(&self, cx: &App) -> Option<ThreadStatus> {
1919        let session = self.0.read(cx).active_session()?;
1920        let thread = session.read(cx).running_state().read(cx).thread_id()?;
1921        session.read(cx).session(cx).read(cx).thread_state(thread)
1922    }
1923}
1924
Served at tenant.openagents/omega Member data and write actions are omitted.