Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/repl/src/notebook/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,9 @@ impl Render for CodeCell {
Output::Image { content, .. } => {
Some(content.clone().into_any_element())
}
Output::Svg { content, .. } => {
Some(content.clone().into_any_element())
}
Output::Message(message) => Some(
div().child(message.clone()).into_any_element(),
),
Expand Down
20 changes: 20 additions & 0 deletions crates/repl/src/outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ use ui::{
mod image;
use image::ImageView;

mod svg;
use svg::SvgView;

mod markdown;
use markdown::MarkdownView;

Expand All @@ -62,6 +65,7 @@ use workspace::Workspace;
fn rank_mime_type(mimetype: &MimeType) -> usize {
match mimetype {
MimeType::DataTable(_) => 6,
MimeType::Svg(_) => 5,
MimeType::Png(_) => 4,
MimeType::Jpeg(_) => 3,
MimeType::Markdown(_) => 2,
Expand Down Expand Up @@ -114,6 +118,10 @@ pub enum Output {
content: Entity<ImageView>,
display_id: Option<String>,
},
Svg {
content: Entity<SvgView>,
display_id: Option<String>,
},
ErrorOutput(ErrorView),
Message(String),
Table {
Expand Down Expand Up @@ -211,6 +219,7 @@ impl Output {
Self::Markdown { content, .. } => Some(content.clone().into_any_element()),
Self::Stream { content, .. } => Some(content.clone().into_any_element()),
Self::Image { content, .. } => Some(content.clone().into_any_element()),
Self::Svg { content, .. } => Some(content.clone().into_any_element()),
Self::Message(message) => Some(div().child(message.clone()).into_any_element()),
Self::Table { content, .. } => Some(content.clone().into_any_element()),
Self::ErrorOutput(error_view) => error_view.render(window, cx),
Expand All @@ -236,6 +245,9 @@ impl Output {
Self::Image { content, .. } => {
Self::render_output_controls(content.clone(), workspace, window, cx)
}
Self::Svg { content, .. } => {
Self::render_output_controls(content.clone(), workspace, window, cx)
}
Self::ErrorOutput(err) => {
// Add buttons for the traceback section
Some(
Expand Down Expand Up @@ -332,6 +344,7 @@ impl Output {
Output::Plain { display_id, .. } => display_id.clone(),
Output::Stream { .. } => None,
Output::Image { display_id, .. } => display_id.clone(),
Output::Svg { display_id, .. } => display_id.clone(),
Output::ErrorOutput(_) => None,
Output::Message(_) => None,
Output::Table { display_id, .. } => display_id.clone(),
Expand Down Expand Up @@ -365,6 +378,13 @@ impl Output {
},
Err(error) => Output::Message(format!("Failed to load image: {}", error)),
},
Some(MimeType::Svg(data)) => match SvgView::from(data, cx) {
Ok(view) => Output::Svg {
content: cx.new(|_| view),
display_id,
},
Err(error) => Output::Message(format!("Failed to load SVG: {}", error)),
},
Some(MimeType::DataTable(data)) => Output::Table {
content: cx.new(|cx| TableView::new(data, window, cx)),
display_id,
Expand Down
78 changes: 78 additions & 0 deletions crates/repl/src/outputs/svg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use anyhow::{Context as _, Result};
use gpui::{App, ClipboardItem, Pixels, RenderImage, Window, img, px};
use std::sync::Arc;
use ui::{IntoElement, Styled, div, prelude::*};

use crate::outputs::OutputContent;

const SVG_SCALE_FACTOR: f32 = 2.0;

pub struct SvgView {
raw_svg: String,
width: Pixels,
height: Pixels,
image: Arc<RenderImage>,
}

impl SvgView {
pub fn from(svg_data: &str, cx: &App) -> Result<Self> {
let renderer = cx.svg_renderer();
let image = renderer
.render_single_frame(svg_data.as_bytes(), 1.0, true)
.context("rendering SVG")?;

let size = image.size(0);
let width = px(size.width.0 as f32 / SVG_SCALE_FACTOR);
let height = px(size.height.0 as f32 / SVG_SCALE_FACTOR);

Ok(Self {
raw_svg: svg_data.to_string(),
width,
height,
image,
})
}
}

impl Render for SvgView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div()
.h(self.height)
.w(self.width)
.child(img(self.image.clone()))
}
}

impl OutputContent for SvgView {
fn clipboard_content(&self, _window: &Window, _cx: &App) -> Option<ClipboardItem> {
Some(ClipboardItem::new_string(self.raw_svg.clone()))
}

fn has_clipboard_content(&self, _window: &Window, _cx: &App) -> bool {
true
}
}

#[cfg(test)]
mod tests {
use super::*;

const SIMPLE_SVG: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect width="100" height="100" fill="red"/></svg>"#;

#[gpui::test]
fn test_valid_svg(cx: &mut App) {
let result = SvgView::from(SIMPLE_SVG, cx);
assert!(result.is_ok());

let view = result.unwrap();
assert_eq!(view.raw_svg, SIMPLE_SVG);
assert!(view.width > Pixels::ZERO);
assert!(view.height > Pixels::ZERO);
}

#[gpui::test]
fn test_invalid_svg(cx: &mut App) {
let result = SvgView::from("not valid svg content", cx);
assert!(result.is_err());
}
}