From 49198a7961c86d9d433784647ec37ddb05ca0852 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 10 Jan 2025 16:16:52 +0200 Subject: [PATCH] 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 --- crates/editor/src/hover_popover.rs | 1 + crates/markdown/src/markdown.rs | 77 +++++++++++++++++++----------- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 439d15ec6a08b9d806913fba4b9c043f60ef8706..7050972e59d52f12b865162c8c1e32441dcbdb02 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -590,6 +590,7 @@ async fn parse_blocks( fallback_language_name, cx, ) + .copy_code_block_buttons(false) }) .ok(); diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index 0b80c77c9cc294685e3586ba8c460bc1ad1236ee..e63265c8d4b9da6fa8a71507ea21d0a5d85a31b9 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -61,7 +61,13 @@ pub struct Markdown { focus_handle: FocusHandle, language_registry: Option>, fallback_code_block_language: Option, + 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;