module_list.rs

  1use anyhow::anyhow;
  2use dap::Module;
  3use gpui::{
  4    AnyElement, Entity, FocusHandle, Focusable, ScrollStrategy, Subscription, Task,
  5    UniformListScrollHandle, WeakEntity, uniform_list,
  6};
  7use project::{
  8    ProjectItem as _, ProjectPath,
  9    debugger::session::{Session, SessionEvent},
 10};
 11use std::{ops::Range, path::Path, sync::Arc};
 12use ui::{WithScrollbar, prelude::*};
 13use workspace::Workspace;
 14
 15pub struct ModuleList {
 16    scroll_handle: UniformListScrollHandle,
 17    selected_ix: Option<usize>,
 18    session: Entity<Session>,
 19    workspace: WeakEntity<Workspace>,
 20    focus_handle: FocusHandle,
 21    entries: Vec<Module>,
 22    _rebuild_task: Option<Task<()>>,
 23    _subscription: Subscription,
 24}
 25
 26impl ModuleList {
 27    pub fn new(
 28        session: Entity<Session>,
 29        workspace: WeakEntity<Workspace>,
 30        cx: &mut Context<Self>,
 31    ) -> Self {
 32        let focus_handle = cx.focus_handle();
 33
 34        let _subscription = cx.subscribe(&session, |this, _, event, cx| match event {
 35            SessionEvent::Stopped(_) | SessionEvent::Modules => {
 36                if this._rebuild_task.is_some() {
 37                    this.schedule_rebuild(cx);
 38                }
 39            }
 40            _ => {}
 41        });
 42
 43        let scroll_handle = UniformListScrollHandle::new();
 44
 45        Self {
 46            scroll_handle,
 47            session,
 48            workspace,
 49            focus_handle,
 50            entries: Vec::new(),
 51            selected_ix: None,
 52            _subscription,
 53            _rebuild_task: None,
 54        }
 55    }
 56
 57    fn schedule_rebuild(&mut self, cx: &mut Context<Self>) {
 58        self._rebuild_task = Some(cx.spawn(async move |this, cx| {
 59            this.update(cx, |this, cx| {
 60                let modules = this
 61                    .session
 62                    .update(cx, |session, cx| session.modules(cx).to_owned());
 63                this.entries = modules;
 64                cx.notify();
 65            })
 66            .ok();
 67        }));
 68    }
 69
 70    fn open_module(&mut self, path: Arc<Path>, window: &mut Window, cx: &mut Context<Self>) {
 71        cx.spawn_in(window, async move |this, cx| {
 72            let (worktree, relative_path) = this
 73                .update(cx, |this, cx| {
 74                    this.workspace.update(cx, |workspace, cx| {
 75                        workspace.project().update(cx, |this, cx| {
 76                            this.find_or_create_worktree(&path, false, cx)
 77                        })
 78                    })
 79                })??
 80                .await?;
 81
 82            let buffer = this
 83                .update(cx, |this, cx| {
 84                    this.workspace.update(cx, |this, cx| {
 85                        this.project().update(cx, |this, cx| {
 86                            let worktree_id = worktree.read(cx).id();
 87                            this.open_buffer(
 88                                ProjectPath {
 89                                    worktree_id,
 90                                    path: relative_path,
 91                                },
 92                                cx,
 93                            )
 94                        })
 95                    })
 96                })??
 97                .await?;
 98
 99            this.update_in(cx, |this, window, cx| {
100                this.workspace.update(cx, |workspace, cx| {
101                    let project_path = buffer.read(cx).project_path(cx).ok_or_else(|| {
102                        anyhow!("Could not select a stack frame for unnamed buffer")
103                    })?;
104                    anyhow::Ok(workspace.open_path_preview(
105                        project_path,
106                        None,
107                        false,
108                        true,
109                        true,
110                        window,
111                        cx,
112                    ))
113                })
114            })???
115            .await?;
116
117            anyhow::Ok(())
118        })
119        .detach();
120    }
121
122    fn render_entry(&mut self, ix: usize, cx: &mut Context<Self>) -> AnyElement {
123        let module = self.entries[ix].clone();
124
125        v_flex()
126            .rounded_md()
127            .w_full()
128            .group("")
129            .id(("module-list", ix))
130            .on_any_mouse_down(|_, _, cx| {
131                cx.stop_propagation();
132            })
133            .when(module.path.is_some(), |this| {
134                this.on_click({
135                    let path = module
136                        .path
137                        .as_deref()
138                        .map(|path| Arc::<Path>::from(Path::new(path)));
139                    cx.listener(move |this, _, window, cx| {
140                        this.selected_ix = Some(ix);
141                        if let Some(path) = path.as_ref() {
142                            this.open_module(path.clone(), window, cx);
143                        }
144                        cx.notify();
145                    })
146                })
147            })
148            .p_1()
149            .hover(|s| s.bg(cx.theme().colors().element_hover))
150            .when(Some(ix) == self.selected_ix, |s| {
151                s.bg(cx.theme().colors().element_hover)
152            })
153            .child(h_flex().gap_0p5().text_ui_sm(cx).child(module.name.clone()))
154            .child(
155                h_flex()
156                    .text_ui_xs(cx)
157                    .text_color(cx.theme().colors().text_muted)
158                    .when_some(module.path, |this, path| this.child(path)),
159            )
160            .into_any()
161    }
162
163    #[cfg(test)]
164    pub(crate) fn modules(&self, cx: &mut Context<Self>) -> Vec<dap::Module> {
165        self.session
166            .update(cx, |session, cx| session.modules(cx).to_vec())
167    }
168
169    fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
170        let Some(ix) = self.selected_ix else { return };
171        let Some(entry) = self.entries.get(ix) else {
172            return;
173        };
174        let Some(path) = entry.path.as_deref() else {
175            return;
176        };
177        let path = Arc::from(Path::new(path));
178        self.open_module(path, window, cx);
179    }
180
181    fn select_ix(&mut self, ix: Option<usize>, cx: &mut Context<Self>) {
182        self.selected_ix = ix;
183        if let Some(ix) = ix {
184            self.scroll_handle
185                .scroll_to_item(ix, ScrollStrategy::Center);
186        }
187        cx.notify();
188    }
189
190    fn select_next(&mut self, _: &menu::SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
191        let ix = match self.selected_ix {
192            _ if self.entries.is_empty() => None,
193            None => Some(0),
194            Some(ix) => {
195                if ix == self.entries.len() - 1 {
196                    Some(0)
197                } else {
198                    Some(ix + 1)
199                }
200            }
201        };
202        self.select_ix(ix, cx);
203    }
204
205    fn select_previous(
206        &mut self,
207        _: &menu::SelectPrevious,
208        _window: &mut Window,
209        cx: &mut Context<Self>,
210    ) {
211        let ix = match self.selected_ix {
212            _ if self.entries.is_empty() => None,
213            None => Some(self.entries.len() - 1),
214            Some(ix) => {
215                if ix == 0 {
216                    Some(self.entries.len() - 1)
217                } else {
218                    Some(ix - 1)
219                }
220            }
221        };
222        self.select_ix(ix, cx);
223    }
224
225    fn select_first(
226        &mut self,
227        _: &menu::SelectFirst,
228        _window: &mut Window,
229        cx: &mut Context<Self>,
230    ) {
231        let ix = if !self.entries.is_empty() {
232            Some(0)
233        } else {
234            None
235        };
236        self.select_ix(ix, cx);
237    }
238
239    fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
240        let ix = if !self.entries.is_empty() {
241            Some(self.entries.len() - 1)
242        } else {
243            None
244        };
245        self.select_ix(ix, cx);
246    }
247
248    fn render_list(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
249        uniform_list(
250            "module-list",
251            self.entries.len(),
252            cx.processor(|this, range: Range<usize>, _window, cx| {
253                range.map(|ix| this.render_entry(ix, cx)).collect()
254            }),
255        )
256        .track_scroll(self.scroll_handle.clone())
257        .size_full()
258    }
259}
260
261impl Focusable for ModuleList {
262    fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {
263        self.focus_handle.clone()
264    }
265}
266
267impl Render for ModuleList {
268    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
269        if self._rebuild_task.is_none() {
270            self.schedule_rebuild(cx);
271        }
272        div()
273            .track_focus(&self.focus_handle)
274            .on_action(cx.listener(Self::select_last))
275            .on_action(cx.listener(Self::select_first))
276            .on_action(cx.listener(Self::select_next))
277            .on_action(cx.listener(Self::select_previous))
278            .on_action(cx.listener(Self::confirm))
279            .size_full()
280            .p_1()
281            .child(self.render_list(window, cx))
282            .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
283    }
284}