1use std::path::Path;
2use std::sync::Arc;
3use std::time::Duration;
4
5use anyhow::{Context as _, Result, anyhow};
6use dap::StackFrameId;
7use gpui::{
8 AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState, MouseButton,
9 Stateful, Subscription, Task, WeakEntity, list,
10};
11use util::debug_panic;
12
13use crate::StackTraceView;
14use language::PointUtf16;
15use project::debugger::breakpoint_store::ActiveStackFrame;
16use project::debugger::session::{Session, SessionEvent, StackFrame};
17use project::{ProjectItem, ProjectPath};
18use ui::{Scrollbar, ScrollbarState, Tooltip, prelude::*};
19use workspace::{ItemHandle, Workspace};
20
21use super::RunningState;
22
23#[derive(Debug)]
24pub enum StackFrameListEvent {
25 SelectedStackFrameChanged(StackFrameId),
26 BuiltEntries,
27}
28
29pub struct StackFrameList {
30 focus_handle: FocusHandle,
31 _subscription: Subscription,
32 session: Entity<Session>,
33 state: WeakEntity<RunningState>,
34 entries: Vec<StackFrameEntry>,
35 workspace: WeakEntity<Workspace>,
36 selected_ix: Option<usize>,
37 opened_stack_frame_id: Option<StackFrameId>,
38 scrollbar_state: ScrollbarState,
39 list_state: ListState,
40 error: Option<SharedString>,
41 _refresh_task: Task<()>,
42}
43
44#[derive(Debug, PartialEq, Eq)]
45pub enum StackFrameEntry {
46 Normal(dap::StackFrame),
47 /// Used to indicate that the frame is artificial and is a visual label or separator
48 Label(dap::StackFrame),
49 Collapsed(Vec<dap::StackFrame>),
50}
51
52impl StackFrameList {
53 pub fn new(
54 workspace: WeakEntity<Workspace>,
55 session: Entity<Session>,
56 state: WeakEntity<RunningState>,
57 window: &mut Window,
58 cx: &mut Context<Self>,
59 ) -> Self {
60 let focus_handle = cx.focus_handle();
61
62 let _subscription =
63 cx.subscribe_in(&session, window, |this, _, event, window, cx| match event {
64 SessionEvent::Threads => {
65 this.schedule_refresh(false, window, cx);
66 }
67 SessionEvent::Stopped(..) | SessionEvent::StackTrace => {
68 this.schedule_refresh(true, window, cx);
69 }
70 _ => {}
71 });
72
73 let list_state = ListState::new(0, gpui::ListAlignment::Top, px(1000.), {
74 let this = cx.weak_entity();
75 move |ix, _window, cx| {
76 this.update(cx, |this, cx| this.render_entry(ix, cx))
77 .unwrap_or(div().into_any())
78 }
79 });
80 let scrollbar_state = ScrollbarState::new(list_state.clone());
81
82 let mut this = Self {
83 session,
84 workspace,
85 focus_handle,
86 state,
87 _subscription,
88 entries: Default::default(),
89 error: None,
90 selected_ix: None,
91 opened_stack_frame_id: None,
92 list_state,
93 scrollbar_state,
94 _refresh_task: Task::ready(()),
95 };
96 this.schedule_refresh(true, window, cx);
97 this
98 }
99
100 #[cfg(test)]
101 pub(crate) fn entries(&self) -> &Vec<StackFrameEntry> {
102 &self.entries
103 }
104
105 pub(crate) fn flatten_entries(
106 &self,
107 show_collapsed: bool,
108 show_labels: bool,
109 ) -> Vec<dap::StackFrame> {
110 self.entries
111 .iter()
112 .flat_map(|frame| match frame {
113 StackFrameEntry::Normal(frame) => vec![frame.clone()],
114 StackFrameEntry::Label(frame) if show_labels => vec![frame.clone()],
115 StackFrameEntry::Collapsed(frames) if show_collapsed => frames.clone(),
116 _ => vec![],
117 })
118 .collect::<Vec<_>>()
119 }
120
121 fn stack_frames(&self, cx: &mut App) -> Result<Vec<StackFrame>> {
122 if let Ok(Some(thread_id)) = self.state.read_with(cx, |state, _| state.thread_id) {
123 self.session
124 .update(cx, |this, cx| this.stack_frames(thread_id, cx))
125 } else {
126 Ok(Vec::default())
127 }
128 }
129
130 #[cfg(test)]
131 pub(crate) fn dap_stack_frames(&self, cx: &mut App) -> Vec<dap::StackFrame> {
132 self.stack_frames(cx)
133 .unwrap_or_default()
134 .into_iter()
135 .map(|stack_frame| stack_frame.dap.clone())
136 .collect()
137 }
138
139 pub fn opened_stack_frame_id(&self) -> Option<StackFrameId> {
140 self.opened_stack_frame_id
141 }
142
143 pub(super) fn schedule_refresh(
144 &mut self,
145 select_first: bool,
146 window: &mut Window,
147 cx: &mut Context<Self>,
148 ) {
149 const REFRESH_DEBOUNCE: Duration = Duration::from_millis(20);
150
151 self._refresh_task = cx.spawn_in(window, async move |this, cx| {
152 let debounce = this
153 .update(cx, |this, cx| {
154 let new_stack_frames = this.stack_frames(cx);
155 new_stack_frames.unwrap_or_default().is_empty() && !this.entries.is_empty()
156 })
157 .ok()
158 .unwrap_or_default();
159
160 if debounce {
161 cx.background_executor().timer(REFRESH_DEBOUNCE).await;
162 }
163 this.update_in(cx, |this, window, cx| {
164 this.build_entries(select_first, window, cx);
165 cx.notify();
166 })
167 .ok();
168 })
169 }
170
171 pub fn build_entries(
172 &mut self,
173 open_first_stack_frame: bool,
174 window: &mut Window,
175 cx: &mut Context<Self>,
176 ) {
177 let old_selected_frame_id = self
178 .selected_ix
179 .and_then(|ix| self.entries.get(ix))
180 .and_then(|entry| match entry {
181 StackFrameEntry::Normal(stack_frame) => Some(stack_frame.id),
182 StackFrameEntry::Collapsed(_) | StackFrameEntry::Label(_) => None,
183 });
184 let mut entries = Vec::new();
185 let mut collapsed_entries = Vec::new();
186 let mut first_stack_frame = None;
187 let mut first_stack_frame_with_path = None;
188
189 let stack_frames = match self.stack_frames(cx) {
190 Ok(stack_frames) => stack_frames,
191 Err(e) => {
192 self.error = Some(format!("{}", e).into());
193 self.entries.clear();
194 self.selected_ix = None;
195 self.list_state.reset(0);
196 cx.emit(StackFrameListEvent::BuiltEntries);
197 cx.notify();
198 return;
199 }
200 };
201 for stack_frame in &stack_frames {
202 match stack_frame.dap.presentation_hint {
203 Some(dap::StackFramePresentationHint::Deemphasize)
204 | Some(dap::StackFramePresentationHint::Subtle) => {
205 collapsed_entries.push(stack_frame.dap.clone());
206 }
207 Some(dap::StackFramePresentationHint::Label) => {
208 entries.push(StackFrameEntry::Label(stack_frame.dap.clone()));
209 }
210 _ => {
211 let collapsed_entries = std::mem::take(&mut collapsed_entries);
212 if !collapsed_entries.is_empty() {
213 entries.push(StackFrameEntry::Collapsed(collapsed_entries.clone()));
214 }
215
216 first_stack_frame.get_or_insert(entries.len());
217
218 if stack_frame
219 .dap
220 .source
221 .as_ref()
222 .is_some_and(|source| source.path.is_some())
223 {
224 first_stack_frame_with_path.get_or_insert(entries.len());
225 }
226 entries.push(StackFrameEntry::Normal(stack_frame.dap.clone()));
227 }
228 }
229 }
230
231 let collapsed_entries = std::mem::take(&mut collapsed_entries);
232 if !collapsed_entries.is_empty() {
233 entries.push(StackFrameEntry::Collapsed(collapsed_entries.clone()));
234 }
235 self.entries = entries;
236
237 if let Some(ix) = first_stack_frame_with_path
238 .or(first_stack_frame)
239 .filter(|_| open_first_stack_frame)
240 {
241 self.select_ix(Some(ix), cx);
242 self.activate_selected_entry(window, cx);
243 } else if let Some(old_selected_frame_id) = old_selected_frame_id {
244 let ix = self.entries.iter().position(|entry| match entry {
245 StackFrameEntry::Normal(frame) => frame.id == old_selected_frame_id,
246 StackFrameEntry::Collapsed(_) | StackFrameEntry::Label(_) => false,
247 });
248 self.selected_ix = ix;
249 }
250
251 self.list_state.reset(self.entries.len());
252 cx.emit(StackFrameListEvent::BuiltEntries);
253 cx.notify();
254 }
255
256 pub fn go_to_stack_frame(
257 &mut self,
258 stack_frame_id: StackFrameId,
259 window: &mut Window,
260 cx: &mut Context<Self>,
261 ) -> Task<Result<()>> {
262 let Some(stack_frame) = self
263 .entries
264 .iter()
265 .flat_map(|entry| match entry {
266 StackFrameEntry::Label(stack_frame) => std::slice::from_ref(stack_frame),
267 StackFrameEntry::Normal(stack_frame) => std::slice::from_ref(stack_frame),
268 StackFrameEntry::Collapsed(stack_frames) => stack_frames.as_slice(),
269 })
270 .find(|stack_frame| stack_frame.id == stack_frame_id)
271 .cloned()
272 else {
273 return Task::ready(Err(anyhow!("No stack frame for ID")));
274 };
275 self.go_to_stack_frame_inner(stack_frame, window, cx)
276 }
277
278 fn go_to_stack_frame_inner(
279 &mut self,
280 stack_frame: dap::StackFrame,
281 window: &mut Window,
282 cx: &mut Context<Self>,
283 ) -> Task<Result<()>> {
284 let stack_frame_id = stack_frame.id;
285 self.opened_stack_frame_id = Some(stack_frame_id);
286 let Some(abs_path) = Self::abs_path_from_stack_frame(&stack_frame) else {
287 return Task::ready(Err(anyhow!("Project path not found")));
288 };
289 let row = stack_frame.line.saturating_sub(1) as u32;
290 cx.emit(StackFrameListEvent::SelectedStackFrameChanged(
291 stack_frame_id,
292 ));
293 cx.spawn_in(window, async move |this, cx| {
294 let (worktree, relative_path) = this
295 .update(cx, |this, cx| {
296 this.workspace.update(cx, |workspace, cx| {
297 workspace.project().update(cx, |this, cx| {
298 this.find_or_create_worktree(&abs_path, false, cx)
299 })
300 })
301 })??
302 .await?;
303 let buffer = this
304 .update(cx, |this, cx| {
305 this.workspace.update(cx, |this, cx| {
306 this.project().update(cx, |this, cx| {
307 let worktree_id = worktree.read(cx).id();
308 this.open_buffer(
309 ProjectPath {
310 worktree_id,
311 path: relative_path.into(),
312 },
313 cx,
314 )
315 })
316 })
317 })??
318 .await?;
319 let position = buffer.read_with(cx, |this, _| {
320 this.snapshot().anchor_after(PointUtf16::new(row, 0))
321 })?;
322 this.update_in(cx, |this, window, cx| {
323 this.workspace.update(cx, |workspace, cx| {
324 let project_path = buffer
325 .read(cx)
326 .project_path(cx)
327 .context("Could not select a stack frame for unnamed buffer")?;
328
329 let open_preview = !workspace
330 .item_of_type::<StackTraceView>(cx)
331 .map(|viewer| {
332 workspace
333 .active_item(cx)
334 .is_some_and(|item| item.item_id() == viewer.item_id())
335 })
336 .unwrap_or_default();
337
338 anyhow::Ok(workspace.open_path_preview(
339 project_path,
340 None,
341 true,
342 true,
343 open_preview,
344 window,
345 cx,
346 ))
347 })
348 })???
349 .await?;
350
351 this.update(cx, |this, cx| {
352 let thread_id = this.state.read_with(cx, |state, _| {
353 state.thread_id.context("No selected thread ID found")
354 })??;
355
356 this.workspace.update(cx, |workspace, cx| {
357 let breakpoint_store = workspace.project().read(cx).breakpoint_store();
358
359 breakpoint_store.update(cx, |store, cx| {
360 store.set_active_position(
361 ActiveStackFrame {
362 session_id: this.session.read(cx).session_id(),
363 thread_id,
364 stack_frame_id,
365 path: abs_path,
366 position,
367 },
368 cx,
369 );
370 })
371 })
372 })?
373 })
374 }
375
376 pub(crate) fn abs_path_from_stack_frame(stack_frame: &dap::StackFrame) -> Option<Arc<Path>> {
377 stack_frame.source.as_ref().and_then(|s| {
378 s.path
379 .as_deref()
380 .map(|path| Arc::<Path>::from(Path::new(path)))
381 .filter(|path| path.is_absolute())
382 })
383 }
384
385 pub fn restart_stack_frame(&mut self, stack_frame_id: u64, cx: &mut Context<Self>) {
386 self.session.update(cx, |state, cx| {
387 state.restart_stack_frame(stack_frame_id, cx)
388 });
389 }
390
391 fn render_label_entry(
392 &self,
393 stack_frame: &dap::StackFrame,
394 _cx: &mut Context<Self>,
395 ) -> AnyElement {
396 h_flex()
397 .rounded_md()
398 .justify_between()
399 .w_full()
400 .group("")
401 .id(("label-stack-frame", stack_frame.id))
402 .p_1()
403 .on_any_mouse_down(|_, _, cx| {
404 cx.stop_propagation();
405 })
406 .child(
407 v_flex().justify_center().gap_0p5().child(
408 Label::new(stack_frame.name.clone())
409 .size(LabelSize::Small)
410 .weight(FontWeight::BOLD)
411 .truncate()
412 .color(Color::Info),
413 ),
414 )
415 .into_any()
416 }
417
418 fn render_normal_entry(
419 &self,
420 ix: usize,
421 stack_frame: &dap::StackFrame,
422 cx: &mut Context<Self>,
423 ) -> AnyElement {
424 let source = stack_frame.source.clone();
425 let is_selected_frame = Some(ix) == self.selected_ix;
426
427 let path = source.clone().and_then(|s| s.path.or(s.name));
428 let formatted_path = path.map(|path| format!("{}:{}", path, stack_frame.line,));
429 let formatted_path = formatted_path.map(|path| {
430 Label::new(path)
431 .size(LabelSize::XSmall)
432 .line_height_style(LineHeightStyle::UiLabel)
433 .truncate()
434 .color(Color::Muted)
435 });
436
437 let supports_frame_restart = self
438 .session
439 .read(cx)
440 .capabilities()
441 .supports_restart_frame
442 .unwrap_or_default();
443
444 let should_deemphasize = matches!(
445 stack_frame.presentation_hint,
446 Some(
447 dap::StackFramePresentationHint::Subtle
448 | dap::StackFramePresentationHint::Deemphasize
449 )
450 );
451 h_flex()
452 .rounded_md()
453 .justify_between()
454 .w_full()
455 .group("")
456 .id(("stack-frame", stack_frame.id))
457 .p_1()
458 .when(is_selected_frame, |this| {
459 this.bg(cx.theme().colors().element_hover)
460 })
461 .on_any_mouse_down(|_, _, cx| {
462 cx.stop_propagation();
463 })
464 .on_click(cx.listener(move |this, _, window, cx| {
465 this.selected_ix = Some(ix);
466 this.activate_selected_entry(window, cx);
467 }))
468 .hover(|style| style.bg(cx.theme().colors().element_hover).cursor_pointer())
469 .child(
470 v_flex()
471 .gap_0p5()
472 .child(
473 Label::new(stack_frame.name.clone())
474 .size(LabelSize::Small)
475 .truncate()
476 .when(should_deemphasize, |this| this.color(Color::Muted)),
477 )
478 .children(formatted_path),
479 )
480 .when(
481 supports_frame_restart && stack_frame.can_restart.unwrap_or(true),
482 |this| {
483 this.child(
484 h_flex()
485 .id(("restart-stack-frame", stack_frame.id))
486 .visible_on_hover("")
487 .absolute()
488 .right_2()
489 .overflow_hidden()
490 .rounded_md()
491 .border_1()
492 .border_color(cx.theme().colors().element_selected)
493 .bg(cx.theme().colors().element_background)
494 .hover(|style| {
495 style
496 .bg(cx.theme().colors().ghost_element_hover)
497 .cursor_pointer()
498 })
499 .child(
500 IconButton::new(
501 ("restart-stack-frame", stack_frame.id),
502 IconName::DebugRestart,
503 )
504 .icon_size(IconSize::Small)
505 .on_click(cx.listener({
506 let stack_frame_id = stack_frame.id;
507 move |this, _, _window, cx| {
508 this.restart_stack_frame(stack_frame_id, cx);
509 }
510 }))
511 .tooltip(move |window, cx| {
512 Tooltip::text("Restart Stack Frame")(window, cx)
513 }),
514 ),
515 )
516 },
517 )
518 .into_any()
519 }
520
521 pub(crate) fn expand_collapsed_entry(&mut self, ix: usize, cx: &mut Context<Self>) {
522 let Some(StackFrameEntry::Collapsed(stack_frames)) = self.entries.get_mut(ix) else {
523 return;
524 };
525 let entries = std::mem::take(stack_frames)
526 .into_iter()
527 .map(StackFrameEntry::Normal);
528 self.entries.splice(ix..ix + 1, entries);
529 self.selected_ix = Some(ix);
530 self.list_state.reset(self.entries.len());
531 cx.emit(StackFrameListEvent::BuiltEntries);
532 cx.notify();
533 }
534
535 fn render_collapsed_entry(
536 &self,
537 ix: usize,
538 stack_frames: &Vec<dap::StackFrame>,
539 cx: &mut Context<Self>,
540 ) -> AnyElement {
541 let first_stack_frame = &stack_frames[0];
542 let is_selected = Some(ix) == self.selected_ix;
543
544 h_flex()
545 .rounded_md()
546 .justify_between()
547 .w_full()
548 .group("")
549 .id(("stack-frame", first_stack_frame.id))
550 .p_1()
551 .when(is_selected, |this| {
552 this.bg(cx.theme().colors().element_hover)
553 })
554 .on_any_mouse_down(|_, _, cx| {
555 cx.stop_propagation();
556 })
557 .on_click(cx.listener(move |this, _, window, cx| {
558 this.selected_ix = Some(ix);
559 this.activate_selected_entry(window, cx);
560 }))
561 .hover(|style| style.bg(cx.theme().colors().element_hover).cursor_pointer())
562 .child(
563 v_flex()
564 .text_ui_sm(cx)
565 .truncate()
566 .text_color(cx.theme().colors().text_muted)
567 .child(format!(
568 "Show {} more{}",
569 stack_frames.len(),
570 first_stack_frame
571 .source
572 .as_ref()
573 .and_then(|source| source.origin.as_ref())
574 .map_or(String::new(), |origin| format!(": {}", origin))
575 )),
576 )
577 .into_any()
578 }
579
580 fn render_entry(&self, ix: usize, cx: &mut Context<Self>) -> AnyElement {
581 match &self.entries[ix] {
582 StackFrameEntry::Label(stack_frame) => self.render_label_entry(stack_frame, cx),
583 StackFrameEntry::Normal(stack_frame) => self.render_normal_entry(ix, stack_frame, cx),
584 StackFrameEntry::Collapsed(stack_frames) => {
585 self.render_collapsed_entry(ix, stack_frames, cx)
586 }
587 }
588 }
589
590 fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
591 div()
592 .occlude()
593 .id("stack-frame-list-vertical-scrollbar")
594 .on_mouse_move(cx.listener(|_, _, _, cx| {
595 cx.notify();
596 cx.stop_propagation()
597 }))
598 .on_hover(|_, _, cx| {
599 cx.stop_propagation();
600 })
601 .on_any_mouse_down(|_, _, cx| {
602 cx.stop_propagation();
603 })
604 .on_mouse_up(
605 MouseButton::Left,
606 cx.listener(|_, _, _, cx| {
607 cx.stop_propagation();
608 }),
609 )
610 .on_scroll_wheel(cx.listener(|_, _, _, cx| {
611 cx.notify();
612 }))
613 .h_full()
614 .absolute()
615 .right_1()
616 .top_1()
617 .bottom_0()
618 .w(px(12.))
619 .cursor_default()
620 .children(Scrollbar::vertical(self.scrollbar_state.clone()))
621 }
622
623 fn select_ix(&mut self, ix: Option<usize>, cx: &mut Context<Self>) {
624 self.selected_ix = ix;
625 cx.notify();
626 }
627
628 fn select_next(&mut self, _: &menu::SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
629 let ix = match self.selected_ix {
630 _ if self.entries.len() == 0 => None,
631 None => Some(0),
632 Some(ix) => {
633 if ix == self.entries.len() - 1 {
634 Some(0)
635 } else {
636 Some(ix + 1)
637 }
638 }
639 };
640 self.select_ix(ix, cx);
641 }
642
643 fn select_previous(
644 &mut self,
645 _: &menu::SelectPrevious,
646 _window: &mut Window,
647 cx: &mut Context<Self>,
648 ) {
649 let ix = match self.selected_ix {
650 _ if self.entries.len() == 0 => None,
651 None => Some(self.entries.len() - 1),
652 Some(ix) => {
653 if ix == 0 {
654 Some(self.entries.len() - 1)
655 } else {
656 Some(ix - 1)
657 }
658 }
659 };
660 self.select_ix(ix, cx);
661 }
662
663 fn select_first(
664 &mut self,
665 _: &menu::SelectFirst,
666 _window: &mut Window,
667 cx: &mut Context<Self>,
668 ) {
669 let ix = if self.entries.len() > 0 {
670 Some(0)
671 } else {
672 None
673 };
674 self.select_ix(ix, cx);
675 }
676
677 fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
678 let ix = if self.entries.len() > 0 {
679 Some(self.entries.len() - 1)
680 } else {
681 None
682 };
683 self.select_ix(ix, cx);
684 }
685
686 fn activate_selected_entry(&mut self, window: &mut Window, cx: &mut Context<Self>) {
687 let Some(ix) = self.selected_ix else {
688 return;
689 };
690 let Some(entry) = self.entries.get_mut(ix) else {
691 return;
692 };
693 match entry {
694 StackFrameEntry::Normal(stack_frame) => {
695 let stack_frame = stack_frame.clone();
696 self.go_to_stack_frame_inner(stack_frame, window, cx)
697 .detach_and_log_err(cx)
698 }
699 StackFrameEntry::Label(_) => {
700 debug_panic!("You should not be able to select a label stack frame")
701 }
702 StackFrameEntry::Collapsed(_) => self.expand_collapsed_entry(ix, cx),
703 }
704 cx.notify();
705 }
706
707 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
708 self.activate_selected_entry(window, cx);
709 }
710
711 fn render_list(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
712 div()
713 .p_1()
714 .size_full()
715 .child(list(self.list_state.clone()).size_full())
716 }
717}
718
719impl Render for StackFrameList {
720 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
721 div()
722 .track_focus(&self.focus_handle)
723 .size_full()
724 .on_action(cx.listener(Self::select_next))
725 .on_action(cx.listener(Self::select_previous))
726 .on_action(cx.listener(Self::select_first))
727 .on_action(cx.listener(Self::select_last))
728 .on_action(cx.listener(Self::confirm))
729 .when_some(self.error.clone(), |el, error| {
730 el.child(
731 h_flex()
732 .bg(cx.theme().status().warning_background)
733 .border_b_1()
734 .border_color(cx.theme().status().warning_border)
735 .pl_1()
736 .child(Icon::new(IconName::Warning).color(Color::Warning))
737 .gap_2()
738 .child(
739 Label::new(error)
740 .size(LabelSize::Small)
741 .color(Color::Warning),
742 ),
743 )
744 })
745 .child(self.render_list(window, cx))
746 .child(self.render_vertical_scrollbar(cx))
747 }
748}
749
750impl Focusable for StackFrameList {
751 fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {
752 self.focus_handle.clone()
753 }
754}
755
756impl EventEmitter<StackFrameListEvent> for StackFrameList {}