csv_preview: Add single-line row displaying mode (#61127)
### Changes Adds a checkbox to toggle between multiline cells (current behavior) and single-line rows that clip overflow, so wide CSVs stay compact when full cell content isn't needed at a glance. #### Demo | Before | After | | --- | --- | | No single line mode | <img width="662" height="316" alt="image" src="https://github.com/user-attachments/assets/3f2a9673-557c-4539-b755-9ff2633de2d7" /> | | <img width="666" height="287" alt="image" src="https://github.com/user-attachments/assets/703f7214-a722-4fa5-9125-f043439e21b2" /> | <img width="660" height="317" alt="image" src="https://github.com/user-attachments/assets/be6ca912-e686-44da-90eb-58f09fab51f1" /> | ### Testing - Toggle "Display multiline rows" off: cells clip to one line with ellipsis instead of wrapping - Toggle it back on: cells wrap and grow to show full content - Resize the window / change font size: single-line clip height tracks the actual text line height instead of a stale hardcoded value Release Notes: - N/A
This commit is contained in:
1 parent
b6a1c2706e
commit
b11cce5c41
8 files changed
+85
-33
No files matched your search
@@ -58,3 +58,5 @@ crates/docs_preprocessor/actions.json
|
||||
|
||||
# NixOS integration test state
|
||||
.nixos-test-history
|
||||
|
||||
.local*
|
||||
@@ -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<std::time::Instant>,
|
||||
}
|
||||
@@ -94,7 +98,7 @@ impl CsvPreviewView {
|
||||
.and_then(|item| item.act_as::<Editor>(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::<CsvPreviewView>()
|
||||
@@ -115,7 +119,7 @@ impl CsvPreviewView {
|
||||
.and_then(|item| item.act_as::<Editor>(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<Editor>, cx: &mut Context<Workspace>) -> Entity<Self> {
|
||||
fn new(editor: &Entity<Editor>, window: &Window, cx: &mut Context<Workspace>) -> Entity<Self> {
|
||||
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();
|
||||
|
||||
@@ -7,7 +7,15 @@ use crate::CsvPreviewView;
|
||||
impl Render for CsvPreviewView {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> 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()
|
||||
|
||||
@@ -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<CsvPreviewView>,
|
||||
) -> Option<UncheckedTableRow<AnyElement>> {
|
||||
// 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(),
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ impl CsvPreviewView {
|
||||
.w_full()
|
||||
.items_center()
|
||||
.font_buffer(cx)
|
||||
.text_buffer(cx)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user