session.rs

  1mod failed;
  2mod inert;
  3pub mod running;
  4mod starting;
  5
  6use std::time::Duration;
  7
  8use dap::client::SessionId;
  9use failed::FailedState;
 10use gpui::{
 11    percentage, Animation, AnimationExt, AnyElement, App, Entity, EventEmitter, FocusHandle,
 12    Focusable, Subscription, Task, Transformation, WeakEntity,
 13};
 14use inert::{InertEvent, InertState};
 15use project::debugger::{dap_store::DapStore, session::Session};
 16use project::worktree_store::WorktreeStore;
 17use project::Project;
 18use rpc::proto::{self, PeerId};
 19use running::RunningState;
 20use starting::{StartingEvent, StartingState};
 21use ui::prelude::*;
 22use workspace::{
 23    item::{self, Item},
 24    FollowableItem, ViewId, Workspace,
 25};
 26
 27pub(crate) enum DebugSessionState {
 28    Inert(Entity<InertState>),
 29    Starting(Entity<StartingState>),
 30    Failed(Entity<FailedState>),
 31    Running(Entity<running::RunningState>),
 32}
 33
 34impl DebugSessionState {
 35    pub(crate) fn as_running(&self) -> Option<&Entity<running::RunningState>> {
 36        match &self {
 37            DebugSessionState::Running(entity) => Some(entity),
 38            _ => None,
 39        }
 40    }
 41}
 42
 43pub struct DebugSession {
 44    remote_id: Option<workspace::ViewId>,
 45    mode: DebugSessionState,
 46    dap_store: WeakEntity<DapStore>,
 47    worktree_store: WeakEntity<WorktreeStore>,
 48    workspace: WeakEntity<Workspace>,
 49    _subscriptions: [Subscription; 1],
 50}
 51
 52#[derive(Debug)]
 53pub enum DebugPanelItemEvent {
 54    Close,
 55    Stopped { go_to_stack_frame: bool },
 56}
 57
 58#[derive(Clone, Copy, PartialEq, Eq, Debug)]
 59pub enum ThreadItem {
 60    Console,
 61    LoadedSource,
 62    Modules,
 63    Variables,
 64}
 65
 66impl DebugSession {
 67    pub(super) fn inert(
 68        project: Entity<Project>,
 69        workspace: WeakEntity<Workspace>,
 70        window: &mut Window,
 71        cx: &mut App,
 72    ) -> Entity<Self> {
 73        let default_cwd = project
 74            .read(cx)
 75            .worktrees(cx)
 76            .next()
 77            .and_then(|tree| tree.read(cx).abs_path().to_str().map(|str| str.to_string()))
 78            .unwrap_or_default();
 79
 80        let inert = cx.new(|cx| InertState::new(workspace.clone(), &default_cwd, window, cx));
 81
 82        let project = project.read(cx);
 83        let dap_store = project.dap_store().downgrade();
 84        let worktree_store = project.worktree_store().downgrade();
 85        cx.new(|cx| {
 86            let _subscriptions = [cx.subscribe_in(&inert, window, Self::on_inert_event)];
 87            Self {
 88                remote_id: None,
 89                mode: DebugSessionState::Inert(inert),
 90                dap_store,
 91                worktree_store,
 92                workspace,
 93                _subscriptions,
 94            }
 95        })
 96    }
 97
 98    pub(crate) fn running(
 99        project: Entity<Project>,
100        workspace: WeakEntity<Workspace>,
101        session: Entity<Session>,
102        window: &mut Window,
103        cx: &mut App,
104    ) -> Entity<Self> {
105        let mode = cx.new(|cx| RunningState::new(session.clone(), workspace.clone(), window, cx));
106
107        cx.new(|cx| Self {
108            _subscriptions: [cx.subscribe(&mode, |_, _, _, cx| {
109                cx.notify();
110            })],
111            remote_id: None,
112            mode: DebugSessionState::Running(mode),
113            dap_store: project.read(cx).dap_store().downgrade(),
114            worktree_store: project.read(cx).worktree_store().downgrade(),
115            workspace,
116        })
117    }
118
119    pub(crate) fn session_id(&self, cx: &App) -> Option<SessionId> {
120        match &self.mode {
121            DebugSessionState::Inert(_) => None,
122            DebugSessionState::Starting(entity) => Some(entity.read(cx).session_id),
123            DebugSessionState::Failed(_) => None,
124            DebugSessionState::Running(entity) => Some(entity.read(cx).session_id()),
125        }
126    }
127
128    pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
129        match &self.mode {
130            DebugSessionState::Inert(_) => {}
131            DebugSessionState::Starting(_entity) => {} // todo(debugger): we need to shutdown the starting process in this case (or recreate it on a breakpoint being hit)
132            DebugSessionState::Failed(_) => {}
133            DebugSessionState::Running(state) => state.update(cx, |state, cx| state.shutdown(cx)),
134        }
135    }
136
137    pub(crate) fn mode(&self) -> &DebugSessionState {
138        &self.mode
139    }
140
141    fn on_inert_event(
142        &mut self,
143        _: &Entity<InertState>,
144        event: &InertEvent,
145        window: &mut Window,
146        cx: &mut Context<'_, Self>,
147    ) {
148        let dap_store = self.dap_store.clone();
149        let InertEvent::Spawned { config } = event;
150        let config = config.clone();
151        let worktree = self
152            .worktree_store
153            .update(cx, |this, _| this.worktrees().next())
154            .ok()
155            .flatten()
156            .expect("worktree-less project");
157        let Ok((new_session_id, task)) = dap_store.update(cx, |store, cx| {
158            store.new_session(config, &worktree, None, cx)
159        }) else {
160            return;
161        };
162        let starting = cx.new(|cx| StartingState::new(new_session_id, task, cx));
163
164        self._subscriptions = [cx.subscribe_in(&starting, window, Self::on_starting_event)];
165        self.mode = DebugSessionState::Starting(starting);
166    }
167
168    fn on_starting_event(
169        &mut self,
170        _: &Entity<StartingState>,
171        event: &StartingEvent,
172        window: &mut Window,
173        cx: &mut Context<'_, Self>,
174    ) {
175        if let StartingEvent::Finished(session) = event {
176            let mode =
177                cx.new(|cx| RunningState::new(session.clone(), self.workspace.clone(), window, cx));
178            self.mode = DebugSessionState::Running(mode);
179        } else if let StartingEvent::Failed = event {
180            self.mode = DebugSessionState::Failed(cx.new(FailedState::new));
181        };
182        cx.notify();
183    }
184}
185impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
186
187impl Focusable for DebugSession {
188    fn focus_handle(&self, cx: &App) -> FocusHandle {
189        match &self.mode {
190            DebugSessionState::Inert(inert_state) => inert_state.focus_handle(cx),
191            DebugSessionState::Starting(starting_state) => starting_state.focus_handle(cx),
192            DebugSessionState::Failed(failed_state) => failed_state.focus_handle(cx),
193            DebugSessionState::Running(running_state) => running_state.focus_handle(cx),
194        }
195    }
196}
197
198impl Item for DebugSession {
199    type Event = DebugPanelItemEvent;
200    fn tab_content(&self, _: item::TabContentParams, _: &Window, cx: &App) -> AnyElement {
201        let (label, color) = match &self.mode {
202            DebugSessionState::Inert(_) => ("New Session", Color::Default),
203            DebugSessionState::Starting(_) => ("Starting", Color::Default),
204            DebugSessionState::Failed(_) => ("Failed", Color::Error),
205            DebugSessionState::Running(state) => (
206                state
207                    .read_with(cx, |state, cx| state.thread_status(cx))
208                    .map(|status| status.label())
209                    .unwrap_or("Running"),
210                Color::Default,
211            ),
212        };
213
214        let is_starting = matches!(self.mode, DebugSessionState::Starting(_));
215
216        h_flex()
217            .gap_1()
218            .children(is_starting.then(|| {
219                Icon::new(IconName::ArrowCircle).with_animation(
220                    "starting-debug-session",
221                    Animation::new(Duration::from_secs(2)).repeat(),
222                    |this, delta| this.transform(Transformation::rotate(percentage(delta))),
223                )
224            }))
225            .child(Label::new(label).color(color))
226            .into_any_element()
227    }
228}
229
230impl FollowableItem for DebugSession {
231    fn remote_id(&self) -> Option<workspace::ViewId> {
232        self.remote_id
233    }
234
235    fn to_state_proto(&self, _window: &Window, _cx: &App) -> Option<proto::view::Variant> {
236        None
237    }
238
239    fn from_state_proto(
240        _workspace: Entity<Workspace>,
241        _remote_id: ViewId,
242        _state: &mut Option<proto::view::Variant>,
243        _window: &mut Window,
244        _cx: &mut App,
245    ) -> Option<gpui::Task<gpui::Result<Entity<Self>>>> {
246        None
247    }
248
249    fn add_event_to_update_proto(
250        &self,
251        _event: &Self::Event,
252        _update: &mut Option<proto::update_view::Variant>,
253        _window: &Window,
254        _cx: &App,
255    ) -> bool {
256        // update.get_or_insert_with(|| proto::update_view::Variant::DebugPanel(Default::default()));
257
258        true
259    }
260
261    fn apply_update_proto(
262        &mut self,
263        _project: &Entity<project::Project>,
264        _message: proto::update_view::Variant,
265        _window: &mut Window,
266        _cx: &mut Context<Self>,
267    ) -> gpui::Task<gpui::Result<()>> {
268        Task::ready(Ok(()))
269    }
270
271    fn set_leader_peer_id(
272        &mut self,
273        _leader_peer_id: Option<PeerId>,
274        _window: &mut Window,
275        _cx: &mut Context<Self>,
276    ) {
277    }
278
279    fn to_follow_event(_event: &Self::Event) -> Option<workspace::item::FollowEvent> {
280        None
281    }
282
283    fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<workspace::item::Dedup> {
284        if existing.session_id(cx) == self.session_id(cx) {
285            Some(item::Dedup::KeepExisting)
286        } else {
287            None
288        }
289    }
290
291    fn is_project_item(&self, _window: &Window, _cx: &App) -> bool {
292        true
293    }
294}
295
296impl Render for DebugSession {
297    fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
298        match &self.mode {
299            DebugSessionState::Inert(inert_state) => {
300                inert_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
301            }
302            DebugSessionState::Starting(starting_state) => {
303                starting_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
304            }
305            DebugSessionState::Failed(failed_state) => {
306                failed_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
307            }
308            DebugSessionState::Running(running_state) => {
309                running_state.update(cx, |this, cx| this.render(window, cx).into_any_element())
310            }
311        }
312    }
313}