mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Measure maximum width of each cell to render table (#14026)
This commit is contained in:
parent
c4bca874b6
commit
4bb8a0845f
@ -2,11 +2,13 @@ use std::sync::Arc;
|
||||
|
||||
use crate::stdio::TerminalOutput;
|
||||
use anyhow::Result;
|
||||
use gpui::{img, AnyElement, FontWeight, ImageData, Render, View};
|
||||
use gpui::{img, AnyElement, FontWeight, ImageData, Render, TextRun, 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, ViewContext};
|
||||
|
||||
// Given these outputs are destined for the editor with the block decorations API, all of them must report
|
||||
@ -97,9 +99,67 @@ impl LineHeight for ImageView {
|
||||
/// It uses the https://specs.frictionlessdata.io/tabular-data-resource/ specification for data interchange.
|
||||
pub struct TableView {
|
||||
pub table: TabularDataResource,
|
||||
pub widths: Vec<Pixels>,
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
Self { table, widths }
|
||||
}
|
||||
|
||||
pub fn render(&self, cx: &ViewContext<ExecutionView>) -> AnyElement {
|
||||
let data = match &self.table.data {
|
||||
Some(data) => data,
|
||||
@ -119,6 +179,8 @@ impl TableView {
|
||||
.map(|row| self.render_row(&self.table.schema, false, &row, cx));
|
||||
|
||||
v_flex()
|
||||
.id("table")
|
||||
.overflow_x_scroll()
|
||||
.w_full()
|
||||
.child(header)
|
||||
.children(body)
|
||||
@ -137,7 +199,8 @@ impl TableView {
|
||||
let row_cells = schema
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
.zip(self.widths.iter())
|
||||
.map(|(field, width)| {
|
||||
let container = match field.field_type {
|
||||
runtimelib::datatable::FieldType::String => div(),
|
||||
|
||||
@ -153,17 +216,11 @@ impl TableView {
|
||||
_ => div(),
|
||||
};
|
||||
|
||||
let value = match row.get(&field.name) {
|
||||
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(),
|
||||
};
|
||||
let value = cell_content(row, &field.name);
|
||||
|
||||
let mut cell = container
|
||||
.w_full()
|
||||
.min_w(*width + px(22.))
|
||||
.w(*width + px(22.))
|
||||
.child(value)
|
||||
.px_2()
|
||||
.py_1()
|
||||
@ -178,7 +235,16 @@ impl TableView {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
h_flex().children(row_cells).into_any_element()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,6 +332,20 @@ impl OutputType {
|
||||
|
||||
el
|
||||
}
|
||||
|
||||
pub fn new(data: &MimeBundle, cx: &mut WindowContext) -> Self {
|
||||
match data.richest(rank_mime_type) {
|
||||
Some(MimeType::Plain(text)) => OutputType::Plain(TerminalOutput::from(text)),
|
||||
Some(MimeType::Markdown(text)) => OutputType::Plain(TerminalOutput::from(text)),
|
||||
Some(MimeType::Png(data)) | Some(MimeType::Jpeg(data)) => match ImageView::from(data) {
|
||||
Ok(view) => OutputType::Image(view),
|
||||
Err(error) => OutputType::Message(format!("Failed to load image: {}", error)),
|
||||
},
|
||||
Some(MimeType::DataTable(data)) => OutputType::Table(TableView::new(data.clone(), cx)),
|
||||
// Any other media types are not supported
|
||||
_ => OutputType::Message("Unsupported media type".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LineHeight for OutputType {
|
||||
@ -283,24 +363,6 @@ impl LineHeight for OutputType {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MimeBundle> for OutputType {
|
||||
fn from(data: &MimeBundle) -> Self {
|
||||
match data.richest(rank_mime_type) {
|
||||
Some(MimeType::Plain(text)) => OutputType::Plain(TerminalOutput::from(text)),
|
||||
Some(MimeType::Markdown(text)) => OutputType::Plain(TerminalOutput::from(text)),
|
||||
Some(MimeType::Png(data)) | Some(MimeType::Jpeg(data)) => match ImageView::from(data) {
|
||||
Ok(view) => OutputType::Image(view),
|
||||
Err(error) => OutputType::Message(format!("Failed to load image: {}", error)),
|
||||
},
|
||||
Some(MimeType::DataTable(data)) => OutputType::Table(TableView {
|
||||
table: data.clone(),
|
||||
}),
|
||||
// Any other media types are not supported
|
||||
_ => OutputType::Message("Unsupported media type".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub enum ExecutionStatus {
|
||||
#[default]
|
||||
@ -330,8 +392,8 @@ impl ExecutionView {
|
||||
/// Accept a Jupyter message belonging to this execution
|
||||
pub fn push_message(&mut self, message: &JupyterMessageContent, cx: &mut ViewContext<Self>) {
|
||||
let output: OutputType = match message {
|
||||
JupyterMessageContent::ExecuteResult(result) => (&result.data).into(),
|
||||
JupyterMessageContent::DisplayData(result) => (&result.data).into(),
|
||||
JupyterMessageContent::ExecuteResult(result) => OutputType::new(&result.data, cx),
|
||||
JupyterMessageContent::DisplayData(result) => OutputType::new(&result.data, cx),
|
||||
JupyterMessageContent::StreamContent(result) => {
|
||||
// Previous stream data will combine together, handling colors, carriage returns, etc
|
||||
if let Some(new_terminal) = self.apply_terminal_text(&result.text) {
|
||||
@ -357,7 +419,7 @@ impl ExecutionView {
|
||||
// Pager data comes in via `?` at the end of a statement in Python, used for showing documentation.
|
||||
// Some UI will show this as a popup. For ease of implementation, it's included as an output here.
|
||||
runtimelib::Payload::Page { data, .. } => {
|
||||
let output: OutputType = (data).into();
|
||||
let output = OutputType::new(data, cx);
|
||||
self.outputs.push(output);
|
||||
}
|
||||
|
||||
|
@ -89,6 +89,7 @@ impl EditorBlock {
|
||||
let render = move |cx: &mut BlockContext| {
|
||||
let execution_view = execution_view.clone();
|
||||
let text_font = ThemeSettings::get_global(cx).buffer_font.family.clone();
|
||||
let text_font_size = ThemeSettings::get_global(cx).buffer_font_size;
|
||||
// Note: we'll want to use `cx.anchor_x` when someone runs something with no output -- just show a checkmark and not make the full block below the line
|
||||
|
||||
let gutter_width = cx.gutter_dimensions.width;
|
||||
@ -101,6 +102,7 @@ impl EditorBlock {
|
||||
.pl(gutter_width)
|
||||
.child(
|
||||
div()
|
||||
.text_size(text_font_size)
|
||||
.font_family(text_font)
|
||||
// .ml(gutter_width)
|
||||
.mx_1()
|
||||
|
Loading…
Reference in New Issue
Block a user