REPL: Refactor output (#16927)

Shuffle `outputs.rs` into individual `outputs/*.rs` files and start
documenting them more.

Release Notes:

- N/A
This commit is contained in:
Kyle Kelley 2024-08-26 18:03:06 -07:00 committed by GitHub
parent bea6786f14
commit 26d943287b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 533 additions and 443 deletions

View file

@ -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('<', "&lt;")
.replace('>', "&gt;")
}
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,

View file

@ -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
}
}

View file

@ -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()
}
}

View file

@ -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();

View file

@ -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('<', "&lt;")
.replace('>', "&gt;")
}
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
}
}

View file

@ -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(),
)
}
}

View file

@ -6,7 +6,6 @@ mod repl_editor;
mod repl_sessions_ui;
mod repl_store;
mod session;
mod stdio;
use std::{sync::Arc, time::Duration};

View file

@ -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;