csv_preview: Add settings UI panel with debug tools during development (#53496)

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

------

This PR adds a settings panel above the CSV table with dropdown menus to
control rendering behavior, and a performance metrics overlay for
debugging.

Currently it's mostly used for me during dev phase, but before release
of CSV preview feature all dev-only options will be cleaned up, and
future features like "copy selected" will have their settings in this
bar

<img width="2260" height="1674" alt="image_2026-04-09_11-52-24"
src="https://github.com/user-attachments/assets/9039fd59-5c46-4be4-9f33-b9825a3cdce3"
/>


**What changed:**

- Adds `render_settings_panel()` method with dropdown menus for:
- **Rendering Mode**: Variable Height (multiline support) vs Uniform
Height (better performance)
  - **Text Alignment**: Top vs Center vertical alignment within cells
- **Font Type**: UI Font vs Monospace for better readability (will be
exposed to user settings)
  - **Experimental**: Popover menu with toggles for debug features
- Adds `render_performance_metrics_overlay()` method showing:
  - CSV parsing duration
  - Rendered row count and indices
  - Positioned in bottom-right corner with semi-transparent styling

Context:
Will iterate on it with @Anthony-Eid 

Release Notes:

- N/A

---------

Co-authored-by: Anthony Eid <anthony@zed.dev>
This commit is contained in:
Oleksandr Kholiavko 2026-05-13 02:44:26 +02:00 committed by GitHub
parent fe9f956460
commit ef341146d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 312 additions and 59 deletions

View file

@ -17,5 +17,8 @@ workspace.workspace = true
log.workspace = true
text.workspace = true
[features]
dev-tools = []
[lints]
workspace = true

View file

@ -179,7 +179,8 @@ impl CsvPreviewView {
column_widths: ColumnWidths::new(cx, 1),
parsing_task: None,
performance_metrics: PerformanceMetrics::default(),
list_state: gpui::ListState::new(contents.rows.len(), ListAlignment::Top, px(1.)),
list_state: gpui::ListState::new(contents.rows.len(), ListAlignment::Top, px(1.))
.measure_all(),
settings: CsvPreviewSettings::default(),
last_parse_end_time: None,
engine: TableDataEngine::default(),
@ -207,7 +208,8 @@ impl CsvPreviewView {
// Update list state with filtered row count
let visible_rows = self.engine.d2d_mapping().visible_row_count();
self.list_state = gpui::ListState::new(visible_rows, ListAlignment::Top, px(100.));
self.list_state =
gpui::ListState::new(visible_rows, ListAlignment::Top, px(100.)).measure_all();
}
pub fn resolve_active_item_as_csv_editor(

View file

@ -1,5 +1,8 @@
#[cfg(feature = "dev-tools")]
mod performance_metrics_overlay;
mod preview_view;
mod render_table;
mod row_identifiers;
mod settings;
mod table_cell;
mod table_header;

View file

@ -0,0 +1,82 @@
//! Performance metrics overlay for CSV preview debugging.
//!
//! Provides a semi-transparent overlay in the bottom-right corner showing
//! CSV parsing performance metrics for developer experience.
use ui::{ActiveTheme, Context, IntoElement, ParentElement, Styled, StyledTypography, div};
use crate::{CsvPreviewView, PerformanceMetrics};
impl CsvPreviewView {
/// Renders a semi-transparent performance metrics overlay in the bottom-right corner.
///
/// Shows CSV parsing duration for debugging and performance monitoring.
/// The overlay is positioned absolutely and styled with reduced opacity.
pub(crate) fn render_performance_metrics_overlay(
&mut self,
cx: &mut Context<Self>,
) -> impl IntoElement {
let theme = cx.theme();
let children = div()
.absolute()
.top_24()
.right_4()
.px_3()
.py_2()
.bg(theme.colors().editor_background)
.border_1()
.border_color(theme.colors().border)
.rounded_md()
.opacity(0.75)
.text_xs()
.font_buffer(cx)
.text_color(theme.colors().text_muted)
.flex()
.flex_col()
.gap_1()
.child("Performance metrics:")
.children(
format_performance_metrics(&self.performance_metrics)
.into_iter()
.map(|line| div().child(line)),
);
// Clear rendered indices to prepare for next frame
self.performance_metrics.rendered_indices.clear();
children
}
}
fn format_performance_metrics(metrics: &PerformanceMetrics) -> Vec<String> {
let mut lines = Vec::new();
// Add timing metrics using the display method
let timing_display = metrics.display();
if !timing_display.is_empty() {
lines.extend(timing_display.lines().map(|line| format!("- {}", line)));
} else {
lines.push("- No timing data yet".to_string());
}
// Add rendered indices information
if metrics.rendered_indices.is_empty() {
lines.push("- Rendered: none".to_string());
} else {
lines.push(format!(
"- Rendered: {} rows",
metrics.rendered_indices.len()
));
if metrics.rendered_indices.len() <= 20 {
// Show indices if not too many
lines.push(format!(" {:?}", metrics.rendered_indices));
} else {
// Show first/last few if too many
let first_few = &metrics.rendered_indices[..5];
let last_few = &metrics.rendered_indices[metrics.rendered_indices.len() - 5..];
lines.push(format!(" {:?}\n..{:?}", first_few, last_few));
}
}
lines
}

View file

@ -2,19 +2,19 @@ use std::time::Instant;
use ui::{div, prelude::*};
use crate::{CsvPreviewView, settings::FontType};
use crate::CsvPreviewView;
impl Render for CsvPreviewView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = cx.theme();
self.performance_metrics.rendered_indices.clear();
let render_prep_start = Instant::now();
let table_with_settings = v_flex()
.size_full()
.p_4()
.bg(theme.colors().editor_background)
.track_focus(&self.focus_handle)
.child(self.render_settings_panel(window, cx))
.child({
if self.engine.contents.number_of_cols == 0 {
div()
@ -23,10 +23,7 @@ impl Render for CsvPreviewView {
.justify_center()
.h_32()
.text_ui(cx)
.map(|div| match self.settings.font_type {
FontType::Ui => div.font_ui(cx),
FontType::Monospace => div.font_buffer(cx),
})
.font_buffer(cx)
.text_color(cx.theme().colors().text_muted)
.child("No CSV content to display")
.into_any_element()
@ -41,10 +38,28 @@ impl Render for CsvPreviewView {
(render_prep_duration, std::time::Instant::now()),
);
div()
let div = div()
.relative()
.w_full()
.h_full()
.child(table_with_settings)
.child(table_with_settings);
#[cfg(feature = "dev-tools")]
let show_perf_metrics_overlay = self.settings.show_perf_metrics_overlay;
#[cfg(feature = "dev-tools")]
let div = div.when(show_perf_metrics_overlay, |div| {
div.child(self.render_performance_metrics_overlay(cx))
});
#[cfg(feature = "dev-tools")]
if !show_perf_metrics_overlay {
self.performance_metrics.rendered_indices.clear();
}
#[cfg(not(feature = "dev-tools"))]
self.performance_metrics.rendered_indices.clear();
div
}
}

View file

@ -133,7 +133,6 @@ impl CsvPreviewView {
display_cell_id,
cell_content,
this.settings.vertical_alignment,
this.settings.font_type,
cx,
),
);

View file

@ -1,12 +1,12 @@
use ui::{
ActiveTheme as _, AnyElement, Button, ButtonCommon as _, ButtonSize, ButtonStyle,
Clickable as _, Context, ElementId, FluentBuilder as _, IntoElement as _, ParentElement as _,
SharedString, Styled as _, StyledTypography as _, Tooltip, div,
Clickable as _, Context, ElementId, IntoElement as _, ParentElement as _, SharedString,
Styled as _, StyledTypography as _, Tooltip, div,
};
use crate::{
CsvPreviewView,
settings::{FontType, RowIdentifiers},
settings::RowIdentifiers,
types::{DataRow, DisplayRow, LineNumber},
};
@ -119,10 +119,7 @@ impl CsvPreviewView {
let view = cx.entity();
let value = div()
.map(|div| match self.settings.font_type {
FontType::Ui => div.font_ui(cx),
FontType::Monospace => div.font_buffer(cx),
})
.font_buffer(cx)
.child(
Button::new(
ElementId::Name("row-identifier-toggle".into()),
@ -179,10 +176,7 @@ impl CsvPreviewView {
// Row identifiers are always centered
.items_center()
.justify_end()
.map(|div| match self.settings.font_type {
FontType::Ui => div.font_ui(cx),
FontType::Monospace => div.font_buffer(cx),
})
.font_buffer(cx)
.child(row_identifier)
.into_any_element();
Some(value)

View file

@ -0,0 +1,182 @@
use ui::{
ActiveTheme as _, AnyElement, ButtonSize, Context, ContextMenu, DropdownMenu, ElementId,
IntoElement as _, ParentElement as _, Styled as _, Tooltip, Window, div, h_flex,
};
use crate::{CsvPreviewView, settings::VerticalAlignment};
///// Settings related /////
impl CsvPreviewView {
/// Render settings panel above the table
pub(crate) fn render_settings_panel(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> AnyElement {
let current_alignment_text = match self.settings.vertical_alignment {
VerticalAlignment::Top => "Top",
VerticalAlignment::Center => "Center",
};
let view = cx.entity();
let alignment_dropdown_menu = ContextMenu::build(window, cx, |menu, _window, _cx| {
menu.entry("Top", None, {
let view = view.clone();
move |_window, cx| {
view.update(cx, |this, cx| {
this.settings.vertical_alignment = VerticalAlignment::Top;
cx.notify();
});
}
})
.entry("Center", None, {
let view = view.clone();
move |_window, cx| {
view.update(cx, |this, cx| {
this.settings.vertical_alignment = VerticalAlignment::Center;
cx.notify();
});
}
})
});
let panel = h_flex()
.gap_4()
.p_2()
.bg(cx.theme().colors().surface_background)
.border_b_1()
.border_color(cx.theme().colors().border)
.flex_wrap()
.child(
h_flex()
.gap_2()
.items_center()
.child(
div()
.text_sm()
.text_color(cx.theme().colors().text_muted)
.child("Text Alignment:"),
)
.child(
DropdownMenu::new(
ElementId::Name("vertical-alignment-dropdown".into()),
current_alignment_text,
alignment_dropdown_menu,
)
.trigger_size(ButtonSize::Compact)
.trigger_tooltip(Tooltip::text(
"Choose vertical text alignment within cells",
)),
),
);
#[cfg(feature = "dev-tools")]
let panel = panel.child(
h_flex()
.gap_2()
.items_center()
.child(
div()
.text_sm()
.text_color(cx.theme().colors().text_muted)
.child("Dev-only:"),
)
.child(create_dev_only_popover_menu(cx)),
);
panel.into_any_element()
}
}
#[cfg(feature = "dev-tools")]
fn create_dev_only_popover_menu(
cx: &mut Context<'_, CsvPreviewView>,
) -> ui::PopoverMenu<ContextMenu> {
use crate::settings::RowRenderMechanism;
use ui::{IconButton, IconName, IconPosition, IconSize, PopoverMenu};
PopoverMenu::new("debug-options-menu")
.trigger_with_tooltip(
IconButton::new("debug-options-trigger", IconName::Settings).icon_size(IconSize::Small),
Tooltip::text(
"Dev-only section used for debugging purposes.\nWill be removed on public release of CSV feature"
),
)
.menu({
let view_entity = cx.entity();
move |window, cx| {
let view = view_entity.read(cx);
let settings = view.settings.clone();
Some(ContextMenu::build(window, cx, |menu, _, _| {
menu.header("Rendering Mode")
.toggleable_entry(
"Variable Height",
settings.rendering_with == RowRenderMechanism::VariableList,
IconPosition::Start,
None,
{
let view_entity = view_entity.clone();
move |_w, cx| {
view_entity.update(cx, |view, cx| {
view.settings.rendering_with =
RowRenderMechanism::VariableList;
view.settings.multiline_cells_enabled = true;
cx.notify();
})
}
},
)
.toggleable_entry(
"Uniform Height",
settings.rendering_with == RowRenderMechanism::UniformList,
IconPosition::Start,
None,
{
let view_entity = view_entity.clone();
move |_w, cx| {
view_entity.update(cx, |view, cx| {
view.settings.rendering_with =
RowRenderMechanism::UniformList;
view.settings.multiline_cells_enabled = false;
cx.notify();
})
}
},
)
.separator()
.toggleable_entry(
"Show perf metrics",
settings.show_perf_metrics_overlay,
IconPosition::Start,
None,
{
let view_entity = view_entity.clone();
move |_w, cx| {
view_entity.update(cx, |view, cx| {
view.settings.show_perf_metrics_overlay =
!view.settings.show_perf_metrics_overlay;
cx.notify();
})
}
},
)
.toggleable_entry(
"Show cell positions",
settings.show_debug_info,
IconPosition::Start,
None,
{
let view_entity = view_entity.clone();
move |_, cx| {
view_entity.update(cx, |view, cx| {
view.settings.show_debug_info =
!view.settings.show_debug_info;
cx.notify();
})
}
},
)
}))
}
})
}

View file

@ -3,11 +3,7 @@
use gpui::{AnyElement, ElementId};
use ui::{SharedString, Tooltip, div, prelude::*};
use crate::{
CsvPreviewView,
settings::{FontType, VerticalAlignment},
types::DisplayCellId,
};
use crate::{CsvPreviewView, settings::VerticalAlignment, types::DisplayCellId};
impl CsvPreviewView {
/// Create selectable table cell with mouse event handlers.
@ -15,18 +11,11 @@ impl CsvPreviewView {
display_cell_id: DisplayCellId,
cell_content: SharedString,
vertical_alignment: VerticalAlignment,
font_type: FontType,
cx: &Context<CsvPreviewView>,
) -> AnyElement {
create_table_cell(
display_cell_id,
cell_content,
vertical_alignment,
font_type,
cx,
)
// Mouse events handlers will be here
.into_any_element()
create_table_cell(display_cell_id, cell_content, vertical_alignment, cx)
// Mouse events handlers will be here
.into_any_element()
}
}
@ -35,7 +24,6 @@ fn create_table_cell(
display_cell_id: DisplayCellId,
cell_content: SharedString,
vertical_alignment: VerticalAlignment,
font_type: FontType,
cx: &Context<'_, CsvPreviewView>,
) -> gpui::Stateful<Div> {
div()
@ -61,10 +49,7 @@ fn create_table_cell(
VerticalAlignment::Top => div.content_start(),
VerticalAlignment::Center => div.content_center(),
})
.map(|div| match font_type {
FontType::Ui => div.font_ui(cx),
FontType::Monospace => div.font_buffer(cx),
})
.font_buffer(cx)
.tooltip(Tooltip::text(cell_content.clone()))
.child(div().child(cell_content))
}

View file

@ -3,7 +3,6 @@ use ui::{Tooltip, prelude::*};
use crate::{
CsvPreviewView,
settings::FontType,
table_data_engine::sorting_by_column::{AppliedSorting, SortDirection},
types::AnyColumn,
};
@ -21,10 +20,7 @@ impl CsvPreviewView {
.justify_between()
.items_center()
.w_full()
.map(|div| match self.settings.font_type {
FontType::Ui => div.font_ui(cx),
FontType::Monospace => div.font_buffer(cx),
})
.font_buffer(cx)
.child(div().child(header_text))
.child(h_flex().gap_1().child(self.create_sort_button(cx, col_idx)))
.into_any_element()

View file

@ -1,4 +1,4 @@
#[derive(Default, Clone, Copy)]
#[derive(Default, Clone, Copy, PartialEq)]
pub enum RowRenderMechanism {
/// More correct for multiline content, but slower.
#[allow(dead_code)] // Will be used when settings ui is added
@ -17,15 +17,6 @@ pub enum VerticalAlignment {
Center,
}
#[derive(Default, Clone, Copy)]
pub enum FontType {
/// Use the default UI font
#[default]
Ui,
/// Use monospace font (same as buffer/editor font)
Monospace,
}
#[derive(Default, Clone, Copy)]
pub enum RowIdentifiers {
/// Show original line numbers from CSV file
@ -39,8 +30,9 @@ pub enum RowIdentifiers {
pub(crate) struct CsvPreviewSettings {
pub(crate) rendering_with: RowRenderMechanism,
pub(crate) vertical_alignment: VerticalAlignment,
pub(crate) font_type: FontType,
pub(crate) numbering_type: RowIdentifiers,
pub(crate) show_debug_info: bool,
#[cfg(feature = "dev-tools")]
pub(crate) show_perf_metrics_overlay: bool,
pub(crate) multiline_cells_enabled: bool,
}