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

Kirill Bulatov created

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

Change summary

crates/editor/src/hover_popover.rs |  1 
crates/markdown/src/markdown.rs    | 77 ++++++++++++++++++++-----------
2 files changed, 50 insertions(+), 28 deletions(-)

Detailed changes

crates/markdown/src/markdown.rs 🔗

@@ -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(),
-                                            ))
-                                        }
-                                    }),
-                            );
 
-                            el.child(copy_button)
-                        });
+                        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)
+                            });
+                        }
+
                         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;