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 running_state(&self) -> Entity<RunningState> {
108        match &self.mode {
109            DebugSessionState::Running(running_state) => running_state.clone(),
110        }
111    }
112
113    pub(crate) fn label(&self, cx: &App) -> SharedString {
114        if let Some(label) = self.label.get() {
115            return label.clone();
116        }
117
118        let session_id = match &self.mode {
119            DebugSessionState::Running(running_state) => running_state.read(cx).session_id(),
120        };
121
122        let Ok(Some(session)) = self
123            .dap_store
124            .read_with(cx, |store, _| store.session_by_id(session_id))
125        else {
126            return "".into();
127        };
128
129        self.label
130            .get_or_init(|| session.read(cx).label())
131            .to_owned()
132    }
133
134    pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
135        let label = self.label(cx);
136
137        let icon = match &self.mode {
138            DebugSessionState::Running(state) => {
139                if state.read(cx).session().read(cx).is_terminated() {
140                    Some(Indicator::dot().color(Color::Error))
141                } else {
142                    match state.read(cx).thread_status(cx).unwrap_or_default() {
143                        project::debugger::session::ThreadStatus::Stopped => {
144                            Some(Indicator::dot().color(Color::Conflict))
145                        }
146                        _ => Some(Indicator::dot().color(Color::Success)),
147                    }
148                }
149            }
150        };
151
152        h_flex()
153            .gap_2()
154            .when_some(icon, |this, indicator| this.child(indicator))
155            .justify_between()
156            .child(Label::new(label))
157            .into_any_element()
158    }
159}
160
161impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
162
163impl Focusable for DebugSession {
164    fn focus_handle(&self, cx: &App) -> FocusHandle {
165        match &self.mode {
166            DebugSessionState::Running(running_state) => running_state.focus_handle(cx),
167        }
168    }
169}
170
171impl Item for DebugSession {
172    type Event = DebugPanelItemEvent;
173}
174
175impl FollowableItem for DebugSession {
176    fn remote_id(&self) -> Option<workspace::ViewId> {
177        self.remote_id
178    }
179
180    fn to_state_proto(&self, _window: &Window, _cx: &App) -> Option<proto::view::Variant> {
181        None
182    }
183
184    fn from_state_proto(
185        _workspace: Entity<Workspace>,
186        _remote_id: ViewId,
187        _state: &mut Option<proto::view::Variant>,
188        _window: &mut Window,
189        _cx: &mut App,
190    ) -> Option<gpui::Task<gpui::Result<Entity<Self>>>> {
191        None
192    }
193
194    fn add_event_to_update_proto(
195        &self,
196        _event: &Self::Event,
197        _update: &mut Option<proto::update_view::Variant>,
198        _window: &Window,
199        _cx: &App,
200    ) -> bool {
201        // update.get_or_insert_with(|| proto::update_view::Variant::DebugPanel(Default::default()));
202
203        true
204    }
205
206    fn apply_update_proto(
207        &mut self,
208        _project: &Entity<project::Project>,
209        _message: proto::update_view::Variant,
210        _window: &mut Window,
211        _cx: &mut Context<Self>,
212    ) -> gpui::Task<gpui::Result<()>> {
213        Task::ready(Ok(()))
214    }
215
216    fn set_leader_peer_id(
217        &mut self,
218        _leader_peer_id: Option<PeerId>,
219        _window: &mut Window,
220        _cx: &mut Context<Self>,
221    ) {
222    }
223
224    fn to_follow_event(_event: &Self::Event) -> Option<workspace::item::FollowEvent> {
225        None
226    }
227
228    fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<workspace::item::Dedup> {
229        if existing.session_id(cx) == self.session_id(cx) {
230            Some(item::Dedup::KeepExisting)
231        } else {
232            None
233        }
234    }
235
236    fn is_project_item(&self, _window: &Window, _cx: &App) -> bool {
237        true
238    }
239}
240
241impl Render for DebugSession {
242    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
243        match &self.mode {
244            DebugSessionState::Running(running_state) => {
245                running_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
246            }
247        }
248    }
249}