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