Skip to content

Instantly share code, notes, and snippets.

@airstrike
Created January 10, 2025 20:05
Show Gist options
  • Save airstrike/b9b9c9a8867396a948af9abd4f6eff32 to your computer and use it in GitHub Desktop.
Save airstrike/b9b9c9a8867396a948af9abd4f6eff32 to your computer and use it in GitHub Desktop.
A variation of iced's markdown widget which includes a "copy" button for codeblocks
use iced::widget::{button, column, container, hover, rich_text, row, scrollable, text, Button};
use iced::widget::{horizontal_space, markdown};
use iced::{padding, Element, Font, Length, Pixels};
pub use iced::widget::markdown::{HeadingLevel, Item, Settings, Style, Url};
/// Display a bunch of Markdown items with copy functionality for code blocks.
pub fn view<'a, Message, Theme, Renderer>(
items: impl IntoIterator<Item = &'a Item>,
settings: Settings,
style: Style,
on_url: impl Fn(Url) -> Message + 'a + Clone,
on_copy: impl Fn(String) -> Message + 'a + Clone,
copy_button: impl Fn() -> Button<'a, Message, Theme, Renderer> + Clone + 'a,
) -> Element<'a, Message, Theme, Renderer>
where
Message: Clone + 'a,
Theme: markdown::Catalog + button::Catalog + 'a,
Renderer: iced::advanced::text::Renderer<Font = Font> + 'a,
{
let Settings {
text_size,
h1_size,
h2_size,
h3_size,
h4_size,
h5_size,
h6_size,
code_size,
} = settings;
let spacing = text_size * 0.625;
let blocks = items.into_iter().enumerate().map(|(i, item)| match item {
Item::Heading(level, heading) => Element::from(
container(rich_text(heading.spans(style)).size(match level {
HeadingLevel::H1 => h1_size,
HeadingLevel::H2 => h2_size,
HeadingLevel::H3 => h3_size,
HeadingLevel::H4 => h4_size,
HeadingLevel::H5 => h5_size,
HeadingLevel::H6 => h6_size,
}))
.padding(padding::top(if i > 0 {
text_size / 2.0
} else {
Pixels::ZERO
})),
)
.map(on_url.clone()),
Item::Paragraph(paragraph) => {
Element::from(rich_text(paragraph.spans(style)).size(text_size)).map(on_url.clone())
}
Item::List { start: None, items } => column(items.iter().map(|items| {
row![
text("•").size(text_size),
view(
items,
settings,
style,
on_url.clone(),
on_copy.clone(),
copy_button.clone()
)
]
.spacing(spacing)
.into()
}))
.spacing(spacing)
.into(),
Item::List {
start: Some(start),
items,
} => column(items.iter().enumerate().map(|(i, items)| {
row![
text!("{}.", i as u64 + *start).size(text_size),
view(
items,
settings,
style,
on_url.clone(),
on_copy.clone(),
copy_button.clone(),
)
]
.spacing(spacing)
.into()
}))
.spacing(spacing)
.into(),
Item::CodeBlock(code) => {
let code_content = code
.spans(style)
.iter()
.map(|span| span.text.clone())
.collect::<String>();
let code_view = Element::from(
container(
scrollable(
container(
rich_text(code.spans(style))
.font(Font::MONOSPACE)
.size(code_size),
)
.padding(spacing.0 / 2.0),
)
.direction(scrollable::Direction::Horizontal(
scrollable::Scrollbar::default()
.width(spacing.0 / 2.0)
.scroller_width(spacing.0 / 2.0),
)),
)
.width(Length::Fill)
.padding(spacing.0 / 2.0)
.class(Theme::code_block()),
)
.map(on_url.clone());
let copy_button = Element::from(
row![
horizontal_space(),
copy_button().on_press(on_copy(code_content))
]
.padding(10),
)
.map(Message::from);
hover(code_view, copy_button).into()
}
});
Element::new(column(blocks).width(Length::Fill).spacing(text_size))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment