session.rs

  1pub mod running;
  2
  3use std::sync::OnceLock;
  4
  5use dap::client::SessionId;
  6use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity};
  7use project::Project;
  8use project::debugger::{dap_store::DapStore, session::Session};
  9use project::worktree_store::WorktreeStore;
 10use rpc::proto::{self, PeerId};
 11use running::RunningState;
 12use ui::{Indicator, prelude::*};
 13use workspace::{
 14    FollowableItem, ViewId, Workspace,
 15    item::{self, Item},
 16};
 17
 18use crate::debugger_panel::DebugPanel;
 19use crate::persistence::SerializedPaneLayout;
 20
 21pub(crate) enum DebugSessionState {
 22    Running(Entity<running::RunningState>),
 23}
 24
 25impl DebugSessionState {
 26    pub(crate) fn as_running(&self) -> Option<&Entity<running::RunningState>> {
 27        match &self {
 28            DebugSessionState::Running(entity) => Some(entity),
 29        }
 30    }
 31}
 32
 33pub struct DebugSession {
 34    remote_id: Option<workspace::ViewId>,
 35    mode: DebugSessionState,
 36    label: OnceLock<SharedString>,
 37    dap_store: WeakEntity<DapStore>,
 38    _debug_panel: WeakEntity<DebugPanel>,
 39    _worktree_store: WeakEntity<WorktreeStore>,
 40    _workspace: WeakEntity<Workspace>,
 41    _subscriptions: [Subscription; 1],
 42}
 43
 44#[derive(Debug)]
 45pub enum DebugPanelItemEvent {
 46    Close,
 47    Stopped { go_to_stack_frame: bool },
 48}
 49
 50impl DebugSession {
 51    pub(crate) fn running(
 52        project: Entity<Project>,
 53        workspace: WeakEntity<Workspace>,
 54        session: Entity<Session>,
 55        _debug_panel: WeakEntity<DebugPanel>,
 56        serialized_pane_layout: Option<SerializedPaneLayout>,
 57        window: &mut Window,
 58        cx: &mut App,
 59    ) -> Entity<Self> {
 60        let mode = cx.new(|cx| {
 61            RunningState::new(
 62                session.clone(),
 63                project.clone(),
 64                workspace.clone(),
 65                serialized_pane_layout,
 66                window,
 67                cx,
 68            )
 69        });
 70
 71        cx.new(|cx| Self {
 72            _subscriptions: [cx.subscribe(&mode, |_, _, _, cx| {
 73                cx.notify();
 74            })],
 75            remote_id: None,
 76            mode: DebugSessionState::Running(mode),
 77            label: OnceLock::new(),
 78            dap_store: project.read(cx).dap_store().downgrade(),
 79            _debug_panel,
 80            _worktree_store: project.read(cx).worktree_store().downgrade(),
 81            _workspace: workspace,
 82        })
 83    }
 84
 85    pub(crate) fn session_id(&self, cx: &App) -> SessionId {
 86        match &self.mode {
 87            DebugSessionState::Running(entity) => entity.read(cx).session_id(),
 88        }
 89    }
 90
 91    pub fn session(&self, cx: &App) -> Entity<Session> {
 92        match &self.mode {
 93            DebugSessionState::Running(entity) => entity.read(cx).session().clone(),
 94        }
 95    }
 96
 97    pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
 98        match &self.mode {
 99            DebugSessionState::Running(state) => state.update(cx, |state, cx| state.shutdown(cx)),
100        }
101    }
102
103    pub(crate) fn mode(&self) -> &DebugSessionState {
104        &self.mode
105    }
106
107    pub(crate) fn label(&self, cx: &App) -> SharedString {
108        if let Some(label) = self.label.get() {
109            return label.clone();
110        }
111
112        let session_id = match &self.mode {
113            DebugSessionState::Running(running_state) => running_state.read(cx).session_id(),
114        };
115
116        let Ok(Some(session)) = self
117            .dap_store
118            .read_with(cx, |store, _| store.session_by_id(session_id))
119        else {
120            return "".into();
121        };
122
123        self.label
124            .get_or_init(|| session.read(cx).label())
125            .to_owned()
126    }
127
128    #[allow(unused)]
129    pub(crate) fn running_state(&self) -> Entity<RunningState> {
130        match &self.mode {
131            DebugSessionState::Running(running_state) => running_state.clone(),
132        }
133    }
134
135    pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
136        let label = self.label(cx);
137
138        let icon = match &self.mode {
139            DebugSessionState::Running(state) => {
140                if state.read(cx).session().read(cx).is_terminated() {
141                    Some(Indicator::dot().color(Color::Error))
142                } else {
143                    match state.read(cx).thread_status(cx).unwrap_or_default() {
144                        project::debugger::session::ThreadStatus::Stopped => {
145                            Some(Indicator::dot().color(Color::Conflict))
146                        }
147                        _ => Some(Indicator::dot().color(Color::Success)),
148                    }
149                }
150            }
151        };
152
153        h_flex()
154            .gap_2()
155            .when_some(icon, |this, indicator| this.child(indicator))
156            .justify_between()
157            .child(Label::new(label))
158            .into_any_element()
159    }
160}
161
162impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
163
164impl Focusable for DebugSession {
165    fn focus_handle(&self, cx: &App) -> FocusHandle {
166        match &self.mode {
167            DebugSessionState::Running(running_state) => running_state.focus_handle(cx),
168        }
169    }
170}
171
172impl Item for DebugSession {
173    type Event = DebugPanelItemEvent;
174    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
175        "Debugger".into()
176    }
177}
178
179impl FollowableItem for DebugSession {
180    fn remote_id(&self) -> Option<workspace::ViewId> {
181        self.remote_id
182    }
183
184    fn to_state_proto(&self, _window: &Window, _cx: &App) -> Option<proto::view::Variant> {
185        None
186    }
187
188    fn from_state_proto(
189        _workspace: Entity<Workspace>,
190        _remote_id: ViewId,
191        _state: &mut Option<proto::view::Variant>,
192        _window: &mut Window,
193        _cx: &mut App,
194    ) -> Option<gpui::Task<gpui::Result<Entity<Self>>>> {
195        None
196    }
197
198    fn add_event_to_update_proto(
199        &self,
200        _event: &Self::Event,
201        _update: &mut Option<proto::update_view::Variant>,
202        _window: &Window,
203        _cx: &App,
204    ) -> bool {
205        // update.get_or_insert_with(|| proto::update_view::Variant::DebugPanel(Default::default()));
206
207        true
208    }
209
210    fn apply_update_proto(
211        &mut self,
212        _project: &Entity<project::Project>,
213        _message: proto::update_view::Variant,
214        _window: &mut Window,
215        _cx: &mut Context<Self>,
216    ) -> gpui::Task<gpui::Result<()>> {
217        Task::ready(Ok(()))
218    }
219
220    fn set_leader_peer_id(
221        &mut self,
222        _leader_peer_id: Option<PeerId>,
223        _window: &mut Window,
224        _cx: &mut Context<Self>,
225    ) {
226    }
227
228    fn to_follow_event(_event: &Self::Event) -> Option<workspace::item::FollowEvent> {
229        None
230    }
231
232    fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<workspace::item::Dedup> {
233        if existing.session_id(cx) == self.session_id(cx) {
234            Some(item::Dedup::KeepExisting)
235        } else {
236            None
237        }
238    }
239
240    fn is_project_item(&self, _window: &Window, _cx: &App) -> bool {
241        true
242    }
243}
244
245impl Render for DebugSession {
246    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
247        match &self.mode {
248            DebugSessionState::Running(running_state) => {
249                running_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
250            }
251        }
252    }
253}