Do not show copy buttons in editor's hover popovers (#22962)

Follow-up of https://github.com/zed-industries/zed/pull/22866

Added a config option to the markdown renderer to omit code copying
buttons, and used those for editor hover popovers.

Such popovers are quite frequent in language servers' hover responses,
e.g. rust-analyzer on `.clone()` hover may respond with
```
{"jsonrpc":"2.0","id":119,"result":{"contents":{"kind":"markdown","value":"\n```rust\nalloc::string::String\n```\n\n```rust\nfn clone(&self) -> Self\n```\n\n---\n\nReturns a copy of the value.\n\n# Examples\n\n```rust\nlet hello = \"Hello\"; // &str implements Clone\n\nassert_eq!(\"Hello\", hello.clone());\n```"},"range":{"start":{"line":518,"character":24},"end":{"line":518,"character":29}}}}
```

(note multiple code blocks sent)


![image](https://github.com/user-attachments/assets/4c40b15e-8f53-4b3d-a809-f1e4d35a00a7)


![image](https://github.com/user-attachments/assets/77b8e13b-b665-42d3-b633-5a0375998f06)

Sounds that editor has either to use a different way to copy popover's
data (so the entire text gets copied, not just its code blocks), or at
least better handle hover popover's hovering to show the button.


Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov 2025-01-10 16:16:52 +02:00 committed by GitHub
parent c3301077af
commit 49198a7961
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 27 deletions

View file

@ -590,6 +590,7 @@ async fn parse_blocks(
fallback_language_name,
cx,
)
.copy_code_block_buttons(false)
})
.ok();

View file

@ -61,7 +61,13 @@ pub struct Markdown {
focus_handle: FocusHandle,
language_registry: Option<Arc<LanguageRegistry>>,
fallback_code_block_language: Option<String>,
options: Options,
}
#[derive(Debug)]
struct Options {
parse_links_only: bool,
copy_code_block_buttons: bool,
}
actions!(markdown, [Copy]);
@ -87,7 +93,10 @@ impl Markdown {
focus_handle,
language_registry,
fallback_code_block_language,
parse_links_only: false,
options: Options {
parse_links_only: false,
copy_code_block_buttons: true,
},
};
this.parse(cx);
this
@ -113,7 +122,10 @@ impl Markdown {
focus_handle,
language_registry,
fallback_code_block_language,
parse_links_only: true,
options: Options {
parse_links_only: true,
copy_code_block_buttons: true,
},
};
this.parse(cx);
this
@ -164,7 +176,7 @@ impl Markdown {
}
let text = self.source.clone();
let parse_text_only = self.parse_links_only;
let parse_text_only = self.options.parse_links_only;
let parsed = cx.background_executor().spawn(async move {
let text = SharedString::from(text);
let events = match parse_text_only {
@ -195,6 +207,11 @@ impl Markdown {
.log_err()
}));
}
pub fn copy_code_block_buttons(mut self, should_copy: bool) -> Self {
self.options.copy_code_block_buttons = should_copy;
self
}
}
impl Render for Markdown {
@ -667,31 +684,35 @@ impl Element for MarkdownElement {
}
MarkdownTagEnd::CodeBlock => {
builder.trim_trailing_newline();
builder.flush_text();
builder.modify_current_div(|el| {
let id =
ElementId::NamedInteger("copy-markdown-code".into(), range.end);
let copy_button = div().absolute().top_1().right_1().w_5().child(
IconButton::new(id, IconName::Copy)
.icon_color(Color::Muted)
.shape(ui::IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Copy Code Block", cx))
.on_click({
let code = without_fences(
parsed_markdown.source()[range.clone()].trim(),
)
.to_string();
move |_, cx| {
cx.write_to_clipboard(ClipboardItem::new_string(
code.clone(),
))
}
}),
);
if self.markdown.read(cx).options.copy_code_block_buttons {
builder.flush_text();
builder.modify_current_div(|el| {
let id =
ElementId::NamedInteger("copy-markdown-code".into(), range.end);
let copy_button = div().absolute().top_1().right_1().w_5().child(
IconButton::new(id, IconName::Copy)
.icon_color(Color::Muted)
.shape(ui::IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Copy Code Block", cx))
.on_click({
let code = without_fences(
parsed_markdown.source()[range.clone()].trim(),
)
.to_string();
move |_, cx| {
cx.write_to_clipboard(ClipboardItem::new_string(
code.clone(),
))
}
}),
);
el.child(copy_button)
});
}
el.child(copy_button)
});
builder.pop_div();
builder.pop_code_block();
if self.style.code_block.text.is_some() {
@ -1033,7 +1054,7 @@ impl MarkdownElementBuilder {
}
}
pub fn flush_text(&mut self) {
fn flush_text(&mut self) {
let line = mem::take(&mut self.pending_line);
if line.text.is_empty() {
return;