1use crate::types::TableCell;
2use gpui::{AnyElement, Entity};
3use std::ops::Range;
4use ui::Table;
5use ui::TableColumnWidths;
6use ui::TableResizeBehavior;
7use ui::UncheckedTableRow;
8use ui::{DefiniteLength, div, prelude::*};
9
10use crate::{
11 CsvPreviewView,
12 settings::RowRenderMechanism,
13 types::{AnyColumn, DisplayCellId, DisplayRow},
14};
15
16impl CsvPreviewView {
17 /// Creates a new table.
18 /// Column number is derived from the `TableColumnWidths` entity.
19 pub(crate) fn create_table(
20 &self,
21 current_widths: &Entity<TableColumnWidths>,
22 cx: &mut Context<Self>,
23 ) -> AnyElement {
24 let cols = current_widths.read(cx).cols();
25 let remaining_col_number = cols - 1;
26 let fraction = if remaining_col_number > 0 {
27 1. / remaining_col_number as f32
28 } else {
29 1. // only column with line numbers is present. Put 100%, but it will be overwritten anyways :D
30 };
31 let mut widths = vec![DefiniteLength::Fraction(fraction); cols];
32 let line_number_width = self.calculate_row_identifier_column_width();
33 widths[0] = DefiniteLength::Absolute(AbsoluteLength::Pixels(line_number_width.into()));
34
35 let mut resize_behaviors = vec![TableResizeBehavior::Resizable; cols];
36 resize_behaviors[0] = TableResizeBehavior::None;
37
38 self.create_table_inner(
39 self.engine.contents.rows.len(),
40 widths,
41 resize_behaviors,
42 current_widths,
43 cx,
44 )
45 }
46
47 fn create_table_inner(
48 &self,
49 row_count: usize,
50 widths: UncheckedTableRow<DefiniteLength>,
51 resize_behaviors: UncheckedTableRow<TableResizeBehavior>,
52 current_widths: &Entity<TableColumnWidths>,
53 cx: &mut Context<Self>,
54 ) -> AnyElement {
55 let cols = widths.len();
56 // Create headers array with interactive elements
57 let mut headers = Vec::with_capacity(cols);
58
59 headers.push(self.create_row_identifier_header(cx));
60
61 // Add the actual CSV headers with sort buttons
62 for i in 0..(cols - 1) {
63 let header_text = self
64 .engine
65 .contents
66 .headers
67 .get(AnyColumn(i))
68 .and_then(|h| h.display_value().cloned())
69 .unwrap_or_else(|| format!("Col {}", i + 1).into());
70
71 headers.push(self.create_header_element_with_sort_button(
72 header_text,
73 cx,
74 AnyColumn::from(i),
75 ));
76 }
77
78 Table::new(cols)
79 .interactable(&self.table_interaction_state)
80 .striped()
81 .column_widths(widths)
82 .resizable_columns(resize_behaviors, current_widths, cx)
83 .header(headers)
84 .disable_base_style()
85 .map(|table| {
86 let row_identifier_text_color = cx.theme().colors().editor_line_number;
87 match self.settings.rendering_with {
88 RowRenderMechanism::VariableList => {
89 table.variable_row_height_list(row_count, self.list_state.clone(), {
90 cx.processor(move |this, display_row: usize, _window, cx| {
91 this.performance_metrics.rendered_indices.push(display_row);
92
93 let display_row = DisplayRow(display_row);
94 Self::render_single_table_row(
95 this,
96 cols,
97 display_row,
98 row_identifier_text_color,
99 cx,
100 )
101 .unwrap_or_else(|| panic!("Expected to render a table row"))
102 })
103 })
104 }
105 RowRenderMechanism::UniformList => {
106 table.uniform_list("csv-table", row_count, {
107 cx.processor(move |this, range: Range<usize>, _window, cx| {
108 // Record all display indices in the range for performance metrics
109 this.performance_metrics
110 .rendered_indices
111 .extend(range.clone());
112
113 range
114 .filter_map(|display_index| {
115 Self::render_single_table_row(
116 this,
117 cols,
118 DisplayRow(display_index),
119 row_identifier_text_color,
120 cx,
121 )
122 })
123 .collect()
124 })
125 })
126 }
127 }
128 })
129 .into_any_element()
130 }
131
132 /// Render a single table row
133 ///
134 /// Used both by UniformList and VariableRowHeightList
135 fn render_single_table_row(
136 this: &CsvPreviewView,
137 cols: usize,
138 display_row: DisplayRow,
139 row_identifier_text_color: gpui::Hsla,
140 cx: &Context<CsvPreviewView>,
141 ) -> Option<UncheckedTableRow<AnyElement>> {
142 // Get the actual row index from our sorted indices
143 let data_row = this.engine.d2d_mapping().get_data_row(display_row)?;
144 let row = this.engine.contents.get_row(data_row)?;
145
146 let mut elements = Vec::with_capacity(cols);
147 elements.push(this.create_row_identifier_cell(display_row, data_row, cx)?);
148
149 // Remaining columns: actual CSV data
150 for col in (0..this.engine.contents.number_of_cols).map(AnyColumn) {
151 let table_cell = row.expect_get(col);
152
153 // TODO: Introduce `<null>` cell type
154 let cell_content = table_cell.display_value().cloned().unwrap_or_default();
155
156 let display_cell_id = DisplayCellId::new(display_row, col);
157
158 let cell = div().size_full().whitespace_nowrap().text_ellipsis().child(
159 CsvPreviewView::create_selectable_cell(
160 display_cell_id,
161 cell_content,
162 this.settings.vertical_alignment,
163 this.settings.font_type,
164 cx,
165 ),
166 );
167
168 elements.push(
169 div()
170 .size_full()
171 .when(this.settings.show_debug_info, |parent| {
172 parent.child(div().text_color(row_identifier_text_color).child(
173 match table_cell {
174 TableCell::Real { position: pos, .. } => {
175 let slv = pos.start.timestamp().value;
176 let so = pos.start.offset;
177 let elv = pos.end.timestamp().value;
178 let eo = pos.end.offset;
179 format!("Pos {so}(L{slv})-{eo}(L{elv})")
180 }
181 TableCell::Virtual => "Virtual cell".into(),
182 },
183 ))
184 })
185 .text_ui(cx)
186 .child(cell)
187 .into_any_element(),
188 );
189 }
190
191 Some(elements)
192 }
193}