1use crate::app_state_container::AppStateContainer;
2use crate::buffer::BufferAPI;
3use crate::data_exporter::DataExporter;
4use anyhow::{anyhow, Result};
5use serde_json::Value;
6use tracing::trace;
7
8pub struct YankManager;
10
11pub struct YankResult {
13 pub description: String,
14 pub preview: String,
15 pub full_value: String,
16}
17
18impl YankManager {
19 pub fn yank_cell(
21 buffer: &dyn BufferAPI,
22 state_container: &AppStateContainer,
23 row_index: usize,
24 column_index: usize,
25 ) -> Result<YankResult> {
26 let (value, header, actual_row_index) = if let Some(dataview) = buffer.get_dataview() {
28 trace!(
29 "yank_cell: Using DataView for cell at visual_row={}, col={}",
30 row_index,
31 column_index
32 );
33
34 let value = dataview
37 .get_cell_value(row_index, column_index)
38 .unwrap_or_else(|| "NULL".to_string());
39
40 let headers = dataview.column_names();
41 let header = headers
42 .get(column_index)
43 .ok_or_else(|| anyhow!("Column index out of bounds"))?
44 .clone();
45
46 let actual_row =
49 if let Some(filtered_idx) = dataview.visible_row_indices().get(row_index) {
50 *filtered_idx
51 } else {
52 row_index
53 };
54
55 (value, header, actual_row)
56 } else if let Some(datatable) = buffer.get_datatable() {
57 trace!(
58 "yank_cell: Using DataTable for cell at row={}, col={}",
59 row_index,
60 column_index
61 );
62
63 let row_data = datatable
64 .get_row_as_strings(row_index)
65 .ok_or_else(|| anyhow!("Row index out of bounds"))?;
66
67 let headers = datatable.column_names();
68 let header = headers
69 .get(column_index)
70 .ok_or_else(|| anyhow!("Column index out of bounds"))?
71 .clone();
72
73 let value = row_data
74 .get(column_index)
75 .cloned()
76 .unwrap_or_else(|| "NULL".to_string());
77
78 (value, header, row_index)
79 } else {
80 return Err(anyhow!("No data available"));
81 };
82
83 let col_name = header.to_string();
85 let display_value = if value.len() > 20 {
86 format!("{}...", &value[..17])
87 } else {
88 value.clone()
89 };
90
91 let clipboard_len = value.len();
93 state_container.yank_cell(
94 actual_row_index,
95 column_index,
96 value.clone(),
97 display_value.clone(),
98 )?;
99
100 Ok(YankResult {
101 description: format!("{} ({} chars)", col_name, clipboard_len),
102 preview: display_value,
103 full_value: value,
104 })
105 }
106
107 pub fn yank_row(
109 buffer: &dyn BufferAPI,
110 state_container: &AppStateContainer,
111 row_index: usize,
112 ) -> Result<YankResult> {
113 let (row_data, actual_row_index) = if let Some(dataview) = buffer.get_dataview() {
115 trace!("yank_row: Using DataView for row {}", row_index);
116 let data = dataview
117 .get_row_values(row_index)
118 .ok_or_else(|| anyhow!("Row index out of bounds"))?;
119
120 let actual_row =
122 if let Some(filtered_idx) = dataview.visible_row_indices().get(row_index) {
123 *filtered_idx
124 } else {
125 row_index
126 };
127
128 (data, actual_row)
129 } else if let Some(datatable) = buffer.get_datatable() {
130 trace!("yank_row: Using DataTable for row {}", row_index);
131 let data = datatable
132 .get_row_as_strings(row_index)
133 .ok_or_else(|| anyhow!("Row index out of bounds"))?;
134 (data, row_index)
135 } else {
136 return Err(anyhow!("No data available"));
137 };
138
139 let row_text = row_data.join("\t");
141
142 let num_values = row_data.len();
144
145 let clipboard_len = row_text.len();
147 state_container.yank_row(
148 actual_row_index,
149 row_text.clone(),
150 format!("{} values", num_values),
151 )?;
152
153 Ok(YankResult {
154 description: format!("Row {} ({} chars)", row_index + 1, clipboard_len),
155 preview: format!("{} values", num_values),
156 full_value: row_text,
157 })
158 }
159
160 pub fn yank_column(
162 buffer: &dyn BufferAPI,
163 state_container: &AppStateContainer,
164 column_index: usize,
165 ) -> Result<YankResult> {
166 let (column_values, header) = if let Some(dataview) = buffer.get_dataview() {
168 let headers = dataview.column_names();
169 let header = headers
170 .get(column_index)
171 .ok_or_else(|| anyhow!("Column index out of bounds"))?
172 .clone();
173
174 trace!(
175 "yank_column: Using DataView for column {} ({}), visible rows: {}",
176 column_index,
177 header,
178 dataview.row_count()
179 );
180
181 let values = dataview.get_column_values(column_index);
182 (values, header)
183 } else if let Some(datatable) = buffer.get_datatable() {
184 let headers = datatable.column_names();
186 let header = headers
187 .get(column_index)
188 .ok_or_else(|| anyhow!("Column index out of bounds"))?
189 .clone();
190
191 trace!(
192 "yank_column: Using DataTable for column {} ({}), total rows: {}",
193 column_index,
194 header,
195 datatable.row_count()
196 );
197
198 let mut column_values = Vec::new();
200 if buffer.is_fuzzy_filter_active() {
201 let filtered_indices = buffer.get_fuzzy_filter_indices();
202 trace!(
203 "yank_column: Filter active, yanking {} filtered rows",
204 filtered_indices.len()
205 );
206
207 for &row_idx in filtered_indices {
208 if let Some(row_data) = datatable.get_row_as_strings(row_idx) {
209 let value = row_data
210 .get(column_index)
211 .cloned()
212 .unwrap_or_else(|| "NULL".to_string())
213 .replace('\t', " ")
214 .replace('\n', " ")
215 .replace('\r', "");
216 column_values.push(value);
217 }
218 }
219 } else {
220 trace!(
221 "yank_column: No filter, yanking all {} rows",
222 datatable.row_count()
223 );
224
225 for row_idx in 0..datatable.row_count() {
226 if let Some(row_data) = datatable.get_row_as_strings(row_idx) {
227 let value = row_data
228 .get(column_index)
229 .cloned()
230 .unwrap_or_else(|| "NULL".to_string())
231 .replace('\t', " ")
232 .replace('\n', " ")
233 .replace('\r', "");
234 column_values.push(value);
235 }
236 }
237 }
238
239 (column_values, header)
240 } else {
241 return Err(anyhow!("No data available"));
242 };
243
244 let column_text = column_values.join("\r\n");
246
247 let preview = if column_values.len() > 5 {
248 format!("{} values", column_values.len())
249 } else {
250 column_values.join(", ")
251 };
252
253 let clipboard_len = column_text.len();
255 state_container.yank_column(
256 header.to_string(),
257 column_index,
258 column_text.clone(),
259 preview.clone(),
260 )?;
261
262 Ok(YankResult {
263 description: format!("Column '{}' ({} chars)", header, clipboard_len),
264 preview,
265 full_value: column_text,
266 })
267 }
268
269 pub fn yank_all(
271 buffer: &dyn BufferAPI,
272 state_container: &AppStateContainer,
273 ) -> Result<YankResult> {
274 let tsv_text = if let Some(dataview) = buffer.get_dataview() {
276 dataview.to_tsv()?
278 } else if let Some(datatable) = buffer.get_datatable() {
279 let data = Self::datatable_to_json(datatable)?;
281 DataExporter::generate_tsv_text(&data)
282 .ok_or_else(|| anyhow!("Failed to generate TSV"))?
283 } else {
284 return Err(anyhow!("No data available"));
285 };
286
287 let clipboard_len = tsv_text.len();
289
290 let (row_count, col_count, filter_info) = if let Some(dataview) = buffer.get_dataview() {
292 let rows = dataview.row_count();
294 let cols = dataview.column_count();
295 let filtered = dataview.has_filter();
296 (rows, cols, if filtered { " (filtered)" } else { "" })
297 } else if let Some(datatable) = buffer.get_datatable() {
298 let rows = datatable.row_count();
300 let cols = datatable.column_count();
301 (rows, cols, "")
302 } else {
303 (0, 0, "")
304 };
305
306 let preview = format!("{} rows × {} columns", row_count, col_count);
308 state_container.yank_all(tsv_text.clone(), preview.clone())?;
309
310 Ok(YankResult {
311 description: format!("All data{} as TSV ({} chars)", filter_info, clipboard_len),
312 preview,
313 full_value: tsv_text,
314 })
315 }
316
317 fn datatable_to_json(datatable: &crate::data::datatable::DataTable) -> Result<Vec<Value>> {
319 let headers = datatable.column_names();
320 let mut json_data = Vec::new();
321
322 for row_idx in 0..datatable.row_count() {
323 if let Some(row_data) = datatable.get_row_as_strings(row_idx) {
324 let mut obj = serde_json::Map::new();
325 for (i, header) in headers.iter().enumerate() {
326 if let Some(value) = row_data.get(i) {
327 if value == "NULL" || value.is_empty() {
329 obj.insert(header.clone(), Value::Null);
330 } else if let Ok(n) = value.parse::<f64>() {
331 obj.insert(
332 header.clone(),
333 Value::Number(
334 serde_json::Number::from_f64(n)
335 .unwrap_or_else(|| serde_json::Number::from(0)),
336 ),
337 );
338 } else if value == "true" || value == "false" {
339 obj.insert(header.clone(), Value::Bool(value == "true"));
340 } else {
341 obj.insert(header.clone(), Value::String(value.clone()));
342 }
343 } else {
344 obj.insert(header.clone(), Value::Null);
345 }
346 }
347 json_data.push(Value::Object(obj));
348 }
349 }
350
351 Ok(json_data)
352 }
353}