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}