1#![deny(missing_docs)]
2
3use std::borrow::Cow;
4use std::collections::{HashMap, HashSet};
5
6use crate::{
7 expressions::{
8 types::Area,
9 utils::{is_valid_column_number, is_valid_row},
10 },
11 model::CellStructure,
12 types::MergedCell,
13 user_model::sequence_detector::detect_progression,
14 UserModel,
15};
16
17use crate::user_model::history::Diff;
18
19const PARTIAL_MERGE_ERROR: &str =
20 "Cannot auto-fill: a merged cell partially overlaps the fill area";
21const CUT_MERGE_ERROR: &str = "Cannot auto-fill: the fill size must fit whole merged cells";
22
23struct MergedFillPlan {
27 covered_source: HashSet<(i32, i32)>,
30 new_merges: Vec<MergedCell>,
32 old_merged_cells: Vec<MergedCell>,
34 merged_cells_after_removal: Vec<MergedCell>,
37}
38
39fn merge_is_contained(m: &MergedCell, row1: i32, column1: i32, row2: i32, column2: i32) -> bool {
40 m.row >= row1
41 && m.column >= column1
42 && m.row + m.height - 1 <= row2
43 && m.column + m.width - 1 <= column2
44}
45
46impl<'a> UserModel<'a> {
47 fn plan_fill_merges(
60 &self,
61 sheet: u32,
62 source: (i32, i32, i32, i32),
63 target: (i32, i32, i32, i32),
64 by_rows: bool,
65 ) -> Result<MergedFillPlan, String> {
66 let (s_row1, s_column1, s_row2, s_column2) = source;
67 let (t_row1, t_column1, t_row2, t_column2) = target;
68 let merged_cells = &self.model.workbook.worksheet(sheet)?.merged_cells;
69
70 let mut source_merges = Vec::new();
71 let mut merged_cells_after_removal = Vec::new();
72 for m in merged_cells {
73 if m.intersects(
74 s_row1,
75 s_column1,
76 s_column2 - s_column1 + 1,
77 s_row2 - s_row1 + 1,
78 ) {
79 if !merge_is_contained(m, s_row1, s_column1, s_row2, s_column2) {
80 return Err(PARTIAL_MERGE_ERROR.to_string());
81 }
82 source_merges.push(*m);
83 merged_cells_after_removal.push(*m);
84 } else if m.intersects(
85 t_row1,
86 t_column1,
87 t_column2 - t_column1 + 1,
88 t_row2 - t_row1 + 1,
89 ) {
90 if !merge_is_contained(m, t_row1, t_column1, t_row2, t_column2) {
91 return Err(PARTIAL_MERGE_ERROR.to_string());
92 }
93 } else {
95 merged_cells_after_removal.push(*m);
96 }
97 }
98
99 let mut covered_source = HashSet::new();
100 for m in &source_merges {
101 for row in m.row..m.row + m.height {
102 for column in m.column..m.column + m.width {
103 if (row, column) != (m.row, m.column) {
104 covered_source.insert((row, column));
105 }
106 }
107 }
108 }
109
110 let mut new_merges = Vec::new();
113 if !source_merges.is_empty() {
114 let mut tile_starts = Vec::new();
115 if by_rows {
116 let period = s_row2 - s_row1 + 1;
117 if t_row1 > s_row2 {
118 let mut start = t_row1;
119 while start <= t_row2 {
120 tile_starts.push(start);
121 start += period;
122 }
123 } else {
124 let mut start = s_row1 - period;
125 while start + period > t_row1 {
126 tile_starts.push(start);
127 start -= period;
128 }
129 }
130 for tile_start in tile_starts {
131 for m in &source_merges {
132 let first_row = tile_start + (m.row - s_row1);
133 let last_row = first_row + m.height - 1;
134 if last_row < t_row1 || first_row > t_row2 {
135 continue;
136 }
137 if first_row < t_row1 || last_row > t_row2 {
138 return Err(CUT_MERGE_ERROR.to_string());
139 }
140 new_merges.push(MergedCell {
141 row: first_row,
142 column: m.column,
143 width: m.width,
144 height: m.height,
145 });
146 }
147 }
148 } else {
149 let period = s_column2 - s_column1 + 1;
150 if t_column1 > s_column2 {
151 let mut start = t_column1;
152 while start <= t_column2 {
153 tile_starts.push(start);
154 start += period;
155 }
156 } else {
157 let mut start = s_column1 - period;
158 while start + period > t_column1 {
159 tile_starts.push(start);
160 start -= period;
161 }
162 }
163 for tile_start in tile_starts {
164 for m in &source_merges {
165 let first_column = tile_start + (m.column - s_column1);
166 let last_column = first_column + m.width - 1;
167 if last_column < t_column1 || first_column > t_column2 {
168 continue;
169 }
170 if first_column < t_column1 || last_column > t_column2 {
171 return Err(CUT_MERGE_ERROR.to_string());
172 }
173 new_merges.push(MergedCell {
174 row: m.row,
175 column: first_column,
176 width: m.width,
177 height: m.height,
178 });
179 }
180 }
181 }
182 }
183
184 Ok(MergedFillPlan {
185 covered_source,
186 new_merges,
187 old_merged_cells: merged_cells.clone(),
188 merged_cells_after_removal,
189 })
190 }
191
192 fn apply_fill_merge_removal(
195 &mut self,
196 sheet: u32,
197 plan: &MergedFillPlan,
198 diff_list: &mut Vec<Diff>,
199 ) -> Result<(), String> {
200 if plan.merged_cells_after_removal != plan.old_merged_cells {
201 self.model.workbook.worksheet_mut(sheet)?.merged_cells =
202 plan.merged_cells_after_removal.clone();
203 diff_list.push(Diff::SetMergedCells {
204 sheet,
205 old_value: plan.old_merged_cells.clone(),
206 new_value: plan.merged_cells_after_removal.clone(),
207 });
208 }
209 Ok(())
210 }
211
212 fn apply_fill_merge_creation(
216 &mut self,
217 sheet: u32,
218 plan: &MergedFillPlan,
219 diff_list: &mut Vec<Diff>,
220 ) -> Result<(), String> {
221 if !plan.new_merges.is_empty() {
222 let worksheet = self.model.workbook.worksheet_mut(sheet)?;
223 worksheet
224 .merged_cells
225 .extend(plan.new_merges.iter().copied());
226 diff_list.push(Diff::SetMergedCells {
227 sheet,
228 old_value: plan.merged_cells_after_removal.clone(),
229 new_value: worksheet.merged_cells.clone(),
230 });
231 }
232 Ok(())
233 }
234
235 fn fill_covered_cell(
239 &mut self,
240 sheet: u32,
241 source: (i32, i32),
242 target: (i32, i32),
243 old_value: Option<crate::types::Cell>,
244 diff_list: &mut Vec<Diff>,
245 ) -> Result<(), String> {
246 let (source_row, source_column) = source;
247 let (row, column) = target;
248 let old_style = self.model.get_cell_style_or_none(sheet, row, column)?;
249 self.model.prepare_cell_for_user_input(sheet, row, column)?;
252 let worksheet = self.model.workbook.worksheet_mut(sheet)?;
253 if worksheet.cell(row, column).is_some() {
254 worksheet.cell_clear_contents(row, column)?;
255 }
256 if old_value.is_some() {
257 diff_list.push(Diff::SetCellValue {
258 sheet,
259 row,
260 column,
261 new_value: "".to_string(),
262 old_value: Box::new(old_value),
263 });
264 }
265
266 let new_style = self
267 .model
268 .get_style_for_cell(sheet, source_row, source_column)?;
269 self.model.set_cell_style(sheet, row, column, &new_style)?;
270 diff_list.push(Diff::SetCellStyle {
271 sheet,
272 row,
273 column,
274 old_value: Box::new(old_style),
275 new_value: Box::new(new_style),
276 });
277
278 self.fill_cell_link(sheet, source_row, source_column, row, column, diff_list)
279 }
280 fn collect_and_clear_cse_in_fill_target(
287 &mut self,
288 sheet: u32,
289 row_start: i32,
290 row_end: i32,
291 col_start: i32,
292 col_end: i32,
293 ) -> Result<HashMap<(i32, i32), Option<crate::types::Cell>>, String> {
294 let mut anchors: Vec<(i32, i32, i32, i32)> = Vec::new();
297 let mut handled: Vec<(i32, i32)> = Vec::new();
298 for row in row_start..=row_end {
299 for col in col_start..=col_end {
300 let (ar, ac, w, h) = match self.model.get_cell_structure(sheet, row, col)? {
301 CellStructure::ArrayFormula { range: (w, h) } if w > 1 || h > 1 => {
302 (row, col, w, h)
303 }
304 CellStructure::SpillArray {
305 anchor: (ar, ac),
306 range: (w, h),
307 } => (ar, ac, w, h),
308 _ => continue,
309 };
310 if handled.contains(&(ar, ac)) {
311 continue;
312 }
313 handled.push((ar, ac));
314 let completely_covered = ar >= row_start
315 && ar + h - 1 <= row_end
316 && ac >= col_start
317 && ac + w - 1 <= col_end;
318 if !completely_covered {
319 return Err(
320 "Cannot autofill: selection partially overlaps an array formula"
321 .to_string(),
322 );
323 }
324 anchors.push((ar, ac, w, h));
325 }
326 }
327
328 let mut saved: HashMap<(i32, i32), Option<crate::types::Cell>> = HashMap::new();
330 for (ar, ac, w, h) in anchors {
331 for r in ar..ar + h {
332 for c in ac..ac + w {
333 let cell = self
334 .model
335 .workbook
336 .worksheet(sheet)?
337 .cell(r, c)
338 .map(Cow::into_owned);
339 saved.insert((r, c), cell);
340 }
341 }
342 let ws = self.model.workbook.worksheet_mut(sheet)?;
343 for r in ar..ar + h {
344 for c in ac..ac + w {
345 let _ = ws.cell_clear_contents(r, c);
346 }
347 }
348 }
349 Ok(saved)
350 }
351
352 pub fn auto_fill_rows(&mut self, source_area: &Area, to_row: i32) -> Result<(), String> {
355 let mut diff_list = Vec::new();
356 let sheet = source_area.sheet;
357 let row1 = source_area.row;
358 let column1 = source_area.column;
359 let width = source_area.width;
360 let height = source_area.height;
361
362 if self.model.workbook.worksheet(sheet).is_err() {
364 return Err(format!("Invalid worksheet index: '{sheet}'"));
365 }
366
367 if !is_valid_column_number(column1) {
368 return Err(format!("Invalid column: '{column1}'"));
369 }
370 if !is_valid_row(row1) {
371 return Err(format!("Invalid row: '{row1}'"));
372 }
373 if width <= 0 || height <= 0 {
374 return Err(format!("Invalid width='{}' or height='{}'", width, height));
375 }
376
377 let last_column = column1 + width - 1;
378 let last_row = row1 + height - 1;
379
380 if !is_valid_column_number(last_column) {
381 return Err(format!("Invalid column: '{last_column}'"));
382 }
383 if !is_valid_row(last_row) {
384 return Err(format!("Invalid row: '{last_row}'"));
385 }
386
387 if !is_valid_row(to_row) {
388 return Err(format!("Invalid row: '{to_row}'"));
389 }
390
391 let anchor_row;
393 let sign;
394 let row_range: Vec<i32>;
396
397 if to_row > last_row {
398 anchor_row = row1;
400 sign = 1;
401 row_range = (last_row + 1..=to_row).collect();
402 } else if to_row < row1 {
403 anchor_row = last_row;
405 sign = -1;
406 row_range = (to_row..row1).rev().collect();
407 } else {
408 return Err("Invalid parameters for autofill".to_string());
409 }
410
411 let fill_row_start = if sign < 0 { to_row } else { last_row + 1 };
413 let fill_row_end = if sign < 0 { row1 - 1 } else { to_row };
414
415 let plan = self.plan_fill_merges(
419 sheet,
420 (row1, column1, last_row, last_column),
421 (fill_row_start, column1, fill_row_end, last_column),
422 true,
423 )?;
424 self.apply_fill_merge_removal(sheet, &plan, &mut diff_list)?;
425
426 let saved_cse = self.collect_and_clear_cse_in_fill_target(
427 sheet,
428 fill_row_start,
429 fill_row_end,
430 column1,
431 last_column,
432 )?;
433
434 for column in column1..=last_column {
435 let mut index = 0;
436 let locale = &self.model.locale;
437 let values = if sign < 0 {
440 (row1..=last_row)
441 .rev()
442 .filter(|row| !plan.covered_source.contains(&(*row, column)))
443 .map(|row| self.get_cell_content(sheet, row, column))
444 .collect::<Result<Vec<_>, _>>()?
445 } else {
446 (row1..=last_row)
447 .filter(|row| !plan.covered_source.contains(&(*row, column)))
448 .map(|row| self.get_cell_content(sheet, row, column))
449 .collect::<Result<Vec<_>, _>>()?
450 };
451 let case_seed = self.get_cell_content(sheet, row1, column)?;
452 let possible_progression = if values.is_empty() {
453 None
454 } else {
455 detect_progression(&values, locale, &case_seed)
456 };
457 let mut progression_idx = 0;
458 for row_ref in row_range.iter() {
459 let row = *row_ref;
460
461 let old_value = saved_cse.get(&(row, column)).cloned().unwrap_or_else(|| {
462 self.model
463 .workbook
464 .worksheet(sheet)
465 .ok()
466 .and_then(|ws| ws.cell(row, column).map(Cow::into_owned))
467 });
468
469 let source_row = anchor_row + index;
470
471 if plan.covered_source.contains(&(source_row, column)) {
472 self.fill_covered_cell(
474 sheet,
475 (source_row, column),
476 (row, column),
477 old_value,
478 &mut diff_list,
479 )?;
480 index = (index + sign) % source_area.height;
481 continue;
482 }
483
484 let old_style = self.model.get_cell_style_or_none(sheet, row, column)?;
485 let target_value;
486
487 if let Some(ref detected_progression) = possible_progression {
489 target_value = detected_progression.next(progression_idx);
490 progression_idx += 1;
491 } else {
492 target_value = self
493 .model
494 .extend_to(sheet, source_row, column, row, column)?;
495 }
496
497 self.model
498 .set_user_input(sheet, row, column, target_value.to_string())?;
499
500 let new_style = self.model.get_style_for_cell(sheet, source_row, column)?;
502 self.model.set_cell_style(sheet, row, column, &new_style)?;
503
504 diff_list.push(Diff::SetCellStyle {
506 sheet,
507 row,
508 column,
509 old_value: Box::new(old_style),
510 new_value: Box::new(new_style),
511 });
512 diff_list.push(Diff::SetCellValue {
513 sheet,
514 row,
515 column,
516 new_value: target_value.to_string(),
517 old_value: Box::new(old_value),
518 });
519
520 self.fill_cell_link(sheet, source_row, column, row, column, &mut diff_list)?;
521
522 index = (index + sign) % source_area.height;
523 }
524 }
525 self.apply_fill_merge_creation(sheet, &plan, &mut diff_list)?;
526 self.push_diff_list(diff_list);
527 self.evaluate();
528 Ok(())
529 }
530
531 pub fn auto_fill_columns(&mut self, source_area: &Area, to_column: i32) -> Result<(), String> {
534 let mut diff_list = Vec::new();
535 let sheet = source_area.sheet;
536 let row1 = source_area.row;
537 let column1 = source_area.column;
538 let width = source_area.width;
539 let height = source_area.height;
540
541 if self.model.workbook.worksheet(sheet).is_err() {
543 return Err(format!("Invalid worksheet index: '{sheet}'"));
544 }
545
546 if !is_valid_column_number(column1) {
547 return Err(format!("Invalid column: '{column1}'"));
548 }
549 if !is_valid_row(row1) {
550 return Err(format!("Invalid row: '{row1}'"));
551 }
552 if width <= 0 || height <= 0 {
553 return Err(format!("Invalid width='{}' or height='{}'", width, height));
554 }
555
556 let last_column = column1 + width - 1;
557 let last_row = row1 + height - 1;
558
559 if !is_valid_column_number(last_column) {
560 return Err(format!("Invalid column: '{last_column}'"));
561 }
562 if !is_valid_row(last_row) {
563 return Err(format!("Invalid row: '{last_row}'"));
564 }
565
566 if !is_valid_column_number(to_column) {
567 return Err(format!("Invalid column: '{to_column}'"));
568 }
569
570 let anchor_column;
572 let sign;
573 let column_range: Vec<i32>;
575
576 if to_column > last_column {
577 anchor_column = column1;
579 sign = 1;
580 column_range = (last_column + 1..to_column + 1).collect();
581 } else if to_column < column1 {
582 anchor_column = last_column;
584 sign = -1;
585 column_range = (to_column..column1).rev().collect();
586 } else {
587 return Err("Invalid parameters for autofill".to_string());
588 }
589
590 let fill_col_start = if sign < 0 { to_column } else { last_column + 1 };
592 let fill_col_end = if sign < 0 { column1 - 1 } else { to_column };
593
594 let plan = self.plan_fill_merges(
598 sheet,
599 (row1, column1, last_row, last_column),
600 (row1, fill_col_start, last_row, fill_col_end),
601 false,
602 )?;
603 self.apply_fill_merge_removal(sheet, &plan, &mut diff_list)?;
604
605 let saved_cse = self.collect_and_clear_cse_in_fill_target(
606 sheet,
607 row1,
608 last_row,
609 fill_col_start,
610 fill_col_end,
611 )?;
612
613 for row in row1..=last_row {
614 let mut index = 0;
615 let locale = &self.model.locale;
616 let values = if sign < 0 {
619 (column1..=last_column)
620 .rev()
621 .filter(|column| !plan.covered_source.contains(&(row, *column)))
622 .map(|column| self.get_cell_content(sheet, row, column))
623 .collect::<Result<Vec<_>, _>>()?
624 } else {
625 (column1..=last_column)
626 .filter(|column| !plan.covered_source.contains(&(row, *column)))
627 .map(|column| self.get_cell_content(sheet, row, column))
628 .collect::<Result<Vec<_>, _>>()?
629 };
630 let case_seed = self.get_cell_content(sheet, row, column1)?;
631 let possible_progression = if values.is_empty() {
632 None
633 } else {
634 detect_progression(&values, locale, &case_seed)
635 };
636 let mut progression_idx = 0;
637 for column_ref in column_range.iter() {
638 let column = *column_ref;
639
640 let old_value = saved_cse.get(&(row, column)).cloned().unwrap_or_else(|| {
642 self.model
643 .workbook
644 .worksheet(sheet)
645 .ok()
646 .and_then(|ws| ws.cell(row, column).map(Cow::into_owned))
647 });
648
649 let source_column = anchor_column + index;
650
651 if plan.covered_source.contains(&(row, source_column)) {
652 self.fill_covered_cell(
654 sheet,
655 (row, source_column),
656 (row, column),
657 old_value,
658 &mut diff_list,
659 )?;
660 index = (index + sign) % source_area.width;
661 continue;
662 }
663
664 let old_style = self.model.get_cell_style_or_none(sheet, row, column)?;
665 let target_value;
666
667 if let Some(ref detected_progression) = possible_progression {
669 target_value = detected_progression.next(progression_idx);
670 progression_idx += 1;
671 } else {
672 target_value = self
673 .model
674 .extend_to(sheet, row, source_column, row, column)?;
675 }
676
677 self.model
678 .set_user_input(sheet, row, column, target_value.to_string())?;
679
680 let new_style = self.model.get_style_for_cell(sheet, row, source_column)?;
681 self.model.set_cell_style(sheet, row, column, &new_style)?;
684
685 diff_list.push(Diff::SetCellStyle {
687 sheet,
688 row,
689 column,
690 old_value: Box::new(old_style),
691 new_value: Box::new(new_style),
692 });
693
694 diff_list.push(Diff::SetCellValue {
695 sheet,
696 row,
697 column,
698 new_value: target_value.to_string(),
699 old_value: Box::new(old_value),
700 });
701
702 self.fill_cell_link(sheet, row, source_column, row, column, &mut diff_list)?;
703
704 index = (index + sign) % source_area.width;
705 }
706 }
707 self.apply_fill_merge_creation(sheet, &plan, &mut diff_list)?;
708 self.push_diff_list(diff_list);
709 self.evaluate();
710 Ok(())
711 }
712
713 fn fill_cell_link(
717 &mut self,
718 sheet: u32,
719 source_row: i32,
720 source_column: i32,
721 row: i32,
722 column: i32,
723 diff_list: &mut Vec<Diff>,
724 ) -> Result<(), String> {
725 let new_link = self.model.get_cell_link(sheet, source_row, source_column)?;
726 let old_link = self.model.get_cell_link(sheet, row, column)?;
727 if old_link == new_link {
728 return Ok(());
729 }
730 match &new_link {
731 Some(link) => self.model.set_cell_link(sheet, row, column, link.clone())?,
732 None => self.model.delete_cell_link(sheet, row, column)?,
733 }
734 diff_list.push(Diff::SetCellLink {
735 sheet,
736 row,
737 column,
738 old_value: Box::new(old_link),
739 new_value: Box::new(new_link),
740 });
741 Ok(())
742 }
743}