assistant2: Add "Copy code" button to code blocks (#22866)
Here's what it looks like, including the "Copy" hover text in one case:  Release Notes: - N/A --------- Co-authored-by: Conrad <conrad@zed.dev> Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
parent
a8ef0f2426
commit
7d905d0791
1 changed files with 74 additions and 2 deletions
|
@ -14,7 +14,7 @@ use parser::{parse_links_only, parse_markdown, MarkdownEvent, MarkdownTag, Markd
|
||||||
|
|
||||||
use std::{iter, mem, ops::Range, rc::Rc, sync::Arc};
|
use std::{iter, mem, ops::Range, rc::Rc, sync::Arc};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use ui::prelude::*;
|
use ui::{prelude::*, Tooltip};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -667,6 +667,31 @@ impl Element for MarkdownElement {
|
||||||
}
|
}
|
||||||
MarkdownTagEnd::CodeBlock => {
|
MarkdownTagEnd::CodeBlock => {
|
||||||
builder.trim_trailing_newline();
|
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)
|
||||||
|
});
|
||||||
builder.pop_div();
|
builder.pop_div();
|
||||||
builder.pop_code_block();
|
builder.pop_code_block();
|
||||||
if self.style.code_block.text.is_some() {
|
if self.style.code_block.text.is_some() {
|
||||||
|
@ -917,6 +942,13 @@ impl MarkdownElementBuilder {
|
||||||
self.div_stack.push(div);
|
self.div_stack.push(div);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn modify_current_div(&mut self, f: impl FnOnce(AnyDiv) -> AnyDiv) {
|
||||||
|
self.flush_text();
|
||||||
|
if let Some(div) = self.div_stack.pop() {
|
||||||
|
self.div_stack.push(f(div));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn pop_div(&mut self) {
|
fn pop_div(&mut self) {
|
||||||
self.flush_text();
|
self.flush_text();
|
||||||
let div = self.div_stack.pop().unwrap().into_any_element();
|
let div = self.div_stack.pop().unwrap().into_any_element();
|
||||||
|
@ -1001,7 +1033,7 @@ impl MarkdownElementBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush_text(&mut self) {
|
pub fn flush_text(&mut self) {
|
||||||
let line = mem::take(&mut self.pending_line);
|
let line = mem::take(&mut self.pending_line);
|
||||||
if line.text.is_empty() {
|
if line.text.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
@ -1220,3 +1252,43 @@ impl RenderedText {
|
||||||
.find(|link| link.source_range.contains(&source_index))
|
.find(|link| link.source_range.contains(&source_index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Some markdown blocks are indented, and others have e.g. ```rust … ``` around them.
|
||||||
|
/// If this block is fenced with backticks, strip them off (and the language name).
|
||||||
|
/// We use this when copying code blocks to the clipboard.
|
||||||
|
fn without_fences(mut markdown: &str) -> &str {
|
||||||
|
if let Some(opening_backticks) = markdown.find("```") {
|
||||||
|
markdown = &markdown[opening_backticks..];
|
||||||
|
|
||||||
|
// Trim off the next newline. This also trims off a language name if it's there.
|
||||||
|
if let Some(newline) = markdown.find('\n') {
|
||||||
|
markdown = &markdown[newline + 1..];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(closing_backticks) = markdown.rfind("```") {
|
||||||
|
markdown = &markdown[..closing_backticks];
|
||||||
|
};
|
||||||
|
|
||||||
|
markdown
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_without_fences() {
|
||||||
|
let input = "```rust\nlet x = 5;\n```";
|
||||||
|
assert_eq!(without_fences(input), "let x = 5;\n");
|
||||||
|
|
||||||
|
let input = " ```\nno language\n``` ";
|
||||||
|
assert_eq!(without_fences(input), "no language\n");
|
||||||
|
|
||||||
|
let input = "plain text";
|
||||||
|
assert_eq!(without_fences(input), "plain text");
|
||||||
|
|
||||||
|
let input = "```python\nprint('hello')\nprint('world')\n```";
|
||||||
|
assert_eq!(without_fences(input), "print('hello')\nprint('world')\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue