Detailed changes
@@ -1,25 +1,25 @@
-use std::sync::Arc;
use std::time::Duration;
-use crate::stdio::TerminalOutput;
-use anyhow::Result;
-use base64::prelude::*;
use gpui::{
- img, percentage, Animation, AnimationExt, AnyElement, ClipboardItem, FontWeight, Image,
- ImageFormat, Render, RenderImage, Task, TextRun, Transformation, View,
+ percentage, Animation, AnimationExt, AnyElement, ClipboardItem, Render, Transformation, View,
};
-use runtimelib::datatable::TableSchema;
-use runtimelib::media::datatable::TabularDataResource;
use runtimelib::{ExecutionState, JupyterMessageContent, MimeBundle, MimeType};
-use serde_json::Value;
-use settings::Settings;
-use theme::ThemeSettings;
use ui::{div, prelude::*, v_flex, IntoElement, Styled, Tooltip, ViewContext};
-use markdown_preview::{
- markdown_elements::ParsedMarkdown, markdown_parser::parse_markdown,
- markdown_renderer::render_markdown_block,
-};
+mod image;
+use image::ImageView;
+
+mod markdown;
+use markdown::MarkdownView;
+
+mod table;
+use table::TableView;
+
+pub mod plain;
+use plain::TerminalOutput;
+
+mod user_error;
+use user_error::ErrorView;
/// When deciding what to render from a collection of mediatypes, we need to rank them in order of importance
fn rank_mime_type(mimetype: &MimeType) -> usize {
@@ -39,428 +39,6 @@ pub(crate) trait SupportsClipboard {
fn has_clipboard_content(&self, cx: &WindowContext) -> bool;
}
-/// ImageView renders an image inline in an editor, adapting to the line height to fit the image.
-pub struct ImageView {
- clipboard_image: Arc<Image>,
- height: u32,
- width: u32,
- image: Arc<RenderImage>,
-}
-
-impl ImageView {
- fn render(&self, cx: &ViewContext<ExecutionView>) -> AnyElement {
- let line_height = cx.line_height();
-
- let (height, width) = if self.height as f32 / line_height.0 == u8::MAX as f32 {
- let height = u8::MAX as f32 * line_height.0;
- let width = self.width as f32 * height / self.height as f32;
- (height, width)
- } else {
- (self.height as f32, self.width as f32)
- };
-
- let image = self.image.clone();
-
- div()
- .h(Pixels(height))
- .w(Pixels(width))
- .child(img(image))
- .into_any_element()
- }
-
- fn from(base64_encoded_data: &str) -> Result<Self> {
- let bytes = BASE64_STANDARD.decode(base64_encoded_data)?;
-
- let format = image::guess_format(&bytes)?;
- let mut data = image::load_from_memory_with_format(&bytes, format)?.into_rgba8();
-
- // Convert from RGBA to BGRA.
- for pixel in data.chunks_exact_mut(4) {
- pixel.swap(0, 2);
- }
-
- let height = data.height();
- let width = data.width();
-
- let gpui_image_data = RenderImage::new(vec![image::Frame::new(data)]);
-
- let format = match format {
- image::ImageFormat::Png => ImageFormat::Png,
- image::ImageFormat::Jpeg => ImageFormat::Jpeg,
- image::ImageFormat::Gif => ImageFormat::Gif,
- image::ImageFormat::WebP => ImageFormat::Webp,
- image::ImageFormat::Tiff => ImageFormat::Tiff,
- image::ImageFormat::Bmp => ImageFormat::Bmp,
- _ => {
- return Err(anyhow::anyhow!("unsupported image format"));
- }
- };
-
- // Convert back to a GPUI image for use with the clipboard
- let clipboard_image = Arc::new(Image {
- format,
- bytes,
- id: gpui_image_data.id.0 as u64,
- });
-
- return Ok(ImageView {
- clipboard_image,
- height,
- width,
- image: Arc::new(gpui_image_data),
- });
- }
-}
-
-impl SupportsClipboard for ImageView {
- fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
- Some(ClipboardItem::new_image(self.clipboard_image.as_ref()))
- }
-
- fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
- true
- }
-}
-
-/// TableView renders a static table inline in a buffer.
-/// It uses the https://specs.frictionlessdata.io/tabular-data-resource/ specification for data interchange.
-pub struct TableView {
- pub table: TabularDataResource,
- pub widths: Vec<Pixels>,
- cached_clipboard_content: ClipboardItem,
-}
-
-fn cell_content(row: &Value, field: &str) -> String {
- match row.get(&field) {
- Some(Value::String(s)) => s.clone(),
- Some(Value::Number(n)) => n.to_string(),
- Some(Value::Bool(b)) => b.to_string(),
- Some(Value::Array(arr)) => format!("{:?}", arr),
- Some(Value::Object(obj)) => format!("{:?}", obj),
- Some(Value::Null) | None => String::new(),
- }
-}
-
-// Declare constant for the padding multiple on the line height
-const TABLE_Y_PADDING_MULTIPLE: f32 = 0.5;
-
-impl TableView {
- pub fn new(table: TabularDataResource, cx: &mut WindowContext) -> Self {
- let mut widths = Vec::with_capacity(table.schema.fields.len());
-
- let text_system = cx.text_system();
- let text_style = cx.text_style();
- let text_font = ThemeSettings::get_global(cx).buffer_font.clone();
- let font_size = ThemeSettings::get_global(cx).buffer_font_size;
- let mut runs = [TextRun {
- len: 0,
- font: text_font,
- color: text_style.color,
- background_color: None,
- underline: None,
- strikethrough: None,
- }];
-
- for field in table.schema.fields.iter() {
- runs[0].len = field.name.len();
- let mut width = text_system
- .layout_line(&field.name, font_size, &runs)
- .map(|layout| layout.width)
- .unwrap_or(px(0.));
-
- let Some(data) = table.data.as_ref() else {
- widths.push(width);
- continue;
- };
-
- for row in data {
- let content = cell_content(&row, &field.name);
- runs[0].len = content.len();
- let cell_width = cx
- .text_system()
- .layout_line(&content, font_size, &runs)
- .map(|layout| layout.width)
- .unwrap_or(px(0.));
-
- width = width.max(cell_width)
- }
-
- widths.push(width)
- }
-
- let cached_clipboard_content = Self::create_clipboard_content(&table);
-
- Self {
- table,
- widths,
- cached_clipboard_content: ClipboardItem::new_string(cached_clipboard_content),
- }
- }
-
- fn escape_markdown(s: &str) -> String {
- s.replace('|', "\\|")
- .replace('*', "\\*")
- .replace('_', "\\_")
- .replace('`', "\\`")
- .replace('[', "\\[")
- .replace(']', "\\]")
- .replace('<', "<")
- .replace('>', ">")
- }
-
- fn create_clipboard_content(table: &TabularDataResource) -> String {
- let data = match table.data.as_ref() {
- Some(data) => data,
- None => &Vec::new(),
- };
- let schema = table.schema.clone();
-
- let mut markdown = format!(
- "| {} |\n",
- table
- .schema
- .fields
- .iter()
- .map(|field| field.name.clone())
- .collect::<Vec<_>>()
- .join(" | ")
- );
-
- markdown.push_str("|---");
- for _ in 1..table.schema.fields.len() {
- markdown.push_str("|---");
- }
- markdown.push_str("|\n");
-
- let body = data
- .iter()
- .map(|record: &Value| {
- let row_content = schema
- .fields
- .iter()
- .map(|field| Self::escape_markdown(&cell_content(record, &field.name)))
- .collect::<Vec<_>>();
-
- row_content.join(" | ")
- })
- .collect::<Vec<String>>();
-
- for row in body {
- markdown.push_str(&format!("| {} |\n", row));
- }
-
- markdown
- }
-
- pub fn render(&self, cx: &ViewContext<ExecutionView>) -> AnyElement {
- let data = match &self.table.data {
- Some(data) => data,
- None => return div().into_any_element(),
- };
-
- let mut headings = serde_json::Map::new();
- for field in &self.table.schema.fields {
- headings.insert(field.name.clone(), Value::String(field.name.clone()));
- }
- let header = self.render_row(&self.table.schema, true, &Value::Object(headings), cx);
-
- let body = data
- .iter()
- .map(|row| self.render_row(&self.table.schema, false, &row, cx));
-
- v_flex()
- .id("table")
- .overflow_x_scroll()
- .w_full()
- .child(header)
- .children(body)
- .into_any_element()
- }
-
- pub fn render_row(
- &self,
- schema: &TableSchema,
- is_header: bool,
- row: &Value,
- cx: &ViewContext<ExecutionView>,
- ) -> AnyElement {
- let theme = cx.theme();
-
- let line_height = cx.line_height();
-
- let row_cells = schema
- .fields
- .iter()
- .zip(self.widths.iter())
- .map(|(field, width)| {
- let container = match field.field_type {
- runtimelib::datatable::FieldType::String => div(),
-
- runtimelib::datatable::FieldType::Number
- | runtimelib::datatable::FieldType::Integer
- | runtimelib::datatable::FieldType::Date
- | runtimelib::datatable::FieldType::Time
- | runtimelib::datatable::FieldType::Datetime
- | runtimelib::datatable::FieldType::Year
- | runtimelib::datatable::FieldType::Duration
- | runtimelib::datatable::FieldType::Yearmonth => v_flex().items_end(),
-
- _ => div(),
- };
-
- let value = cell_content(row, &field.name);
-
- let mut cell = container
- .min_w(*width + px(22.))
- .w(*width + px(22.))
- .child(value)
- .px_2()
- .py((TABLE_Y_PADDING_MULTIPLE / 2.0) * line_height)
- .border_color(theme.colors().border);
-
- if is_header {
- cell = cell.border_1().bg(theme.colors().border_focused)
- } else {
- cell = cell.border_1()
- }
- cell
- })
- .collect::<Vec<_>>();
-
- let mut total_width = px(0.);
- for width in self.widths.iter() {
- // Width fudge factor: border + 2 (heading), padding
- total_width += *width + px(22.);
- }
-
- h_flex()
- .w(total_width)
- .children(row_cells)
- .into_any_element()
- }
-}
-
-impl SupportsClipboard for TableView {
- fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
- Some(self.cached_clipboard_content.clone())
- }
-
- fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
- true
- }
-}
-
-/// Userspace error from the kernel
-pub struct ErrorView {
- pub ename: String,
- pub evalue: String,
- pub traceback: TerminalOutput,
-}
-
-impl ErrorView {
- fn render(&self, cx: &mut ViewContext<ExecutionView>) -> Option<AnyElement> {
- let theme = cx.theme();
-
- let padding = cx.line_height() / 2.;
-
- Some(
- v_flex()
- .gap_3()
- .child(
- h_flex()
- .font_buffer(cx)
- .child(
- Label::new(format!("{}: ", self.ename.clone()))
- // .size(LabelSize::Large)
- .color(Color::Error)
- .weight(FontWeight::BOLD),
- )
- .child(
- Label::new(self.evalue.clone())
- // .size(LabelSize::Large)
- .weight(FontWeight::BOLD),
- ),
- )
- .child(
- div()
- .w_full()
- .px(padding)
- .py(padding)
- .border_l_1()
- .border_color(theme.status().error_border)
- .child(self.traceback.render(cx)),
- )
- .into_any_element(),
- )
- }
-}
-
-pub struct MarkdownView {
- raw_text: String,
- contents: Option<ParsedMarkdown>,
- parsing_markdown_task: Option<Task<Result<()>>>,
-}
-
-impl MarkdownView {
- pub fn from(text: String, cx: &mut ViewContext<Self>) -> Self {
- let task = cx.spawn(|markdown_view, mut cx| {
- let text = text.clone();
- let parsed = cx
- .background_executor()
- .spawn(async move { parse_markdown(&text, None, None).await });
-
- async move {
- let content = parsed.await;
-
- markdown_view.update(&mut cx, |markdown, cx| {
- markdown.parsing_markdown_task.take();
- markdown.contents = Some(content);
- cx.notify();
- })
- }
- });
-
- Self {
- raw_text: text.clone(),
- contents: None,
- parsing_markdown_task: Some(task),
- }
- }
-}
-
-impl SupportsClipboard for MarkdownView {
- fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
- Some(ClipboardItem::new_string(self.raw_text.clone()))
- }
-
- fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
- true
- }
-}
-
-impl Render for MarkdownView {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
- let Some(parsed) = self.contents.as_ref() else {
- return div().into_any_element();
- };
-
- let mut markdown_render_context =
- markdown_preview::markdown_renderer::RenderContext::new(None, cx);
-
- v_flex()
- .gap_3()
- .py_4()
- .children(parsed.children.iter().map(|child| {
- div().relative().child(
- div()
- .relative()
- .child(render_markdown_block(child, &mut markdown_render_context)),
- )
- }))
- .into_any_element()
- }
-}
-
pub struct Output {
content: OutputContent,
display_id: Option<String>,
@@ -573,6 +151,9 @@ pub enum ExecutionStatus {
Restarting,
}
+/// An ExecutionView shows the outputs of an execution.
+/// It can hold zero or more outputs, which the user
+/// sees as "the output" for a single execution.
pub struct ExecutionView {
pub outputs: Vec<Output>,
pub status: ExecutionStatus,
@@ -0,0 +1,92 @@
+use anyhow::Result;
+use base64::prelude::*;
+use gpui::{
+ img, AnyElement, ClipboardItem, Image, ImageFormat, Pixels, RenderImage, WindowContext,
+};
+use std::sync::Arc;
+use ui::{div, prelude::*, IntoElement, Styled};
+
+use crate::outputs::SupportsClipboard;
+
+/// ImageView renders an image inline in an editor, adapting to the line height to fit the image.
+pub struct ImageView {
+ clipboard_image: Arc<Image>,
+ height: u32,
+ width: u32,
+ image: Arc<RenderImage>,
+}
+
+impl ImageView {
+ pub fn from(base64_encoded_data: &str) -> Result<Self> {
+ let bytes = BASE64_STANDARD.decode(base64_encoded_data)?;
+
+ let format = image::guess_format(&bytes)?;
+ let mut data = image::load_from_memory_with_format(&bytes, format)?.into_rgba8();
+
+ // Convert from RGBA to BGRA.
+ for pixel in data.chunks_exact_mut(4) {
+ pixel.swap(0, 2);
+ }
+
+ let height = data.height();
+ let width = data.width();
+
+ let gpui_image_data = RenderImage::new(vec![image::Frame::new(data)]);
+
+ let format = match format {
+ image::ImageFormat::Png => ImageFormat::Png,
+ image::ImageFormat::Jpeg => ImageFormat::Jpeg,
+ image::ImageFormat::Gif => ImageFormat::Gif,
+ image::ImageFormat::WebP => ImageFormat::Webp,
+ image::ImageFormat::Tiff => ImageFormat::Tiff,
+ image::ImageFormat::Bmp => ImageFormat::Bmp,
+ _ => {
+ return Err(anyhow::anyhow!("unsupported image format"));
+ }
+ };
+
+ // Convert back to a GPUI image for use with the clipboard
+ let clipboard_image = Arc::new(Image {
+ format,
+ bytes,
+ id: gpui_image_data.id.0 as u64,
+ });
+
+ return Ok(ImageView {
+ clipboard_image,
+ height,
+ width,
+ image: Arc::new(gpui_image_data),
+ });
+ }
+
+ pub fn render(&self, cx: &mut WindowContext) -> AnyElement {
+ let line_height = cx.line_height();
+
+ let (height, width) = if self.height as f32 / line_height.0 == u8::MAX as f32 {
+ let height = u8::MAX as f32 * line_height.0;
+ let width = self.width as f32 * height / self.height as f32;
+ (height, width)
+ } else {
+ (self.height as f32, self.width as f32)
+ };
+
+ let image = self.image.clone();
+
+ div()
+ .h(Pixels(height))
+ .w(Pixels(width))
+ .child(img(image))
+ .into_any_element()
+ }
+}
+
+impl SupportsClipboard for ImageView {
+ fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
+ Some(ClipboardItem::new_image(self.clipboard_image.as_ref()))
+ }
+
+ fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
+ true
+ }
+}
@@ -0,0 +1,75 @@
+use anyhow::Result;
+use gpui::{div, prelude::*, ClipboardItem, Task, ViewContext, WindowContext};
+use markdown_preview::{
+ markdown_elements::ParsedMarkdown, markdown_parser::parse_markdown,
+ markdown_renderer::render_markdown_block,
+};
+use ui::v_flex;
+
+use crate::outputs::SupportsClipboard;
+
+pub struct MarkdownView {
+ raw_text: String,
+ contents: Option<ParsedMarkdown>,
+ parsing_markdown_task: Option<Task<Result<()>>>,
+}
+
+impl MarkdownView {
+ pub fn from(text: String, cx: &mut ViewContext<Self>) -> Self {
+ let task = cx.spawn(|markdown_view, mut cx| {
+ let text = text.clone();
+ let parsed = cx
+ .background_executor()
+ .spawn(async move { parse_markdown(&text, None, None).await });
+
+ async move {
+ let content = parsed.await;
+
+ markdown_view.update(&mut cx, |markdown, cx| {
+ markdown.parsing_markdown_task.take();
+ markdown.contents = Some(content);
+ cx.notify();
+ })
+ }
+ });
+
+ Self {
+ raw_text: text.clone(),
+ contents: None,
+ parsing_markdown_task: Some(task),
+ }
+ }
+}
+
+impl SupportsClipboard for MarkdownView {
+ fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
+ Some(ClipboardItem::new_string(self.raw_text.clone()))
+ }
+
+ fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
+ true
+ }
+}
+
+impl Render for MarkdownView {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+ let Some(parsed) = self.contents.as_ref() else {
+ return div().into_any_element();
+ };
+
+ let mut markdown_render_context =
+ markdown_preview::markdown_renderer::RenderContext::new(None, cx);
+
+ v_flex()
+ .gap_3()
+ .py_4()
+ .children(parsed.children.iter().map(|child| {
+ div().relative().child(
+ div()
+ .relative()
+ .child(render_markdown_block(child, &mut markdown_render_context)),
+ )
+ }))
+ .into_any_element()
+ }
+}
@@ -1,4 +1,3 @@
-use crate::outputs::{ExecutionView, SupportsClipboard};
use alacritty_terminal::{grid::Dimensions as _, term::Config, vte::ansi::Processor};
use gpui::{canvas, size, AnyElement, ClipboardItem, FontStyle, TextStyle, WhiteSpace};
use settings::Settings as _;
@@ -6,7 +5,9 @@ use std::mem;
use terminal::ZedListener;
use terminal_view::terminal_element::TerminalElement;
use theme::ThemeSettings;
-use ui::{prelude::*, IntoElement, ViewContext};
+use ui::{prelude::*, IntoElement};
+
+use crate::outputs::SupportsClipboard;
/// Implements the most basic of terminal output for use by Jupyter outputs
/// whether:
@@ -119,7 +120,7 @@ impl TerminalOutput {
}
}
- pub fn render(&self, cx: &mut ViewContext<ExecutionView>) -> AnyElement {
+ pub fn render(&self, cx: &mut WindowContext) -> AnyElement {
let text_style = text_style(cx);
let text_system = cx.text_system();
@@ -0,0 +1,293 @@
+//! # Table Output for REPL
+//!
+//! This module provides functionality to render tabular data in Zed's REPL output.
+//!
+//! It supports the [Frictionless Data Table Schema](https://specs.frictionlessdata.io/table-schema/)
+//! for data interchange, implemented by Pandas in Python and Polars for Deno.
+//!
+//! # Python Example
+//!
+//! Tables can be created and displayed in two main ways:
+//!
+//! 1. Using raw JSON data conforming to the Tabular Data Resource specification.
+//! 2. Using Pandas DataFrames (in Python kernels).
+//!
+//! ## Raw JSON Method
+//!
+//! To create a table using raw JSON, you need to provide a JSON object that conforms
+//! to the Tabular Data Resource specification. Here's an example:
+//!
+//! ```json
+//! {
+//! "schema": {
+//! "fields": [
+//! {"name": "id", "type": "integer"},
+//! {"name": "name", "type": "string"},
+//! {"name": "age", "type": "integer"}
+//! ]
+//! },
+//! "data": [
+//! {"id": 1, "name": "Alice", "age": 30},
+//! {"id": 2, "name": "Bob", "age": 28},
+//! {"id": 3, "name": "Charlie", "age": 35}
+//! ]
+//! }
+//! ```
+//!
+//! ## Pandas Method
+//!
+//! To create a table using Pandas in a Python kernel, you can use the following steps:
+//!
+//! ```python
+//! import pandas as pd
+//!
+//! # Enable table schema output
+//! pd.set_option('display.html.table_schema', True)
+//!
+//! # Create a DataFrame
+//! df = pd.DataFrame({
+//! 'id': [1, 2, 3],
+//! 'name': ['Alice', 'Bob', 'Charlie'],
+//! 'age': [30, 28, 35]
+//! })
+//!
+//! # Display the DataFrame
+//! display(df)
+//! ```
+use gpui::{AnyElement, ClipboardItem, TextRun};
+use runtimelib::datatable::TableSchema;
+use runtimelib::media::datatable::TabularDataResource;
+use serde_json::Value;
+use settings::Settings;
+use theme::ThemeSettings;
+use ui::{div, prelude::*, v_flex, IntoElement, Styled};
+
+use crate::outputs::SupportsClipboard;
+
+/// TableView renders a static table inline in a buffer.
+/// It uses the https://specs.frictionlessdata.io/tabular-data-resource/ specification for data interchange.
+pub struct TableView {
+ pub table: TabularDataResource,
+ pub widths: Vec<Pixels>,
+ cached_clipboard_content: ClipboardItem,
+}
+
+fn cell_content(row: &Value, field: &str) -> String {
+ match row.get(&field) {
+ Some(Value::String(s)) => s.clone(),
+ Some(Value::Number(n)) => n.to_string(),
+ Some(Value::Bool(b)) => b.to_string(),
+ Some(Value::Array(arr)) => format!("{:?}", arr),
+ Some(Value::Object(obj)) => format!("{:?}", obj),
+ Some(Value::Null) | None => String::new(),
+ }
+}
+
+// Declare constant for the padding multiple on the line height
+const TABLE_Y_PADDING_MULTIPLE: f32 = 0.5;
+
+impl TableView {
+ pub fn new(table: TabularDataResource, cx: &mut WindowContext) -> Self {
+ let mut widths = Vec::with_capacity(table.schema.fields.len());
+
+ let text_system = cx.text_system();
+ let text_style = cx.text_style();
+ let text_font = ThemeSettings::get_global(cx).buffer_font.clone();
+ let font_size = ThemeSettings::get_global(cx).buffer_font_size;
+ let mut runs = [TextRun {
+ len: 0,
+ font: text_font,
+ color: text_style.color,
+ background_color: None,
+ underline: None,
+ strikethrough: None,
+ }];
+
+ for field in table.schema.fields.iter() {
+ runs[0].len = field.name.len();
+ let mut width = text_system
+ .layout_line(&field.name, font_size, &runs)
+ .map(|layout| layout.width)
+ .unwrap_or(px(0.));
+
+ let Some(data) = table.data.as_ref() else {
+ widths.push(width);
+ continue;
+ };
+
+ for row in data {
+ let content = cell_content(&row, &field.name);
+ runs[0].len = content.len();
+ let cell_width = cx
+ .text_system()
+ .layout_line(&content, font_size, &runs)
+ .map(|layout| layout.width)
+ .unwrap_or(px(0.));
+
+ width = width.max(cell_width)
+ }
+
+ widths.push(width)
+ }
+
+ let cached_clipboard_content = Self::create_clipboard_content(&table);
+
+ Self {
+ table,
+ widths,
+ cached_clipboard_content: ClipboardItem::new_string(cached_clipboard_content),
+ }
+ }
+
+ fn escape_markdown(s: &str) -> String {
+ s.replace('|', "\\|")
+ .replace('*', "\\*")
+ .replace('_', "\\_")
+ .replace('`', "\\`")
+ .replace('[', "\\[")
+ .replace(']', "\\]")
+ .replace('<', "<")
+ .replace('>', ">")
+ }
+
+ fn create_clipboard_content(table: &TabularDataResource) -> String {
+ let data = match table.data.as_ref() {
+ Some(data) => data,
+ None => &Vec::new(),
+ };
+ let schema = table.schema.clone();
+
+ let mut markdown = format!(
+ "| {} |\n",
+ table
+ .schema
+ .fields
+ .iter()
+ .map(|field| field.name.clone())
+ .collect::<Vec<_>>()
+ .join(" | ")
+ );
+
+ markdown.push_str("|---");
+ for _ in 1..table.schema.fields.len() {
+ markdown.push_str("|---");
+ }
+ markdown.push_str("|\n");
+
+ let body = data
+ .iter()
+ .map(|record: &Value| {
+ let row_content = schema
+ .fields
+ .iter()
+ .map(|field| Self::escape_markdown(&cell_content(record, &field.name)))
+ .collect::<Vec<_>>();
+
+ row_content.join(" | ")
+ })
+ .collect::<Vec<String>>();
+
+ for row in body {
+ markdown.push_str(&format!("| {} |\n", row));
+ }
+
+ markdown
+ }
+
+ pub fn render(&self, cx: &WindowContext) -> AnyElement {
+ let data = match &self.table.data {
+ Some(data) => data,
+ None => return div().into_any_element(),
+ };
+
+ let mut headings = serde_json::Map::new();
+ for field in &self.table.schema.fields {
+ headings.insert(field.name.clone(), Value::String(field.name.clone()));
+ }
+ let header = self.render_row(&self.table.schema, true, &Value::Object(headings), cx);
+
+ let body = data
+ .iter()
+ .map(|row| self.render_row(&self.table.schema, false, &row, cx));
+
+ v_flex()
+ .id("table")
+ .overflow_x_scroll()
+ .w_full()
+ .child(header)
+ .children(body)
+ .into_any_element()
+ }
+
+ pub fn render_row(
+ &self,
+ schema: &TableSchema,
+ is_header: bool,
+ row: &Value,
+ cx: &WindowContext,
+ ) -> AnyElement {
+ let theme = cx.theme();
+
+ let line_height = cx.line_height();
+
+ let row_cells = schema
+ .fields
+ .iter()
+ .zip(self.widths.iter())
+ .map(|(field, width)| {
+ let container = match field.field_type {
+ runtimelib::datatable::FieldType::String => div(),
+
+ runtimelib::datatable::FieldType::Number
+ | runtimelib::datatable::FieldType::Integer
+ | runtimelib::datatable::FieldType::Date
+ | runtimelib::datatable::FieldType::Time
+ | runtimelib::datatable::FieldType::Datetime
+ | runtimelib::datatable::FieldType::Year
+ | runtimelib::datatable::FieldType::Duration
+ | runtimelib::datatable::FieldType::Yearmonth => v_flex().items_end(),
+
+ _ => div(),
+ };
+
+ let value = cell_content(row, &field.name);
+
+ let mut cell = container
+ .min_w(*width + px(22.))
+ .w(*width + px(22.))
+ .child(value)
+ .px_2()
+ .py((TABLE_Y_PADDING_MULTIPLE / 2.0) * line_height)
+ .border_color(theme.colors().border);
+
+ if is_header {
+ cell = cell.border_1().bg(theme.colors().border_focused)
+ } else {
+ cell = cell.border_1()
+ }
+ cell
+ })
+ .collect::<Vec<_>>();
+
+ let mut total_width = px(0.);
+ for width in self.widths.iter() {
+ // Width fudge factor: border + 2 (heading), padding
+ total_width += *width + px(22.);
+ }
+
+ h_flex()
+ .w(total_width)
+ .children(row_cells)
+ .into_any_element()
+ }
+}
+
+impl SupportsClipboard for TableView {
+ fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
+ Some(self.cached_clipboard_content.clone())
+ }
+
+ fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
+ true
+ }
+}
@@ -0,0 +1,49 @@
+use gpui::{AnyElement, FontWeight, WindowContext};
+use ui::{h_flex, prelude::*, v_flex, Label};
+
+use crate::outputs::plain::TerminalOutput;
+
+/// Userspace error from the kernel
+pub struct ErrorView {
+ pub ename: String,
+ pub evalue: String,
+ pub traceback: TerminalOutput,
+}
+
+impl ErrorView {
+ pub fn render(&self, cx: &mut WindowContext) -> Option<AnyElement> {
+ let theme = cx.theme();
+
+ let padding = cx.line_height() / 2.;
+
+ Some(
+ v_flex()
+ .gap_3()
+ .child(
+ h_flex()
+ .font_buffer(cx)
+ .child(
+ Label::new(format!("{}: ", self.ename.clone()))
+ // .size(LabelSize::Large)
+ .color(Color::Error)
+ .weight(FontWeight::BOLD),
+ )
+ .child(
+ Label::new(self.evalue.clone())
+ // .size(LabelSize::Large)
+ .weight(FontWeight::BOLD),
+ ),
+ )
+ .child(
+ div()
+ .w_full()
+ .px(padding)
+ .py(padding)
+ .border_l_1()
+ .border_color(theme.status().error_border)
+ .child(self.traceback.render(cx)),
+ )
+ .into_any_element(),
+ )
+ }
+}
@@ -6,7 +6,6 @@ mod repl_editor;
mod repl_sessions_ui;
mod repl_store;
mod session;
-mod stdio;
use std::{sync::Arc, time::Duration};
@@ -1,9 +1,9 @@
use crate::components::KernelListItem;
+use crate::KernelStatus;
use crate::{
kernels::{Kernel, KernelSpecification, RunningKernel},
outputs::{ExecutionStatus, ExecutionView},
};
-use crate::{stdio, KernelStatus};
use client::telemetry::Telemetry;
use collections::{HashMap, HashSet};
use editor::{
@@ -115,7 +115,7 @@ impl EditorBlock {
) -> RenderBlock {
let render = move |cx: &mut BlockContext| {
let execution_view = execution_view.clone();
- let text_style = stdio::text_style(cx);
+ let text_style = crate::outputs::plain::text_style(cx);
let gutter = cx.gutter_dimensions;