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