From 2260b87ea8bb3448efd1a13d03ac8bc3557fb74d Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Mon, 8 Dec 2025 15:04:49 +0100 Subject: [PATCH] agent_ui: Fix show markdown list checked state (#43567) Closes #37527 This PR adds support for showing the list state of a list item inside the agent UI. **Before** Screenshot 2025-11-26 at 16 21 31 **After** Screenshot 2025-11-26 at 16 41 32 Release Notes: - Agent UI now show the checked state of a list item --------- Co-authored-by: Danilo Leal --- crates/markdown/src/markdown.rs | 30 +++++++++++++--- crates/ui/src/components/toggle.rs | 57 ++++++++++++++++++++---------- 2 files changed, 63 insertions(+), 24 deletions(-) diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index 3f7d8e0d29eca1fff4af2b34c0bac9f32b4d730d..317657ea5f520cee15fc49d462b3f8ac5f0072dc 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -7,6 +7,7 @@ use gpui::HitboxBehavior; use language::LanguageName; use log::Level; pub use path_range::{LineCol, PathWithRange}; +use ui::Checkbox; use std::borrow::Cow; use std::iter; @@ -795,7 +796,7 @@ impl Element for MarkdownElement { let mut code_block_ids = HashSet::default(); let mut current_img_block_range: Option> = None; - for (range, event) in parsed_markdown.events.iter() { + for (index, (range, event)) in parsed_markdown.events.iter().enumerate() { // Skip alt text for images that rendered if let Some(current_img_block_range) = ¤t_img_block_range && current_img_block_range.end > range.end @@ -945,13 +946,29 @@ impl Element for MarkdownElement { MarkdownTag::HtmlBlock => builder.push_div(div(), range, markdown_end), MarkdownTag::List(bullet_index) => { builder.push_list(*bullet_index); - builder.push_div(div().pl_4(), range, markdown_end); + builder.push_div(div().pl_2p5(), range, markdown_end); } MarkdownTag::Item => { - let bullet = if let Some(bullet_index) = builder.next_bullet_index() { - format!("{}.", bullet_index) + let bullet = if let Some((_, MarkdownEvent::TaskListMarker(checked))) = + parsed_markdown.events.get(index.saturating_add(1)) + { + let source = &parsed_markdown.source()[range.clone()]; + + Checkbox::new( + ElementId::Name(source.to_string().into()), + if *checked { + ToggleState::Selected + } else { + ToggleState::Unselected + }, + ) + .fill() + .visualization_only(true) + .into_any_element() + } else if let Some(bullet_index) = builder.next_bullet_index() { + div().child(format!("{}.", bullet_index)).into_any_element() } else { - "•".to_string() + div().child("•").into_any_element() }; builder.push_div( div() @@ -1226,6 +1243,9 @@ impl Element for MarkdownElement { } MarkdownEvent::SoftBreak => builder.push_text(" ", range.clone()), MarkdownEvent::HardBreak => builder.push_text("\n", range.clone()), + MarkdownEvent::TaskListMarker(_) => { + // handled inside the `MarkdownTag::Item` case + } _ => log::debug!("unsupported markdown event {:?}", event), } } diff --git a/crates/ui/src/components/toggle.rs b/crates/ui/src/components/toggle.rs index e0360d6a2a86c212a47ba3dd8df7d40f0c3e152a..86ff1d8eff8691a2610a4a7e2268aaf47502e306 100644 --- a/crates/ui/src/components/toggle.rs +++ b/crates/ui/src/components/toggle.rs @@ -44,15 +44,16 @@ pub enum ToggleStyle { pub struct Checkbox { id: ElementId, toggle_state: ToggleState, + style: ToggleStyle, disabled: bool, placeholder: bool, - on_click: Option>, filled: bool, - style: ToggleStyle, - tooltip: Option AnyView>>, + visualization: bool, label: Option, label_size: LabelSize, label_color: Color, + tooltip: Option AnyView>>, + on_click: Option>, } impl Checkbox { @@ -61,15 +62,16 @@ impl Checkbox { Self { id: id.into(), toggle_state: checked, + style: ToggleStyle::default(), disabled: false, - on_click: None, + placeholder: false, filled: false, - style: ToggleStyle::default(), - tooltip: None, + visualization: false, label: None, label_size: LabelSize::Default, label_color: Color::Muted, - placeholder: false, + tooltip: None, + on_click: None, } } @@ -110,6 +112,13 @@ impl Checkbox { self } + /// Makes the checkbox look enabled but without pointer cursor and hover styles. + /// Primarily used for uninteractive markdown previews. + pub fn visualization_only(mut self, visualization: bool) -> Self { + self.visualization = visualization; + self + } + /// Sets the style of the checkbox using the specified [`ToggleStyle`]. pub fn style(mut self, style: ToggleStyle) -> Self { self.style = style; @@ -209,11 +218,10 @@ impl RenderOnce for Checkbox { let size = Self::container_size(); let checkbox = h_flex() + .group(group_id.clone()) .id(self.id.clone()) - .justify_center() - .items_center() .size(size) - .group(group_id.clone()) + .justify_center() .child( div() .flex() @@ -230,7 +238,7 @@ impl RenderOnce for Checkbox { .when(self.disabled, |this| { this.bg(cx.theme().colors().element_disabled.opacity(0.6)) }) - .when(!self.disabled, |this| { + .when(!self.disabled && !self.visualization, |this| { this.group_hover(group_id.clone(), |el| el.border_color(hover_border_color)) }) .when(self.placeholder, |this| { @@ -250,20 +258,14 @@ impl RenderOnce for Checkbox { .map(|this| { if self.disabled { this.cursor_not_allowed() + } else if self.visualization { + this.cursor_default() } else { this.cursor_pointer() } }) .gap(DynamicSpacing::Base06.rems(cx)) .child(checkbox) - .when_some( - self.on_click.filter(|_| !self.disabled), - |this, on_click| { - this.on_click(move |click, window, cx| { - on_click(&self.toggle_state.inverse(), click, window, cx) - }) - }, - ) .when_some(self.label, |this, label| { this.child( Label::new(label) @@ -274,6 +276,14 @@ impl RenderOnce for Checkbox { .when_some(self.tooltip, |this, tooltip| { this.tooltip(move |window, cx| tooltip(window, cx)) }) + .when_some( + self.on_click.filter(|_| !self.disabled), + |this, on_click| { + this.on_click(move |click, window, cx| { + on_click(&self.toggle_state.inverse(), click, window, cx) + }) + }, + ) } } @@ -914,6 +924,15 @@ impl Component for Checkbox { .into_any_element(), )], ), + example_group_with_title( + "Extra", + vec![single_example( + "Visualization-Only", + Checkbox::new("viz_only", ToggleState::Selected) + .visualization_only(true) + .into_any_element(), + )], + ), ]) .into_any_element(), )