Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T06:48:16.128Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

module_list.rs

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