dropdown_menus.rs

  1use std::time::Duration;
  2
  3use gpui::{Animation, AnimationExt as _, Entity, Transformation, percentage};
  4use project::debugger::session::{ThreadId, ThreadStatus};
  5use ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
  6
  7use crate::{
  8    debugger_panel::DebugPanel,
  9    session::{DebugSession, running::RunningState},
 10};
 11
 12impl DebugPanel {
 13    fn dropdown_label(label: impl Into<SharedString>) -> Label {
 14        Label::new(label).size(LabelSize::Small)
 15    }
 16
 17    pub fn render_session_menu(
 18        &mut self,
 19        active_session: Option<Entity<DebugSession>>,
 20        running_state: Option<Entity<RunningState>>,
 21        window: &mut Window,
 22        cx: &mut Context<Self>,
 23    ) -> Option<impl IntoElement> {
 24        if let Some(running_state) = running_state {
 25            let sessions = self.sessions().clone();
 26            let weak = cx.weak_entity();
 27            let running_state = running_state.read(cx);
 28            let label = if let Some(active_session) = active_session.clone() {
 29                active_session.read(cx).session(cx).read(cx).label()
 30            } else {
 31                SharedString::new_static("Unknown Session")
 32            };
 33
 34            let is_terminated = running_state.session().read(cx).is_terminated();
 35            let is_started = active_session
 36                .is_some_and(|session| session.read(cx).session(cx).read(cx).is_started());
 37
 38            let session_state_indicator = if is_terminated {
 39                Indicator::dot().color(Color::Error).into_any_element()
 40            } else if !is_started {
 41                Icon::new(IconName::ArrowCircle)
 42                    .size(IconSize::Small)
 43                    .color(Color::Muted)
 44                    .with_animation(
 45                        "arrow-circle",
 46                        Animation::new(Duration::from_secs(2)).repeat(),
 47                        |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
 48                    )
 49                    .into_any_element()
 50            } else {
 51                match running_state.thread_status(cx).unwrap_or_default() {
 52                    ThreadStatus::Stopped => {
 53                        Indicator::dot().color(Color::Conflict).into_any_element()
 54                    }
 55                    _ => Indicator::dot().color(Color::Success).into_any_element(),
 56                }
 57            };
 58
 59            let trigger = h_flex()
 60                .gap_2()
 61                .child(session_state_indicator)
 62                .justify_between()
 63                .child(
 64                    DebugPanel::dropdown_label(label)
 65                        .when(is_terminated, |this| this.strikethrough()),
 66                )
 67                .into_any_element();
 68
 69            Some(
 70                DropdownMenu::new_with_element(
 71                    "debugger-session-list",
 72                    trigger,
 73                    ContextMenu::build(window, cx, move |mut this, _, cx| {
 74                        let context_menu = cx.weak_entity();
 75                        for session in sessions.into_iter() {
 76                            let weak_session = session.downgrade();
 77                            let weak_session_id = weak_session.entity_id();
 78
 79                            this = this.custom_entry(
 80                                {
 81                                    let weak = weak.clone();
 82                                    let context_menu = context_menu.clone();
 83                                    move |_, cx| {
 84                                        weak_session
 85                                            .read_with(cx, |session, cx| {
 86                                                let context_menu = context_menu.clone();
 87                                                let id: SharedString = format!(
 88                                                    "debug-session-{}",
 89                                                    session.session_id(cx).0
 90                                                )
 91                                                .into();
 92                                                h_flex()
 93                                                    .w_full()
 94                                                    .group(id.clone())
 95                                                    .justify_between()
 96                                                    .child(session.label_element(cx))
 97                                                    .child(
 98                                                        IconButton::new(
 99                                                            "close-debug-session",
100                                                            IconName::Close,
101                                                        )
102                                                        .visible_on_hover(id.clone())
103                                                        .icon_size(IconSize::Small)
104                                                        .on_click({
105                                                            let weak = weak.clone();
106                                                            move |_, window, cx| {
107                                                                weak.update(cx, |panel, cx| {
108                                                                    panel.close_session(
109                                                                        weak_session_id,
110                                                                        window,
111                                                                        cx,
112                                                                    );
113                                                                })
114                                                                .ok();
115                                                                context_menu
116                                                                    .update(cx, |this, cx| {
117                                                                        this.cancel(
118                                                                            &Default::default(),
119                                                                            window,
120                                                                            cx,
121                                                                        );
122                                                                    })
123                                                                    .ok();
124                                                            }
125                                                        }),
126                                                    )
127                                                    .into_any_element()
128                                            })
129                                            .unwrap_or_else(|_| div().into_any_element())
130                                    }
131                                },
132                                {
133                                    let weak = weak.clone();
134                                    move |window, cx| {
135                                        weak.update(cx, |panel, cx| {
136                                            panel.activate_session(session.clone(), window, cx);
137                                        })
138                                        .ok();
139                                    }
140                                },
141                            );
142                        }
143                        this
144                    }),
145                )
146                .style(DropdownStyle::Ghost)
147                .handle(self.session_picker_menu_handle.clone()),
148            )
149        } else {
150            None
151        }
152    }
153
154    pub(crate) fn render_thread_dropdown(
155        &self,
156        running_state: &Entity<RunningState>,
157        threads: Vec<(dap::Thread, ThreadStatus)>,
158        window: &mut Window,
159        cx: &mut Context<Self>,
160    ) -> Option<DropdownMenu> {
161        let running_state = running_state.clone();
162        let running_state_read = running_state.read(cx);
163        let thread_id = running_state_read.thread_id();
164        let session = running_state_read.session();
165        let session_id = session.read(cx).session_id();
166        let session_terminated = session.read(cx).is_terminated();
167        let selected_thread_name = threads
168            .iter()
169            .find(|(thread, _)| thread_id.map(|id| id.0) == Some(thread.id))
170            .map(|(thread, _)| {
171                thread
172                    .name
173                    .is_empty()
174                    .then(|| format!("Tid: {}", thread.id))
175                    .unwrap_or_else(|| thread.name.clone())
176            });
177
178        if let Some(selected_thread_name) = selected_thread_name {
179            let trigger = DebugPanel::dropdown_label(selected_thread_name).into_any_element();
180            Some(
181                DropdownMenu::new_with_element(
182                    ("thread-list", session_id.0),
183                    trigger,
184                    ContextMenu::build(window, cx, move |mut this, _, _| {
185                        for (thread, _) in threads {
186                            let running_state = running_state.clone();
187                            let thread_id = thread.id;
188                            let entry_name = thread
189                                .name
190                                .is_empty()
191                                .then(|| format!("Tid: {}", thread.id))
192                                .unwrap_or_else(|| thread.name);
193
194                            this = this.entry(entry_name, None, move |window, cx| {
195                                running_state.update(cx, |running_state, cx| {
196                                    running_state.select_thread(ThreadId(thread_id), window, cx);
197                                });
198                            });
199                        }
200                        this
201                    }),
202                )
203                .disabled(session_terminated)
204                .style(DropdownStyle::Ghost)
205                .handle(self.thread_picker_menu_handle.clone()),
206            )
207        } else {
208            None
209        }
210    }
211}