@@ -76,7 +76,6 @@ impl RenderedMessage {
fn from_segments(
segments: &[MessageSegment],
language_registry: Arc<LanguageRegistry>,
- workspace: WeakEntity<Workspace>,
cx: &mut App,
) -> Self {
let mut this = Self {
@@ -84,12 +83,12 @@ impl RenderedMessage {
segments: Vec::with_capacity(segments.len()),
};
for segment in segments {
- this.push_segment(segment, workspace.clone(), cx);
+ this.push_segment(segment, cx);
}
this
}
- fn append_thinking(&mut self, text: &String, workspace: WeakEntity<Workspace>, cx: &mut App) {
+ fn append_thinking(&mut self, text: &String, cx: &mut App) {
if let Some(RenderedMessageSegment::Thinking {
content,
scroll_handle,
@@ -101,18 +100,13 @@ impl RenderedMessage {
scroll_handle.scroll_to_bottom();
} else {
self.segments.push(RenderedMessageSegment::Thinking {
- content: render_markdown(
- text.into(),
- self.language_registry.clone(),
- workspace,
- cx,
- ),
+ content: render_markdown(text.into(), self.language_registry.clone(), cx),
scroll_handle: ScrollHandle::default(),
});
}
}
- fn append_text(&mut self, text: &String, workspace: WeakEntity<Workspace>, cx: &mut App) {
+ fn append_text(&mut self, text: &String, cx: &mut App) {
if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() {
markdown.update(cx, |markdown, cx| markdown.append(text, cx));
} else {
@@ -120,32 +114,20 @@ impl RenderedMessage {
.push(RenderedMessageSegment::Text(render_markdown(
SharedString::from(text),
self.language_registry.clone(),
- workspace,
cx,
)));
}
}
- fn push_segment(
- &mut self,
- segment: &MessageSegment,
- workspace: WeakEntity<Workspace>,
- cx: &mut App,
- ) {
+ fn push_segment(&mut self, segment: &MessageSegment, cx: &mut App) {
let rendered_segment = match segment {
MessageSegment::Thinking(text) => RenderedMessageSegment::Thinking {
- content: render_markdown(
- text.into(),
- self.language_registry.clone(),
- workspace,
- cx,
- ),
+ content: render_markdown(text.into(), self.language_registry.clone(), cx),
scroll_handle: ScrollHandle::default(),
},
MessageSegment::Text(text) => RenderedMessageSegment::Text(render_markdown(
text.into(),
self.language_registry.clone(),
- workspace,
cx,
)),
};
@@ -164,14 +146,9 @@ enum RenderedMessageSegment {
fn render_markdown(
text: SharedString,
language_registry: Arc<LanguageRegistry>,
- workspace: WeakEntity<Workspace>,
cx: &mut App,
) -> Entity<Markdown> {
- cx.new(|cx| {
- Markdown::new(text, Some(language_registry), None, cx).open_url(move |text, window, cx| {
- open_markdown_link(text, workspace.clone(), window, cx);
- })
- })
+ cx.new(|cx| Markdown::new(text, Some(language_registry), None, cx))
}
fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
@@ -261,14 +238,9 @@ fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
fn render_tool_use_markdown(
text: SharedString,
language_registry: Arc<LanguageRegistry>,
- workspace: WeakEntity<Workspace>,
cx: &mut App,
) -> Entity<Markdown> {
- cx.new(|cx| {
- Markdown::new(text, Some(language_registry), None, cx).open_url(move |text, window, cx| {
- open_markdown_link(text, workspace.clone(), window, cx);
- })
- })
+ cx.new(|cx| Markdown::new(text, Some(language_registry), None, cx))
}
fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
@@ -502,12 +474,8 @@ impl ActiveThread {
self.messages.push(*id);
self.list_state.splice(old_len..old_len, 1);
- let rendered_message = RenderedMessage::from_segments(
- segments,
- self.language_registry.clone(),
- self.workspace.clone(),
- cx,
- );
+ let rendered_message =
+ RenderedMessage::from_segments(segments, self.language_registry.clone(), cx);
self.rendered_messages_by_id.insert(*id, rendered_message);
}
@@ -522,12 +490,8 @@ impl ActiveThread {
return;
};
self.list_state.splice(index..index + 1, 1);
- let rendered_message = RenderedMessage::from_segments(
- segments,
- self.language_registry.clone(),
- self.workspace.clone(),
- cx,
- );
+ let rendered_message =
+ RenderedMessage::from_segments(segments, self.language_registry.clone(), cx);
self.rendered_messages_by_id.insert(*id, rendered_message);
}
@@ -549,12 +513,7 @@ impl ActiveThread {
cx: &mut Context<Self>,
) {
let rendered = RenderedToolUse {
- label: render_tool_use_markdown(
- tool_label.into(),
- self.language_registry.clone(),
- self.workspace.clone(),
- cx,
- ),
+ label: render_tool_use_markdown(tool_label.into(), self.language_registry.clone(), cx),
input: render_tool_use_markdown(
format!(
"```json\n{}\n```",
@@ -562,15 +521,9 @@ impl ActiveThread {
)
.into(),
self.language_registry.clone(),
- self.workspace.clone(),
- cx,
- ),
- output: render_tool_use_markdown(
- tool_output,
- self.language_registry.clone(),
- self.workspace.clone(),
cx,
),
+ output: render_tool_use_markdown(tool_output, self.language_registry.clone(), cx),
};
self.rendered_tool_uses
.insert(tool_use_id.clone(), rendered);
@@ -613,12 +566,12 @@ impl ActiveThread {
}
ThreadEvent::StreamedAssistantText(message_id, text) => {
if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
- rendered_message.append_text(text, self.workspace.clone(), cx);
+ rendered_message.append_text(text, cx);
}
}
ThreadEvent::StreamedAssistantThinking(message_id, text) => {
if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
- rendered_message.append_thinking(text, self.workspace.clone(), cx);
+ rendered_message.append_thinking(text, cx);
}
}
ThreadEvent::MessageAdded(message_id) => {
@@ -1550,10 +1503,18 @@ impl ActiveThread {
)
.into_any_element(),
RenderedMessageSegment::Text(markdown) => div()
- .child(MarkdownElement::new(
- markdown.clone(),
- default_markdown_style(window, cx),
- ))
+ .child(
+ MarkdownElement::new(
+ markdown.clone(),
+ default_markdown_style(window, cx),
+ )
+ .on_url_click({
+ let workspace = self.workspace.clone();
+ move |text, window, cx| {
+ open_markdown_link(text, workspace.clone(), window, cx);
+ }
+ }),
+ )
.into_any_element(),
},
),
@@ -1712,10 +1673,23 @@ impl ActiveThread {
.h_20()
.track_scroll(scroll_handle)
.text_ui_sm(cx)
- .child(MarkdownElement::new(
- markdown.clone(),
- default_markdown_style(window, cx),
- ))
+ .child(
+ MarkdownElement::new(
+ markdown.clone(),
+ default_markdown_style(window, cx),
+ )
+ .on_url_click({
+ let workspace = self.workspace.clone();
+ move |text, window, cx| {
+ open_markdown_link(
+ text,
+ workspace.clone(),
+ window,
+ cx,
+ );
+ }
+ }),
+ )
.overflow_hidden(),
)
.child(gradient_overlay),
@@ -1730,10 +1704,18 @@ impl ActiveThread {
.rounded_b_lg()
.bg(editor_bg)
.text_ui_sm(cx)
- .child(MarkdownElement::new(
- markdown.clone(),
- default_markdown_style(window, cx),
- )),
+ .child(
+ MarkdownElement::new(
+ markdown.clone(),
+ default_markdown_style(window, cx),
+ )
+ .on_url_click({
+ let workspace = self.workspace.clone();
+ move |text, window, cx| {
+ open_markdown_link(text, workspace.clone(), window, cx);
+ }
+ }),
+ ),
)
}),
)
@@ -1789,13 +1771,41 @@ impl ActiveThread {
let rendered_tool_use = self.rendered_tool_uses.get(&tool_use.id).cloned();
let results_content_container = || v_flex().p_2().gap_0p5();
- let results_content =
- v_flex()
- .gap_1()
- .child(
+ let results_content = v_flex()
+ .gap_1()
+ .child(
+ results_content_container()
+ .child(
+ Label::new("Input")
+ .size(LabelSize::XSmall)
+ .color(Color::Muted)
+ .buffer_font(cx),
+ )
+ .child(
+ div()
+ .w_full()
+ .text_ui_sm(cx)
+ .children(rendered_tool_use.as_ref().map(|rendered| {
+ MarkdownElement::new(
+ rendered.input.clone(),
+ tool_use_markdown_style(window, cx),
+ )
+ .on_url_click({
+ let workspace = self.workspace.clone();
+ move |text, window, cx| {
+ open_markdown_link(text, workspace.clone(), window, cx);
+ }
+ })
+ })),
+ ),
+ )
+ .map(|container| match tool_use.status {
+ ToolUseStatus::Finished(_) => container.child(
results_content_container()
+ .border_t_1()
+ .border_color(self.tool_card_border_color(cx))
.child(
- Label::new("Input")
+ Label::new("Result")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
@@ -1803,95 +1813,87 @@ impl ActiveThread {
.child(div().w_full().text_ui_sm(cx).children(
rendered_tool_use.as_ref().map(|rendered| {
MarkdownElement::new(
- rendered.input.clone(),
+ rendered.output.clone(),
tool_use_markdown_style(window, cx),
)
+ .on_url_click({
+ let workspace = self.workspace.clone();
+ move |text, window, cx| {
+ open_markdown_link(text, workspace.clone(), window, cx);
+ }
+ })
}),
)),
- )
- .map(|container| match tool_use.status {
- ToolUseStatus::Finished(_) => container.child(
- results_content_container()
+ ),
+ ToolUseStatus::Running => container.child(
+ results_content_container().child(
+ h_flex()
+ .gap_1()
+ .pb_1()
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.child(
- Label::new("Result")
+ Icon::new(IconName::ArrowCircle)
+ .size(IconSize::Small)
+ .color(Color::Accent)
+ .with_animation(
+ "arrow-circle",
+ Animation::new(Duration::from_secs(2)).repeat(),
+ |icon, delta| {
+ icon.transform(Transformation::rotate(percentage(
+ delta,
+ )))
+ },
+ ),
+ )
+ .child(
+ Label::new("Runningโฆ")
.size(LabelSize::XSmall)
.color(Color::Muted)
.buffer_font(cx),
- )
- .child(div().w_full().text_ui_sm(cx).children(
- rendered_tool_use.as_ref().map(|rendered| {
+ ),
+ ),
+ ),
+ ToolUseStatus::Error(_) => container.child(
+ results_content_container()
+ .border_t_1()
+ .border_color(self.tool_card_border_color(cx))
+ .child(
+ Label::new("Error")
+ .size(LabelSize::XSmall)
+ .color(Color::Muted)
+ .buffer_font(cx),
+ )
+ .child(
+ div()
+ .text_ui_sm(cx)
+ .children(rendered_tool_use.as_ref().map(|rendered| {
MarkdownElement::new(
rendered.output.clone(),
tool_use_markdown_style(window, cx),
)
- }),
- )),
- ),
- ToolUseStatus::Running => container.child(
- results_content_container().child(
- h_flex()
- .gap_1()
- .pb_1()
- .border_t_1()
- .border_color(self.tool_card_border_color(cx))
- .child(
- Icon::new(IconName::ArrowCircle)
- .size(IconSize::Small)
- .color(Color::Accent)
- .with_animation(
- "arrow-circle",
- Animation::new(Duration::from_secs(2)).repeat(),
- |icon, delta| {
- icon.transform(Transformation::rotate(percentage(
- delta,
- )))
- },
- ),
- )
- .child(
- Label::new("Runningโฆ")
- .size(LabelSize::XSmall)
- .color(Color::Muted)
- .buffer_font(cx),
- ),
+ .on_url_click({
+ let workspace = self.workspace.clone();
+ move |text, window, cx| {
+ open_markdown_link(text, workspace.clone(), window, cx);
+ }
+ })
+ })),
),
- ),
- ToolUseStatus::Error(_) => {
- container.child(
- results_content_container()
- .border_t_1()
- .border_color(self.tool_card_border_color(cx))
- .child(
- Label::new("Error")
- .size(LabelSize::XSmall)
- .color(Color::Muted)
- .buffer_font(cx),
- )
- .child(div().text_ui_sm(cx).children(
- rendered_tool_use.as_ref().map(|rendered| {
- MarkdownElement::new(
- rendered.output.clone(),
- tool_use_markdown_style(window, cx),
- )
- }),
- )),
- )
- }
- ToolUseStatus::Pending => container,
- ToolUseStatus::NeedsConfirmation => container.child(
- results_content_container()
- .border_t_1()
- .border_color(self.tool_card_border_color(cx))
- .child(
- Label::new("Asking Permission")
- .size(LabelSize::Small)
- .color(Color::Muted)
- .buffer_font(cx),
- ),
- ),
- });
+ ),
+ ToolUseStatus::Pending => container,
+ ToolUseStatus::NeedsConfirmation => container.child(
+ results_content_container()
+ .border_t_1()
+ .border_color(self.tool_card_border_color(cx))
+ .child(
+ Label::new("Asking Permission")
+ .size(LabelSize::Small)
+ .color(Color::Muted)
+ .buffer_font(cx),
+ ),
+ ),
+ });
let gradient_overlay = |color: Hsla| {
div()
@@ -1939,7 +1941,9 @@ impl ActiveThread {
)
.child(
h_flex().pr_8().text_ui_sm(cx).children(
- rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)))
+ rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| {
+ open_markdown_link(text, workspace.clone(), window, cx);
+ }}))
),
),
)
@@ -2027,7 +2031,9 @@ impl ActiveThread {
)
.child(
h_flex().pr_8().text_ui_sm(cx).children(
- rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)))
+ rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| {
+ open_markdown_link(text, workspace.clone(), window, cx);
+ }}))
),
),
)
@@ -80,7 +80,6 @@ pub struct Markdown {
focus_handle: FocusHandle,
language_registry: Option<Arc<LanguageRegistry>>,
fallback_code_block_language: Option<String>,
- open_url: Option<Box<dyn Fn(SharedString, &mut Window, &mut App)>>,
options: Options,
copied_code_blocks: HashSet<ElementId>,
}
@@ -116,23 +115,12 @@ impl Markdown {
parse_links_only: false,
copy_code_block_buttons: true,
},
- open_url: None,
copied_code_blocks: HashSet::new(),
};
this.parse(cx);
this
}
- pub fn open_url(
- self,
- open_url: impl Fn(SharedString, &mut Window, &mut App) + 'static,
- ) -> Self {
- Self {
- open_url: Some(Box::new(open_url)),
- ..self
- }
- }
-
pub fn new_text(source: SharedString, cx: &mut Context<Self>) -> Self {
let focus_handle = cx.focus_handle();
let mut this = Self {
@@ -150,7 +138,6 @@ impl Markdown {
parse_links_only: true,
copy_code_block_buttons: true,
},
- open_url: None,
copied_code_blocks: HashSet::new(),
};
this.parse(cx);
@@ -328,11 +315,24 @@ impl ParsedMarkdown {
pub struct MarkdownElement {
markdown: Entity<Markdown>,
style: MarkdownStyle,
+ on_url_click: Option<Box<dyn Fn(SharedString, &mut Window, &mut App)>>,
}
impl MarkdownElement {
pub fn new(markdown: Entity<Markdown>, style: MarkdownStyle) -> Self {
- Self { markdown, style }
+ Self {
+ markdown,
+ style,
+ on_url_click: None,
+ }
+ }
+
+ pub fn on_url_click(
+ mut self,
+ handler: impl Fn(SharedString, &mut Window, &mut App) + 'static,
+ ) -> Self {
+ self.on_url_click = Some(Box::new(handler));
+ self
}
fn paint_selection(
@@ -404,7 +404,7 @@ impl MarkdownElement {
}
fn paint_mouse_listeners(
- &self,
+ &mut self,
hitbox: &Hitbox,
rendered_text: &RenderedText,
window: &mut Window,
@@ -422,6 +422,8 @@ impl MarkdownElement {
window.set_cursor_style(CursorStyle::IBeam, Some(hitbox));
}
+ let on_open_url = self.on_url_click.take();
+
self.on_mouse_event(window, cx, {
let rendered_text = rendered_text.clone();
let hitbox = hitbox.clone();
@@ -493,7 +495,7 @@ impl MarkdownElement {
if phase.bubble() {
if let Some(pressed_link) = markdown.pressed_link.take() {
if Some(&pressed_link) == rendered_text.link_for_position(event.position) {
- if let Some(open_url) = markdown.open_url.as_mut() {
+ if let Some(open_url) = on_open_url.as_ref() {
open_url(pressed_link.destination_url, window, cx);
} else {
cx.open_url(&pressed_link.destination_url);