row_identifiers.rs

  1use ui::{
  2    ActiveTheme as _, AnyElement, Button, ButtonCommon as _, ButtonSize, ButtonStyle,
  3    Clickable as _, Context, ElementId, FluentBuilder as _, IntoElement as _, ParentElement as _,
  4    SharedString, Styled as _, StyledTypography as _, Tooltip, div,
  5};
  6
  7use crate::{
  8    CsvPreviewView,
  9    settings::{FontType, RowIdentifiers},
 10    types::{DataRow, DisplayRow, LineNumber},
 11};
 12
 13pub enum RowIdentDisplayMode {
 14    /// E.g
 15    /// ```text
 16    /// 1
 17    /// ...
 18    /// 5
 19    /// ```
 20    Vertical,
 21    /// E.g.
 22    /// ```text
 23    /// 1-5
 24    /// ```
 25    Horizontal,
 26}
 27
 28impl LineNumber {
 29    pub fn display_string(&self, mode: RowIdentDisplayMode) -> String {
 30        match *self {
 31            LineNumber::Line(line) => line.to_string(),
 32            LineNumber::LineRange(start, end) => match mode {
 33                RowIdentDisplayMode::Vertical => {
 34                    if start + 1 == end {
 35                        format!("{start}\n{end}")
 36                    } else {
 37                        format!("{start}\n...\n{end}")
 38                    }
 39                }
 40                RowIdentDisplayMode::Horizontal => {
 41                    format!("{start}-{end}")
 42                }
 43            },
 44        }
 45    }
 46}
 47
 48impl CsvPreviewView {
 49    /// Calculate the optimal width for the row identifier column (line numbers or row numbers).
 50    ///
 51    /// This ensures the column is wide enough to display the largest identifier comfortably,
 52    /// but not wastefully wide for small files.
 53    pub(crate) fn calculate_row_identifier_column_width(&self) -> f32 {
 54        match self.settings.numbering_type {
 55            RowIdentifiers::SrcLines => self.calculate_line_number_width(),
 56            RowIdentifiers::RowNum => self.calculate_row_number_width(),
 57        }
 58    }
 59
 60    /// Calculate width needed for line numbers (can be multi-line)
 61    fn calculate_line_number_width(&self) -> f32 {
 62        // Find the maximum line number that could be displayed
 63        let max_line_number = self
 64            .engine
 65            .contents
 66            .line_numbers
 67            .iter()
 68            .map(|ln| match ln {
 69                LineNumber::Line(n) => *n,
 70                LineNumber::LineRange(_, end) => *end,
 71            })
 72            .max()
 73            .unwrap_or_default();
 74
 75        let digit_count = if max_line_number == 0 {
 76            1
 77        } else {
 78            (max_line_number as f32).log10().floor() as usize + 1
 79        };
 80
 81        // if !self.settings.multiline_cells_enabled {
 82        //     // Uses horizontal line numbers layout like `123-456`. Needs twice the size
 83        //     digit_count *= 2;
 84        // }
 85
 86        let char_width_px = 9.0; // TODO: get real width of the characters
 87        let base_width = (digit_count as f32) * char_width_px;
 88        let padding = 20.0;
 89        let min_width = 60.0;
 90        (base_width + padding).max(min_width)
 91    }
 92
 93    /// Calculate width needed for sequential row numbers
 94    fn calculate_row_number_width(&self) -> f32 {
 95        let max_row_number = self.engine.contents.rows.len();
 96
 97        let digit_count = if max_row_number == 0 {
 98            1
 99        } else {
100            (max_row_number as f32).log10().floor() as usize + 1
101        };
102
103        let char_width_px = 9.0; // TODO: get real width of the characters
104        let base_width = (digit_count as f32) * char_width_px;
105        let padding = 20.0;
106        let min_width = 60.0;
107        (base_width + padding).max(min_width)
108    }
109
110    pub(crate) fn create_row_identifier_header(
111        &self,
112        cx: &mut Context<'_, CsvPreviewView>,
113    ) -> AnyElement {
114        // First column: row identifier (clickable to toggle between Lines and Rows)
115        let row_identifier_text = match self.settings.numbering_type {
116            RowIdentifiers::SrcLines => "Lines",
117            RowIdentifiers::RowNum => "Rows",
118        };
119
120        let view = cx.entity();
121        let value = div()
122            .map(|div| match self.settings.font_type {
123                FontType::Ui => div.font_ui(cx),
124                FontType::Monospace => div.font_buffer(cx),
125            })
126            .child(
127                Button::new(
128                    ElementId::Name("row-identifier-toggle".into()),
129                    row_identifier_text,
130                )
131                .style(ButtonStyle::Subtle)
132                .size(ButtonSize::Compact)
133                .tooltip(Tooltip::text(
134                    "Toggle between: file line numbers or sequential row numbers",
135                ))
136                .on_click(move |_event, _window, cx| {
137                    view.update(cx, |this, cx| {
138                        this.settings.numbering_type = match this.settings.numbering_type {
139                            RowIdentifiers::SrcLines => RowIdentifiers::RowNum,
140                            RowIdentifiers::RowNum => RowIdentifiers::SrcLines,
141                        };
142                        cx.notify();
143                    });
144                }),
145            )
146            .into_any_element();
147        value
148    }
149
150    pub(crate) fn create_row_identifier_cell(
151        &self,
152        display_row: DisplayRow,
153        data_row: DataRow,
154        cx: &Context<'_, CsvPreviewView>,
155    ) -> Option<AnyElement> {
156        let row_identifier: SharedString = match self.settings.numbering_type {
157            RowIdentifiers::SrcLines => self
158                .engine
159                .contents
160                .line_numbers
161                .get(*data_row)?
162                .display_string(if self.settings.multiline_cells_enabled {
163                    RowIdentDisplayMode::Vertical
164                } else {
165                    RowIdentDisplayMode::Horizontal
166                })
167                .into(),
168            RowIdentifiers::RowNum => (*display_row + 1).to_string().into(),
169        };
170
171        let value = div()
172            .flex()
173            .px_1()
174            .border_b_1()
175            .border_color(cx.theme().colors().border_variant)
176            .h_full()
177            .text_ui(cx)
178            // Row identifiers are always centered
179            .items_center()
180            .justify_end()
181            .map(|div| match self.settings.font_type {
182                FontType::Ui => div.font_ui(cx),
183                FontType::Monospace => div.font_buffer(cx),
184            })
185            .child(row_identifier)
186            .into_any_element();
187        Some(value)
188    }
189}