diff --git a/.gitignore b/.gitignore index cf75babbcd..43a48ce78c 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,5 @@ crates/docs_preprocessor/actions.json # NixOS integration test state .nixos-test-history + +.local* diff --git a/crates/csv_preview/src/csv_preview.rs b/crates/csv_preview/src/csv_preview.rs index 8f8ba76875..cc86fc63dd 100644 --- a/crates/csv_preview/src/csv_preview.rs +++ b/crates/csv_preview/src/csv_preview.rs @@ -49,6 +49,10 @@ pub struct CsvPreviewView { /// Performance metrics for debugging and monitoring CSV operations. pub(crate) performance_metrics: PerformanceMetrics, pub(crate) list_state: gpui::ListState, + /// Cached row height, refreshed from the actual text line height on every render. + /// Used to size not-yet-rendered rows for the scrollbar without a full `.measure_all()` + /// pass, so it tracks the real row height instead of a hardcoded guess. + pub(crate) row_height: Pixels, /// Time when the last parsing operation ended, used for smart debouncing pub(crate) last_parse_end_time: Option, } @@ -94,7 +98,7 @@ impl CsvPreviewView { .and_then(|item| item.act_as::(cx)) .filter(|editor| Self::is_csv_file(editor, cx)) { - let csv_preview = Self::new(&editor, cx); + let csv_preview = Self::new(&editor, window, cx); workspace.active_pane().update(cx, |pane, cx| { let existing = pane .items_of_type::() @@ -115,7 +119,7 @@ impl CsvPreviewView { .and_then(|item| item.act_as::(cx)) .filter(|editor| Self::is_csv_file(editor, cx)) { - let csv_preview = Self::new(&editor, cx); + let csv_preview = Self::new(&editor, window, cx); let pane = workspace .find_pane_in_direction(SplitDirection::Right, cx) .unwrap_or_else(|| { @@ -152,7 +156,7 @@ impl CsvPreviewView { }); } - fn new(editor: &Entity, cx: &mut Context) -> Entity { + fn new(editor: &Entity, window: &Window, cx: &mut Context) -> Entity { let contents = TableLikeContent::default(); let table_interaction_state = cx.new(|cx| { TableInteractionState::new(cx).with_custom_scrollbar(ui::Scrollbars::for_settings::< @@ -173,6 +177,7 @@ impl CsvPreviewView { }, ); + let row_height = window.pixel_snap(window.line_height()); let mut view = CsvPreviewView { focus_handle: cx.focus_handle(), active_editor_state: EditorState { @@ -186,7 +191,8 @@ impl CsvPreviewView { filter_sort_task: None, performance_metrics: PerformanceMetrics::default(), list_state: gpui::ListState::new(contents.rows.len(), ListAlignment::Top, px(1.)) - .with_uniform_item_height(px(24.)), + .with_uniform_item_height(row_height), + row_height, settings: CsvPreviewSettings::default(), last_parse_end_time: None, engine: TableDataEngine::default(), @@ -239,11 +245,10 @@ impl CsvPreviewView { this.update(cx, |view, cx| { view.engine.set_d2d_mapping(mapping); let visible_rows = view.engine.d2d_mapping().visible_row_count(); - // Approximation of single csv table row height. Will be re-measured on scrolling. - // This cheap solution allow to render scrollbar with fraction of a cost compared to `.measure_all()` call - let approximate_height = px(24.); + // Uses the row height measured on the last render. Cheaper than a full + // `.measure_all()` pass; exact row heights are re-measured on scrolling. view.list_state - .reset_with_uniform_height(visible_rows, approximate_height); + .reset_with_uniform_height(visible_rows, view.row_height); cx.notify(); }) .ok(); diff --git a/crates/csv_preview/src/renderer/preview_view.rs b/crates/csv_preview/src/renderer/preview_view.rs index 335633656a..f460142943 100644 --- a/crates/csv_preview/src/renderer/preview_view.rs +++ b/crates/csv_preview/src/renderer/preview_view.rs @@ -7,7 +7,15 @@ use crate::CsvPreviewView; impl Render for CsvPreviewView { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let theme = cx.theme(); - + let row_height = window.pixel_snap(window.line_height()); + if row_height != self.row_height { + self.row_height = row_height; + // Font size (rem size, buffer font override, ...) changed since the list was last + // measured: existing rows and unmeasured-item height hints are now the wrong size. + // Unlike `reset_with_uniform_height`, this preserves scroll position and keeps each + // item's prior size as a hint rather than dropping straight to a fresh guess. + self.list_state.remeasure(); + } let render_prep_start = Instant::now(); let table_with_settings = v_flex() .size_full() diff --git a/crates/csv_preview/src/renderer/render_table.rs b/crates/csv_preview/src/renderer/render_table.rs index 511e36abd1..9a38a3fedd 100644 --- a/crates/csv_preview/src/renderer/render_table.rs +++ b/crates/csv_preview/src/renderer/render_table.rs @@ -69,6 +69,7 @@ impl CsvPreviewView { cols, display_row, row_identifier_text_color, + this.row_height, cx, ) .unwrap_or_else(|| panic!("Expected to render a table row")) @@ -83,6 +84,7 @@ impl CsvPreviewView { .rendered_indices .extend(range.clone()); + let row_height = this.row_height; range .filter_map(|display_index| { Self::render_single_table_row( @@ -90,6 +92,7 @@ impl CsvPreviewView { cols, DisplayRow(display_index), row_identifier_text_color, + row_height, cx, ) }) @@ -110,6 +113,7 @@ impl CsvPreviewView { cols: usize, display_row: DisplayRow, row_identifier_text_color: gpui::Hsla, + row_height: Pixels, cx: &Context, ) -> Option> { // Get the actual row index from our sorted indices @@ -128,14 +132,23 @@ impl CsvPreviewView { let display_cell_id = DisplayCellId::new(display_row, col); - let cell = div().size_full().whitespace_nowrap().text_ellipsis().child( - CsvPreviewView::create_selectable_cell( + let cell = div() + .size_full() + .when( + !this.settings.multiline_cells_effectively_enabled(), + |div| { + div.whitespace_nowrap() + .text_ellipsis() + .h(row_height) + .overflow_hidden() + }, + ) + .child(CsvPreviewView::create_selectable_cell( display_cell_id, cell_content, this.settings.vertical_alignment, cx, - ), - ); + )); elements.push( div() @@ -154,7 +167,6 @@ impl CsvPreviewView { }, )) }) - .text_ui(cx) .child(cell) .into_any_element(), ); diff --git a/crates/csv_preview/src/renderer/row_identifiers.rs b/crates/csv_preview/src/renderer/row_identifiers.rs index 770e266186..b997f64576 100644 --- a/crates/csv_preview/src/renderer/row_identifiers.rs +++ b/crates/csv_preview/src/renderer/row_identifiers.rs @@ -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::{RowIdentifiers, VerticalAlignment}, + settings::RowIdentifiers, types::{DataRow, DisplayRow, LineNumber}, }; @@ -34,7 +34,7 @@ impl LineNumber { if start + 1 == end { format!("{start}\n{end}") } else { - format!("{start}\n...\n{end}") + format!("{start}\n-\n{end}") } } RowIdentDisplayMode::Horizontal => { @@ -78,11 +78,6 @@ impl CsvPreviewView { (max_line_number as f32).log10().floor() as usize + 1 }; - // if !self.settings.multiline_cells_enabled { - // // Uses horizontal line numbers layout like `123-456`. Needs twice the size - // digit_count *= 2; - // } - let char_width_px = 9.0; // TODO: get real width of the characters let base_width = (digit_count as f32) * char_width_px; let padding = 20.0; @@ -157,7 +152,7 @@ impl CsvPreviewView { .contents .line_numbers .get(*data_row)? - .display_string(if self.settings.multiline_cells_enabled { + .display_string(if self.settings.multiline_cells_effectively_enabled() { RowIdentDisplayMode::Vertical } else { RowIdentDisplayMode::Horizontal @@ -172,14 +167,11 @@ impl CsvPreviewView { .border_color(cx.theme().colors().border_variant) .bg(cx.theme().colors().panel_background) .h_full() - .text_ui(cx) .text_color(cx.theme().colors().text_muted) .justify_center() - .map(|div| match self.settings.vertical_alignment { - VerticalAlignment::Top => div.items_start(), - VerticalAlignment::Center => div.items_center(), - }) + .items_center() .font_buffer(cx) + .text_ui(cx) .child(row_identifier) .into_any_element(); Some(value) diff --git a/crates/csv_preview/src/renderer/settings.rs b/crates/csv_preview/src/renderer/settings.rs index 18c185bb0f..fe8f671f92 100644 --- a/crates/csv_preview/src/renderer/settings.rs +++ b/crates/csv_preview/src/renderer/settings.rs @@ -1,6 +1,7 @@ use ui::{ - ActiveTheme as _, AnyElement, ButtonSize, Context, ContextMenu, DropdownMenu, ElementId, - IntoElement as _, ParentElement as _, Styled as _, Tooltip, Window, div, h_flex, + ActiveTheme as _, AnyElement, ButtonSize, Checkbox, Context, ContextMenu, DropdownMenu, + ElementId, IntoElement as _, ParentElement as _, Styled as _, ToggleState, Tooltip, Window, + div, h_flex, }; use crate::{ @@ -121,6 +122,30 @@ impl CsvPreviewView { ), ); + let multiline_enabled = self.settings.multiline_cells_enabled; + let panel = panel.child({ + let view = view.clone(); + Checkbox::new( + ElementId::Name("multiline-rows-checkbox".into()), + if multiline_enabled { + ToggleState::Selected + } else { + ToggleState::Unselected + }, + ) + .label("Display multiline rows") + .tooltip(Tooltip::text( + "When enabled, row height grows to show all content. \ + When disabled, only the first line is visible — hover a cell to see the rest.", + )) + .on_click(move |_state, _window, cx| { + view.update(cx, |this, cx| { + this.settings.multiline_cells_enabled = !this.settings.multiline_cells_enabled; + cx.notify(); + }); + }) + }); + #[cfg(feature = "dev-tools")] let panel = panel.child( h_flex() @@ -171,7 +196,6 @@ fn create_dev_only_popover_menu( view_entity.update(cx, |view, cx| { view.settings.rendering_with = RowRenderMechanism::VariableList; - view.settings.multiline_cells_enabled = true; cx.notify(); }) } @@ -188,7 +212,6 @@ fn create_dev_only_popover_menu( view_entity.update(cx, |view, cx| { view.settings.rendering_with = RowRenderMechanism::UniformList; - view.settings.multiline_cells_enabled = false; cx.notify(); }) } diff --git a/crates/csv_preview/src/renderer/table_header.rs b/crates/csv_preview/src/renderer/table_header.rs index 6549cb5152..444ee09099 100644 --- a/crates/csv_preview/src/renderer/table_header.rs +++ b/crates/csv_preview/src/renderer/table_header.rs @@ -44,6 +44,7 @@ impl CsvPreviewView { .w_full() .items_center() .font_buffer(cx) + .text_buffer(cx) .child( div() .flex_1() diff --git a/crates/csv_preview/src/settings.rs b/crates/csv_preview/src/settings.rs index 2d5cc78c5f..dc871fa4b3 100644 --- a/crates/csv_preview/src/settings.rs +++ b/crates/csv_preview/src/settings.rs @@ -46,3 +46,12 @@ pub(crate) struct CsvPreviewSettings { pub(crate) show_perf_metrics_overlay: bool, pub(crate) multiline_cells_enabled: bool, } + +impl CsvPreviewSettings { + /// `multiline_cells_enabled` only makes sense with `VariableList`, which + /// supports per-row heights; `UniformList` requires every row to share one + /// height, so multiline is never honored there regardless of the setting. + pub(crate) fn multiline_cells_effectively_enabled(&self) -> bool { + self.multiline_cells_enabled && self.rendering_with == RowRenderMechanism::VariableList + } +}