use ui::{ ActiveTheme as _, AnyElement, Button, ButtonCommon as _, ButtonSize, ButtonStyle, Clickable as _, Context, ElementId, IntoElement as _, ParentElement as _, SharedString, Styled as _, StyledTypography as _, Tooltip, div, }; use crate::{ CsvPreviewView, settings::RowIdentifiers, types::{DataRow, DisplayRow, LineNumber}, }; pub enum RowIdentDisplayMode { /// E.g /// ```text /// 1 /// ... /// 5 /// ``` Vertical, /// E.g. /// ```text /// 1-5 /// ``` Horizontal, } impl LineNumber { pub fn display_string(&self, mode: RowIdentDisplayMode) -> String { match *self { LineNumber::Line(line) => line.to_string(), LineNumber::LineRange(start, end) => match mode { RowIdentDisplayMode::Vertical => { if start + 1 == end { format!("{start}\n{end}") } else { format!("{start}\n-\n{end}") } } RowIdentDisplayMode::Horizontal => { format!("{start}-{end}") } }, } } } impl CsvPreviewView { /// Calculate the optimal width for the row identifier column (line numbers or row numbers). /// /// This ensures the column is wide enough to display the largest identifier comfortably, /// but not wastefully wide for small files. pub(crate) fn calculate_row_identifier_column_width(&self) -> f32 { match self.settings.numbering_type { RowIdentifiers::SrcLines => self.calculate_line_number_width(), RowIdentifiers::RowNum => self.calculate_row_number_width(), } } /// Calculate width needed for line numbers (can be multi-line) fn calculate_line_number_width(&self) -> f32 { // Find the maximum line number that could be displayed let max_line_number = self .engine .contents .line_numbers .iter() .map(|ln| match ln { LineNumber::Line(n) => *n, LineNumber::LineRange(_, end) => *end, }) .max() .unwrap_or_default(); let digit_count = if max_line_number == 0 { 1 } else { (max_line_number as f32).log10().floor() as usize + 1 }; 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; let min_width = 60.0; (base_width + padding).max(min_width) } /// Calculate width needed for sequential row numbers fn calculate_row_number_width(&self) -> f32 { let max_row_number = self.engine.contents.rows.len(); let digit_count = if max_row_number == 0 { 1 } else { (max_row_number as f32).log10().floor() as usize + 1 }; 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; let min_width = 60.0; (base_width + padding).max(min_width) } pub(crate) fn create_row_identifier_header( &self, cx: &mut Context<'_, CsvPreviewView>, ) -> AnyElement { // First column: row identifier (clickable to toggle between Lines and Rows) let row_identifier_text = match self.settings.numbering_type { RowIdentifiers::SrcLines => "Lines", RowIdentifiers::RowNum => "Rows", }; let view = cx.entity(); let value = div() .font_buffer(cx) .child( Button::new( ElementId::Name("row-identifier-toggle".into()), row_identifier_text, ) .style(ButtonStyle::Subtle) .size(ButtonSize::Compact) .tooltip(Tooltip::text( "Toggle between: file line numbers or sequential row numbers", )) .on_click(move |_event, _window, cx| { view.update(cx, |this, cx| { this.settings.numbering_type = match this.settings.numbering_type { RowIdentifiers::SrcLines => RowIdentifiers::RowNum, RowIdentifiers::RowNum => RowIdentifiers::SrcLines, }; this.sync_column_widths(cx); cx.notify(); }); }), ) .into_any_element(); value } pub(crate) fn create_row_identifier_cell( &self, display_row: DisplayRow, data_row: DataRow, cx: &Context<'_, CsvPreviewView>, ) -> Option { let row_identifier: SharedString = match self.settings.numbering_type { RowIdentifiers::SrcLines => self .engine .contents .line_numbers .get(*data_row)? .display_string(if self.settings.multiline_cells_effectively_enabled() { RowIdentDisplayMode::Vertical } else { RowIdentDisplayMode::Horizontal }) .into(), RowIdentifiers::RowNum => (*display_row + 1).to_string().into(), }; let value = div() .flex() .px_1() .border_color(cx.theme().colors().border_variant) .bg(cx.theme().colors().panel_background) .h_full() .text_color(cx.theme().colors().text_muted) .justify_center() .items_center() .font_buffer(cx) .text_ui(cx) .child(row_identifier) .into_any_element(); Some(value) } }