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