1#![deny(missing_docs)]
2
3use std::borrow::Cow;
4use std::{
5 collections::{HashMap, HashSet},
6 io::Cursor,
7};
8
9use csv::{ReaderBuilder, WriterBuilder};
10use serde::{Deserialize, Serialize};
11
12use crate::{
13 cf_types::ConditionalFormatting,
14 expressions::types::{Area, CellReferenceIndex},
15 model::CellStructure,
16 types::{ArrayKind, Cell, Link, MergedCell, Style},
17 UserModel,
18};
19
20use crate::user_model::history::Diff;
21
22const PARTIAL_MERGE_PASTE_ERROR: &str =
23 "Cannot paste: a merged cell partially overlaps the paste area";
24
25pub type ClipboardData = HashMap<i32, HashMap<i32, ClipboardCell>>;
27
28pub type ClipboardTuple = (i32, i32, i32, i32);
29
30#[derive(Serialize, Deserialize)]
31pub struct ClipboardCell {
32 text: String,
33 is_spill: bool,
34 style: Style,
35 #[serde(default)]
38 link: Option<Link>,
39}
40
41#[derive(Serialize, Deserialize)]
42pub struct Clipboard {
43 pub(crate) csv: String,
44 pub(crate) data: ClipboardData,
45 pub(crate) sheet: u32,
46 pub(crate) range: (i32, i32, i32, i32),
47}
48
49impl<'a> UserModel<'a> {
50 fn remove_merges_replaced_by_paste(
66 &mut self,
67 target_area: &Area,
68 pasted_merges: &[MergedCell],
69 diff_list: &mut Vec<Diff>,
70 ) -> Result<(), String> {
71 let sheet = target_area.sheet;
72 let old_merged_cells = self.model.get_merged_cells(sheet)?.to_vec();
73 let single_cell_target = target_area.width == 1 && target_area.height == 1;
74 let cell_in_footprint = |row: i32, column: i32| -> bool {
75 (row >= target_area.row
76 && row < target_area.row + target_area.height
77 && column >= target_area.column
78 && column < target_area.column + target_area.width)
79 || pasted_merges.iter().any(|p| {
80 row >= p.row
81 && row < p.row + p.height
82 && column >= p.column
83 && column < p.column + p.width
84 })
85 };
86 let mut merged_cells_after_removal = Vec::new();
87 let mut removed_any = false;
88 for m in &old_merged_cells {
89 let intersects_footprint = m.intersects(
90 target_area.row,
91 target_area.column,
92 target_area.width,
93 target_area.height,
94 ) || pasted_merges
95 .iter()
96 .any(|p| m.intersects(p.row, p.column, p.width, p.height));
97 if !intersects_footprint {
98 merged_cells_after_removal.push(*m);
99 continue;
100 }
101 let mut contained = true;
102 'cells: for row in m.row..m.row + m.height {
103 for column in m.column..m.column + m.width {
104 if !cell_in_footprint(row, column) {
105 contained = false;
106 break 'cells;
107 }
108 }
109 }
110 if contained {
111 removed_any = true;
113 } else if single_cell_target
114 && (m.row, m.column) == (target_area.row, target_area.column)
115 {
116 merged_cells_after_removal.push(*m);
118 } else {
119 return Err(PARTIAL_MERGE_PASTE_ERROR.to_string());
120 }
121 }
122 if removed_any {
123 self.model.workbook.worksheet_mut(sheet)?.merged_cells =
124 merged_cells_after_removal.clone();
125 diff_list.push(Diff::SetMergedCells {
126 sheet,
127 old_value: old_merged_cells,
128 new_value: merged_cells_after_removal,
129 });
130 }
131 Ok(())
132 }
133
134 pub fn copy_to_clipboard(&self) -> Result<Clipboard, String> {
136 let selected_area = self.get_selected_view();
137 let sheet = selected_area.sheet;
138 let mut wtr = WriterBuilder::new().delimiter(b'\t').from_writer(vec![]);
139
140 let mut data = HashMap::new();
141 let [row_start, column_start, row_end, column_end] = selected_area.range;
142 let dimension = self.model.workbook.worksheet(sheet)?.dimension();
143 let row_end = row_end.min(dimension.max_row).max(row_start);
144 let column_end = column_end.min(dimension.max_column).max(column_start);
145 for row in row_start..=row_end {
146 let mut data_row = HashMap::new();
147 let mut text_row = Vec::new();
148 for column in column_start..=column_end {
149 let text = self.get_formatted_cell_value(sheet, row, column)?;
150 let content = self.get_cell_content(sheet, row, column)?;
151 let style = self.model.get_style_for_cell(sheet, row, column)?;
152 let is_spill = matches!(
153 self.model.get_cell_structure(sheet, row, column)?,
154 CellStructure::SpillArray { .. } | CellStructure::SpillDynamic { .. }
155 );
156 let link = self.model.get_cell_link(sheet, row, column)?;
157 data_row.insert(
158 column,
159 ClipboardCell {
160 text: content,
161 is_spill,
162 style,
163 link,
164 },
165 );
166 text_row.push(text);
167 }
168 wtr.write_record(text_row)
169 .map_err(|e| format!("Error while processing csv: {e}"))?;
170 data.insert(row, data_row);
171 }
172
173 let csv = String::from_utf8(
174 wtr.into_inner()
175 .map_err(|e| format!("Processing error: '{e}'"))?,
176 )
177 .map_err(|e| format!("Error converting from utf8: '{e}'"))?;
178
179 Ok(Clipboard {
180 csv: csv.trim().to_string(),
181 data,
182 sheet,
183 range: (row_start, column_start, row_end, column_end),
184 })
185 }
186
187 pub fn paste_from_clipboard(
189 &mut self,
190 source_sheet: u32,
191 source_range: ClipboardTuple,
192 clipboard: &ClipboardData,
193 is_cut: bool,
194 ) -> Result<(), String> {
195 let mut diff_list = Vec::new();
196 let view = self.get_selected_view();
197 let (source_first_row, source_first_column, source_last_row, source_last_column) =
198 source_range;
199 let sheet = view.sheet;
200 let (selected_row, selected_column) = (view.row, view.column);
203 let mut max_row = selected_row;
204 let mut max_column = selected_column;
205 let area = &Area {
206 sheet,
207 row: source_first_row,
208 column: source_first_column,
209 width: source_last_column - source_first_column + 1,
210 height: source_last_row - source_first_row + 1,
211 };
212 let target_area = &Area {
213 sheet,
214 row: selected_row,
215 column: selected_column,
216 width: source_last_column - source_first_column + 1,
217 height: source_last_row - source_first_row + 1,
218 };
219
220 let source_merges: Vec<MergedCell> = self
225 .model
226 .get_merged_cells(source_sheet)?
227 .iter()
228 .filter(|m| {
229 m.intersects(
230 source_first_row,
231 source_first_column,
232 source_last_column - source_first_column + 1,
233 source_last_row - source_first_row + 1,
234 )
235 })
236 .copied()
237 .collect();
238
239 let pasted_merges: Vec<MergedCell> = source_merges
241 .iter()
242 .map(|m| MergedCell {
243 row: m.row + selected_row - source_first_row,
244 column: m.column + selected_column - source_first_column,
245 width: m.width,
246 height: m.height,
247 })
248 .collect();
249 self.remove_merges_replaced_by_paste(target_area, &pasted_merges, &mut diff_list)?;
250
251 let mut seen_cells = HashSet::new();
252 let mut changes = Vec::new();
254 for (source_row, data_row) in clipboard {
255 let delta_row = source_row - source_first_row;
256 let target_row = selected_row + delta_row;
257 max_row = max_row.max(target_row);
258 for (source_column, value) in data_row {
259 let delta_column = source_column - source_first_column;
260 let target_column = selected_column + delta_column;
261 max_column = max_column.max(target_column);
262
263 if value.is_spill {
264 let old_style =
266 self.model
267 .get_cell_style_or_none(sheet, target_row, target_column)?;
268 changes.push((
269 target_row,
270 target_column,
271 None,
272 old_style,
273 None,
274 value.style.clone(),
275 ));
276 seen_cells.insert((target_row, target_column));
277 continue;
278 }
279
280 let source = &CellReferenceIndex {
286 sheet,
287 column: *source_column,
288 row: *source_row,
289 };
290 let target = &CellReferenceIndex {
291 sheet,
292 column: target_column,
293 row: target_row,
294 };
295 let new_value = if is_cut {
296 self.model
297 .move_cell_value_to_area(&value.text, source, target, area)?
298 } else {
299 self.model
300 .extend_copied_value(&value.text, source, target)?
301 };
302
303 let old_value = self
304 .model
305 .workbook
306 .worksheet(sheet)?
307 .cell(target_row, target_column)
308 .map(Cow::into_owned);
309
310 let old_style =
311 self.model
312 .get_cell_style_or_none(sheet, target_row, target_column)?;
313 changes.push((
314 target_row,
315 target_column,
316 old_value.clone(),
317 old_style.clone(),
318 Some(new_value.clone()),
319 value.style.clone(),
320 ));
321 seen_cells.insert((target_row, target_column));
322 }
323 }
324 diff_list.extend(self.range_link_diffs(target_area)?);
326 self.model.range_clear_contents(target_area)?;
328 for (target_row, target_column, old_value, old_style, new_value, style) in changes {
330 if let Some(ref v) = new_value {
331 self.model
332 .set_user_input(sheet, target_row, target_column, v.clone())?;
333 diff_list.push(Diff::SetCellValue {
334 sheet,
335 row: target_row,
336 column: target_column,
337 new_value: v.clone(),
338 old_value: Box::new(old_value),
339 });
340 }
341 self.model
342 .set_cell_style(sheet, target_row, target_column, &style)?;
343
344 diff_list.push(Diff::SetCellStyle {
345 sheet,
346 row: target_row,
347 column: target_column,
348 old_value: Box::new(old_style),
349 new_value: Box::new(style),
350 });
351 }
352 for (source_row, data_row) in clipboard {
355 let target_row = selected_row + (source_row - source_first_row);
356 for (source_column, value) in data_row {
357 let target_column = selected_column + (source_column - source_first_column);
358 let old_link = self.model.get_cell_link(sheet, target_row, target_column)?;
359 if old_link == value.link {
360 continue;
361 }
362 match &value.link {
363 Some(link) => {
364 self.model
365 .set_cell_link(sheet, target_row, target_column, link.clone())?
366 }
367 None => self
368 .model
369 .delete_cell_link(sheet, target_row, target_column)?,
370 }
371 diff_list.push(Diff::SetCellLink {
372 sheet,
373 row: target_row,
374 column: target_column,
375 old_value: Box::new(old_link),
376 new_value: Box::new(value.link.clone()),
377 });
378 }
379 }
380 if is_cut {
381 let old_source_merged_cells = self.model.get_merged_cells(source_sheet)?.to_vec();
388 let old_target_merged_cells = self.model.get_merged_cells(sheet)?.to_vec();
389 self.model.unmerge_cells(&Area {
390 sheet: source_sheet,
391 row: source_first_row,
392 column: source_first_column,
393 width: source_last_column - source_first_column + 1,
394 height: source_last_row - source_first_row + 1,
395 })?;
396 for m in source_merges {
397 let merge_area = Area {
398 sheet,
399 row: m.row + selected_row - source_first_row,
400 column: m.column + selected_column - source_first_column,
401 width: m.width,
402 height: m.height,
403 };
404 let mut merge_diffs = self.covered_cells_clear_diffs(&merge_area)?;
408 if self.model.merge_cells_keep_styles(&merge_area).is_ok() {
415 diff_list.append(&mut merge_diffs);
416 }
417 }
418 let mut snapshot_sheets = vec![source_sheet];
421 if sheet != source_sheet {
422 snapshot_sheets.push(sheet);
423 }
424 for (snapshot_sheet, old_value) in snapshot_sheets
425 .into_iter()
426 .zip([old_source_merged_cells, old_target_merged_cells])
427 {
428 let new_value = self.model.get_merged_cells(snapshot_sheet)?.to_vec();
429 if old_value != new_value {
430 diff_list.push(Diff::SetMergedCells {
431 sheet: snapshot_sheet,
432 old_value,
433 new_value,
434 });
435 }
436 }
437 for row in source_first_row..=source_last_row {
438 for column in source_first_column..=source_last_column {
439 if (source_sheet == sheet) && seen_cells.contains(&(row, column)) {
440 continue;
441 }
442 let old_value = self
443 .model
444 .workbook
445 .worksheet(source_sheet)?
446 .cell(row, column)
447 .map(Cow::into_owned);
448
449 diff_list.push(Diff::RangeClearContents {
450 sheet: source_sheet,
451 row,
452 column,
453 width: 1,
454 height: 1,
455 old_value: vec![vec![old_value.clone()]],
456 });
457
458 let old_link = self.model.get_cell_link(source_sheet, row, column)?;
460 if let Some(old_link) = old_link {
461 self.model.delete_cell_link(source_sheet, row, column)?;
462 diff_list.push(Diff::SetCellLink {
463 sheet: source_sheet,
464 row,
465 column,
466 old_value: Box::new(Some(old_link)),
467 new_value: Box::new(None),
468 });
469 }
470
471 let spill_dims = match &old_value {
476 Some(Cell::ArrayFormula {
477 kind: ArrayKind::Dynamic,
478 r,
479 ..
480 }) => Some(*r),
481 _ => None,
482 };
483 if let Some((spill_w, spill_h)) = spill_dims {
484 let ws = self.model.workbook.worksheet_mut(source_sheet)?;
485 for sr in row..row + spill_h {
486 for sc in column..column + spill_w {
487 if (source_sheet == sheet) && seen_cells.contains(&(sr, sc)) {
488 continue;
489 }
490 let _ = ws.cell_clear_contents(sr, sc);
491 }
492 }
493 } else {
494 let area = Area {
495 sheet: source_sheet,
496 row,
497 column,
498 width: 1,
499 height: 1,
500 };
501 self.model.range_clear_contents(&area)?;
502 }
503 let old_style = self
504 .model
505 .get_cell_style_or_none(source_sheet, row, column)?;
506 let default_style = Style::default();
507 self.model
508 .set_cell_style(source_sheet, row, column, &default_style)?;
509 diff_list.push(Diff::SetCellStyle {
510 sheet: source_sheet,
511 row,
512 column,
513 old_value: Box::new(old_style),
514 new_value: Box::new(default_style),
515 });
516 }
517 }
518 let ext_area = Area {
521 sheet: source_sheet,
522 row: source_first_row,
523 column: source_first_column,
524 width: source_last_column - source_first_column + 1,
525 height: source_last_row - source_first_row + 1,
526 };
527 let ext_updates = self.model.get_external_formula_updates_for_cut(
528 &ext_area,
529 selected_row,
530 selected_column,
531 )?;
532 for (ext_sheet, ext_row, ext_col, new_formula) in ext_updates {
533 let old_cell = self
534 .model
535 .workbook
536 .worksheet(ext_sheet)?
537 .cell(ext_row, ext_col)
538 .map(Cow::into_owned);
539 self.model
540 .set_user_input(ext_sheet, ext_row, ext_col, new_formula.clone())?;
541 diff_list.push(Diff::SetCellValue {
542 sheet: ext_sheet,
543 row: ext_row,
544 column: ext_col,
545 new_value: new_formula,
546 old_value: Box::new(old_cell),
547 });
548 }
549 let dn_updates = self.model.get_defined_name_updates_for_cut(
551 &ext_area,
552 selected_row,
553 selected_column,
554 );
555 for (dn_name, dn_scope, old_formula, new_formula) in dn_updates {
556 diff_list.push(Diff::UpdateDefinedName {
557 name: dn_name.clone(),
558 scope: dn_scope,
559 old_formula: old_formula.clone(),
560 new_name: dn_name.clone(),
561 new_scope: dn_scope,
562 new_formula: new_formula.clone(),
563 });
564 self.model.update_defined_name(
565 &dn_name,
566 dn_scope,
567 &dn_name,
568 dn_scope,
569 &new_formula,
570 )?;
571 }
572 let cf_updates = self.model.get_conditional_formatting_updates_for_cut(
574 &ext_area,
575 selected_row,
576 selected_column,
577 )?;
578 for (cf_sheet, cf_idx, new_range, new_rule) in cf_updates {
579 let old_cf = self
580 .model
581 .workbook
582 .worksheet(cf_sheet)?
583 .conditional_formatting
584 .get(cf_idx)
585 .ok_or_else(|| format!("CF index {cf_idx} not found"))?
586 .clone();
587 {
588 let ws = self.model.workbook.worksheet_mut(cf_sheet)?;
589 ws.conditional_formatting[cf_idx].range = new_range.clone();
590 ws.conditional_formatting[cf_idx].cf_rule = new_rule.clone();
591 }
592 diff_list.push(Diff::UpdateConditionalFormatting {
593 sheet: cf_sheet,
594 index: cf_idx as u32,
595 old_range: old_cf.range,
596 old_rule: Box::new(old_cf.cf_rule),
597 old_priority: old_cf.priority,
598 new_range,
599 new_rule: Box::new(new_rule),
600 });
601 }
602 } else {
603 if !source_merges.is_empty() {
608 let old_merged_cells = self.model.get_merged_cells(sheet)?.to_vec();
609 for m in source_merges {
610 let merge_area = Area {
611 sheet,
612 row: m.row + selected_row - source_first_row,
613 column: m.column + selected_column - source_first_column,
614 width: m.width,
615 height: m.height,
616 };
617 let mut merge_diffs = self.covered_cells_clear_diffs(&merge_area)?;
621 if self.model.merge_cells_keep_styles(&merge_area).is_ok() {
629 diff_list.append(&mut merge_diffs);
630 }
631 }
632 let new_merged_cells = self.model.get_merged_cells(sheet)?.to_vec();
633 if old_merged_cells != new_merged_cells {
634 diff_list.push(Diff::SetMergedCells {
635 sheet,
636 old_value: old_merged_cells,
637 new_value: new_merged_cells,
638 });
639 }
640 }
641
642 let cf_copies = self.model.get_cf_rules_to_copy(
644 source_sheet,
645 source_first_row,
646 source_first_column,
647 source_last_row,
648 source_last_column,
649 selected_row,
650 selected_column,
651 );
652 for (new_range, new_rule) in cf_copies {
653 let priority = self
654 .model
655 .workbook
656 .worksheet(sheet)?
657 .conditional_formatting
658 .iter()
659 .map(|cf| cf.priority)
660 .max()
661 .map(|m| m + 1)
662 .unwrap_or(1);
663 self.model
664 .workbook
665 .worksheet_mut(sheet)?
666 .conditional_formatting
667 .push(ConditionalFormatting {
668 range: new_range.clone(),
669 cf_rule: new_rule.clone(),
670 priority,
671 });
672 diff_list.push(Diff::AddConditionalFormatting {
673 sheet,
674 range: new_range,
675 rule: Box::new(new_rule),
676 priority,
677 });
678 }
679 }
680 self.push_diff_list(diff_list);
681 self.set_selected_range(selected_row, selected_column, max_row, max_column)?;
683 self.evaluate_if_not_paused();
684 Ok(())
685 }
686
687 pub fn paste_csv_string(&mut self, area: &Area, csv: &str) -> Result<(), String> {
689 let sheet = area.sheet;
690
691 let mut records: Vec<Vec<String>> = Vec::new();
693 let mut max_width: i32 = 0;
694 let csv_reader = Cursor::new(csv);
695 let mut reader = ReaderBuilder::new()
696 .delimiter(b'\t')
697 .has_headers(false)
698 .from_reader(csv_reader);
699 for r in reader.records().flatten() {
700 let row_data: Vec<String> = r.iter().map(|v| v.to_string()).collect();
701 max_width = max_width.max(row_data.len() as i32);
702 records.push(row_data);
703 }
704 if records.is_empty() {
705 return Ok(());
706 }
707
708 let paste_area = Area {
710 sheet,
711 row: area.row,
712 column: area.column,
713 width: max_width,
714 height: records.len() as i32,
715 };
716
717 let mut diff_list = Vec::new();
720 self.remove_merges_replaced_by_paste(&paste_area, &[], &mut diff_list)?;
721
722 let mut old_values: HashMap<(i32, i32), Option<Cell>> = HashMap::new();
724 {
725 let ws = self.model.workbook.worksheet(sheet)?;
726 for r in area.row..area.row + records.len() as i32 {
727 for c in area.column..area.column + max_width {
728 old_values.insert((r, c), ws.cell(r, c).map(Cow::into_owned));
729 }
730 }
731 }
732
733 diff_list.extend(self.range_link_diffs(&paste_area)?);
735 self.model.range_clear_contents(&paste_area)?;
736
737 let mut row = area.row;
739 let mut last_column = area.column;
740 for row_data in &records {
741 let mut column = area.column;
742 for value in row_data {
743 let old_value = old_values.remove(&(row, column)).unwrap_or(None);
744 diff_list.push(Diff::SetCellValue {
745 sheet,
746 row,
747 column,
748 new_value: value.to_string(),
749 old_value: Box::new(old_value),
750 });
751 self.set_user_input_with_link_diffs(
753 sheet,
754 row,
755 column,
756 value.to_string(),
757 &mut diff_list,
758 )?;
759 column += 1;
760 }
761 last_column = last_column.max(column - 1);
762 row += 1;
763 }
764 self.push_diff_list(diff_list);
765 self.set_selected_range(area.row, area.column, row - 1, last_column)?;
767 self.evaluate_if_not_paused();
768 Ok(())
769 }
770}