Make loading spinner scrollable with the rest of the thread

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
Danilo Leal 2025-08-25 14:39:59 -03:00
parent 15701e61d1
commit e7abe34581

View file

@ -1528,12 +1528,11 @@ impl AcpThreadView {
return primary; return primary;
}; };
let is_generating = matches!(thread.read(cx).status(), ThreadStatus::Generating); let primary = if entry_ix == total_entries - 1 {
let primary = if entry_ix == total_entries - 1 && !is_generating {
v_flex() v_flex()
.w_full() .w_full()
.child(primary) .child(primary)
.child(self.render_thread_controls(cx)) .child(self.render_thread_controls(&thread, cx))
.when_some( .when_some(
self.thread_feedback.comments_editor.clone(), self.thread_feedback.comments_editor.clone(),
|this, editor| this.child(Self::render_feedback_feedback_editor(editor, cx)), |this, editor| this.child(Self::render_feedback_feedback_editor(editor, cx)),
@ -4160,7 +4159,20 @@ impl AcpThreadView {
} }
} }
fn render_thread_controls(&self, cx: &Context<Self>) -> impl IntoElement { fn render_thread_controls(
&self,
thread: &Entity<AcpThread>,
cx: &Context<Self>,
) -> impl IntoElement {
let is_generating = matches!(thread.read(cx).status(), ThreadStatus::Generating);
if is_generating {
return h_flex().id("thread-controls-container").ml_1().child(
div()
.py_2()
.px(rems_from_px(22.))
.child(SpinnerLabel::new().size(LabelSize::Small)),
);
}
let open_as_markdown = IconButton::new("open-as-markdown", IconName::FileMarkdown) let open_as_markdown = IconButton::new("open-as-markdown", IconName::FileMarkdown)
.shape(ui::IconButtonShape::Square) .shape(ui::IconButtonShape::Square)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -4884,45 +4896,30 @@ impl Render for AcpThreadView {
.items_center() .items_center()
.justify_end() .justify_end()
.child(self.render_load_error(e, cx)), .child(self.render_load_error(e, cx)),
ThreadState::Ready { thread, .. } => { ThreadState::Ready { .. } => v_flex().flex_1().map(|this| {
let thread_clone = thread.clone(); if has_messages {
this.child(
v_flex().flex_1().map(|this| { list(
if has_messages { self.list_state.clone(),
this.child( cx.processor(|this, index: usize, window, cx| {
list( let Some((entry, len)) = this.thread().and_then(|thread| {
self.list_state.clone(), let entries = &thread.read(cx).entries();
cx.processor(|this, index: usize, window, cx| { Some((entries.get(index)?, entries.len()))
let Some((entry, len)) = this.thread().and_then(|thread| { }) else {
let entries = &thread.read(cx).entries(); return Empty.into_any();
Some((entries.get(index)?, entries.len())) };
}) else { this.render_entry(index, len, entry, window, cx)
return Empty.into_any(); }),
};
this.render_entry(index, len, entry, window, cx)
}),
)
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
.flex_grow()
.into_any(),
) )
.child(self.render_vertical_scrollbar(cx)) .with_sizing_behavior(gpui::ListSizingBehavior::Auto)
.children( .flex_grow()
match thread_clone.read(cx).status() { .into_any(),
ThreadStatus::Idle )
| ThreadStatus::WaitingForToolConfirmation => None, .child(self.render_vertical_scrollbar(cx))
ThreadStatus::Generating => div() } else {
.py_2() this.child(self.render_recent_history(window, cx))
.px(rems_from_px(22.)) }
.child(SpinnerLabel::new().size(LabelSize::Small)) }),
.into(),
},
)
} else {
this.child(self.render_recent_history(window, cx))
}
})
}
}) })
// The activity bar is intentionally rendered outside of the ThreadState::Ready match // The activity bar is intentionally rendered outside of the ThreadState::Ready match
// above so that the scrollbar doesn't render behind it. The current setup allows // above so that the scrollbar doesn't render behind it. The current setup allows