module_list.rs

  1use anyhow::anyhow;
  2use gpui::{
  3    list, AnyElement, Empty, Entity, FocusHandle, Focusable, ListState, Subscription, WeakEntity,
  4};
  5use project::{
  6    debugger::session::{Session, SessionEvent},
  7    ProjectItem as _, ProjectPath,
  8};
  9use std::{path::Path, sync::Arc};
 10use ui::prelude::*;
 11use util::maybe;
 12use workspace::Workspace;
 13
 14pub struct ModuleList {
 15    list: ListState,
 16    invalidate: bool,
 17    session: Entity<Session>,
 18    workspace: WeakEntity<Workspace>,
 19    focus_handle: FocusHandle,
 20    _subscription: Subscription,
 21}
 22
 23impl ModuleList {
 24    pub fn new(
 25        session: Entity<Session>,
 26        workspace: WeakEntity<Workspace>,
 27        cx: &mut Context<Self>,
 28    ) -> Self {
 29        let weak_entity = cx.weak_entity();
 30        let focus_handle = cx.focus_handle();
 31
 32        let list = ListState::new(
 33            0,
 34            gpui::ListAlignment::Top,
 35            px(1000.),
 36            move |ix, _window, cx| {
 37                weak_entity
 38                    .upgrade()
 39                    .map(|module_list| module_list.update(cx, |this, cx| this.render_entry(ix, cx)))
 40                    .unwrap_or(div().into_any())
 41            },
 42        );
 43
 44        let _subscription = cx.subscribe(&session, |this, _, event, cx| match event {
 45            SessionEvent::Stopped(_) | SessionEvent::Modules => {
 46                this.invalidate = true;
 47                cx.notify();
 48            }
 49            _ => {}
 50        });
 51
 52        Self {
 53            list,
 54            session,
 55            workspace,
 56            focus_handle,
 57            _subscription,
 58            invalidate: true,
 59        }
 60    }
 61
 62    fn open_module(&mut self, path: Arc<Path>, window: &mut Window, cx: &mut Context<Self>) {
 63        cx.spawn_in(window, async move |this, cx| {
 64            let (worktree, relative_path) = this
 65                .update(cx, |this, cx| {
 66                    this.workspace.update(cx, |workspace, cx| {
 67                        workspace.project().update(cx, |this, cx| {
 68                            this.find_or_create_worktree(&path, false, cx)
 69                        })
 70                    })
 71                })??
 72                .await?;
 73
 74            let buffer = this
 75                .update(cx, |this, cx| {
 76                    this.workspace.update(cx, |this, cx| {
 77                        this.project().update(cx, |this, cx| {
 78                            let worktree_id = worktree.read(cx).id();
 79                            this.open_buffer(
 80                                ProjectPath {
 81                                    worktree_id,
 82                                    path: relative_path.into(),
 83                                },
 84                                cx,
 85                            )
 86                        })
 87                    })
 88                })??
 89                .await?;
 90
 91            this.update_in(cx, |this, window, cx| {
 92                this.workspace.update(cx, |workspace, cx| {
 93                    let project_path = buffer.read(cx).project_path(cx).ok_or_else(|| {
 94                        anyhow!("Could not select a stack frame for unnamed buffer")
 95                    })?;
 96                    anyhow::Ok(workspace.open_path_preview(
 97                        project_path,
 98                        None,
 99                        false,
100                        true,
101                        true,
102                        window,
103                        cx,
104                    ))
105                })
106            })???
107            .await?;
108
109            anyhow::Ok(())
110        })
111        .detach_and_log_err(cx);
112    }
113
114    fn render_entry(&mut self, ix: usize, cx: &mut Context<Self>) -> AnyElement {
115        let Some(module) = maybe!({
116            self.session
117                .update(cx, |state, cx| state.modules(cx).get(ix).cloned())
118        }) else {
119            return Empty.into_any();
120        };
121
122        v_flex()
123            .rounded_md()
124            .w_full()
125            .group("")
126            .id(("module-list", ix))
127            .when(module.path.is_some(), |this| {
128                this.on_click({
129                    let path = module.path.as_deref().map(|path| Arc::<Path>::from(Path::new(path)));
130                    cx.listener(move |this, _, window, cx| {
131                        if let Some(path) = path.as_ref() {
132                            this.open_module(path.clone(), window, cx);
133                        } else {
134                            log::error!("Wasn't able to find module path, but was still able to click on module list entry");
135                        }
136                    })
137                })
138            })
139            .p_1()
140            .hover(|s| s.bg(cx.theme().colors().element_hover))
141            .child(h_flex().gap_0p5().text_ui_sm(cx).child(module.name.clone()))
142            .child(
143                h_flex()
144                    .text_ui_xs(cx)
145                    .text_color(cx.theme().colors().text_muted)
146                    .when_some(module.path.clone(), |this, path| this.child(path)),
147            )
148            .into_any()
149    }
150}
151
152#[cfg(any(test, feature = "test-support"))]
153impl ModuleList {
154    pub fn modules(&self, cx: &mut Context<Self>) -> Vec<dap::Module> {
155        self.session
156            .update(cx, |session, cx| session.modules(cx).to_vec())
157    }
158}
159
160impl Focusable for ModuleList {
161    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {
162        self.focus_handle.clone()
163    }
164}
165
166impl Render for ModuleList {
167    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
168        if self.invalidate {
169            let len = self
170                .session
171                .update(cx, |session, cx| session.modules(cx).len());
172            self.list.reset(len);
173            self.invalidate = false;
174            cx.notify();
175        }
176
177        div()
178            .track_focus(&self.focus_handle)
179            .size_full()
180            .p_1()
181            .child(list(self.list.clone()).size_full())
182    }
183}