1use crate::reference::{CellRef, Coord};
2use formualizer_parse::parser::{ASTNode, ASTNodeType};
3
4pub struct ReferenceAdjuster;
6
7#[derive(Debug, Clone)]
8pub enum ShiftOperation {
9 InsertRows {
10 sheet_id: u16,
11 before: u32,
12 count: u32,
13 },
14 DeleteRows {
15 sheet_id: u16,
16 start: u32,
17 count: u32,
18 },
19 InsertColumns {
20 sheet_id: u16,
21 before: u32,
22 count: u32,
23 },
24 DeleteColumns {
25 sheet_id: u16,
26 start: u32,
27 count: u32,
28 },
29}
30
31impl ReferenceAdjuster {
32 pub fn new() -> Self {
33 Self
34 }
35
36 pub fn adjust_ast(&self, ast: &ASTNode, op: &ShiftOperation) -> ASTNode {
38 match &ast.node_type {
39 ASTNodeType::Reference {
40 original,
41 reference,
42 } => {
43 let adjusted = self.adjust_reference(reference, op);
44 ASTNode {
45 node_type: ASTNodeType::Reference {
46 original: original.clone(),
47 reference: adjusted,
48 },
49 source_token: ast.source_token.clone(),
50 contains_volatile: ast.contains_volatile,
51 }
52 }
53 ASTNodeType::BinaryOp {
54 op: bin_op,
55 left,
56 right,
57 } => ASTNode {
58 node_type: ASTNodeType::BinaryOp {
59 op: bin_op.clone(),
60 left: Box::new(self.adjust_ast(left, op)),
61 right: Box::new(self.adjust_ast(right, op)),
62 },
63 source_token: ast.source_token.clone(),
64 contains_volatile: ast.contains_volatile,
65 },
66 ASTNodeType::UnaryOp { op: un_op, expr } => ASTNode {
67 node_type: ASTNodeType::UnaryOp {
68 op: un_op.clone(),
69 expr: Box::new(self.adjust_ast(expr, op)),
70 },
71 source_token: ast.source_token.clone(),
72 contains_volatile: ast.contains_volatile,
73 },
74 ASTNodeType::Function { name, args } => ASTNode {
75 node_type: ASTNodeType::Function {
76 name: name.clone(),
77 args: args.iter().map(|arg| self.adjust_ast(arg, op)).collect(),
78 },
79 source_token: ast.source_token.clone(),
80 contains_volatile: ast.contains_volatile,
81 },
82 _ => ast.clone(),
83 }
84 }
85
86 pub fn adjust_ast_if_changed(&self, ast: &ASTNode, op: &ShiftOperation) -> Option<ASTNode> {
90 match &ast.node_type {
91 ASTNodeType::Reference {
92 original,
93 reference,
94 } => {
95 let adjusted = self.adjust_reference(reference, op);
96 if adjusted == *reference {
97 return None;
98 }
99 Some(ASTNode {
100 node_type: ASTNodeType::Reference {
101 original: original.clone(),
102 reference: adjusted,
103 },
104 source_token: ast.source_token.clone(),
105 contains_volatile: ast.contains_volatile,
106 })
107 }
108 ASTNodeType::BinaryOp {
109 op: bin_op,
110 left,
111 right,
112 } => {
113 let adjusted_left = self.adjust_ast_if_changed(left, op);
114 let adjusted_right = self.adjust_ast_if_changed(right, op);
115 if adjusted_left.is_none() && adjusted_right.is_none() {
116 return None;
117 }
118 Some(ASTNode {
119 node_type: ASTNodeType::BinaryOp {
120 op: bin_op.clone(),
121 left: Box::new(adjusted_left.unwrap_or_else(|| (**left).clone())),
122 right: Box::new(adjusted_right.unwrap_or_else(|| (**right).clone())),
123 },
124 source_token: ast.source_token.clone(),
125 contains_volatile: ast.contains_volatile,
126 })
127 }
128 ASTNodeType::UnaryOp { op: un_op, expr } => {
129 let adjusted_expr = self.adjust_ast_if_changed(expr, op)?;
130 Some(ASTNode {
131 node_type: ASTNodeType::UnaryOp {
132 op: un_op.clone(),
133 expr: Box::new(adjusted_expr),
134 },
135 source_token: ast.source_token.clone(),
136 contains_volatile: ast.contains_volatile,
137 })
138 }
139 ASTNodeType::Function { name, args } => {
140 let mut changed = false;
141 let adjusted_args = args
142 .iter()
143 .map(|arg| {
144 if let Some(adjusted) = self.adjust_ast_if_changed(arg, op) {
145 changed = true;
146 adjusted
147 } else {
148 arg.clone()
149 }
150 })
151 .collect();
152 if !changed {
153 return None;
154 }
155 Some(ASTNode {
156 node_type: ASTNodeType::Function {
157 name: name.clone(),
158 args: adjusted_args,
159 },
160 source_token: ast.source_token.clone(),
161 contains_volatile: ast.contains_volatile,
162 })
163 }
164 _ => None,
165 }
166 }
167
168 pub fn adjust_cell_ref(&self, cell_ref: &CellRef, op: &ShiftOperation) -> Option<CellRef> {
171 let coord = cell_ref.coord;
172 let adjusted_coord = match op {
173 ShiftOperation::InsertRows {
174 sheet_id,
175 before,
176 count,
177 } if cell_ref.sheet_id == *sheet_id => {
178 if coord.row_abs() || coord.row() < *before {
179 coord
181 } else {
182 Coord::new(
184 coord.row() + count,
185 coord.col(),
186 coord.row_abs(),
187 coord.col_abs(),
188 )
189 }
190 }
191 ShiftOperation::DeleteRows {
192 sheet_id,
193 start,
194 count,
195 } if cell_ref.sheet_id == *sheet_id => {
196 if coord.row_abs() {
197 coord
199 } else if coord.row() >= *start && coord.row() < start + count {
200 return None;
202 } else if coord.row() >= start + count {
203 Coord::new(
205 coord.row() - count,
206 coord.col(),
207 coord.row_abs(),
208 coord.col_abs(),
209 )
210 } else {
211 coord
213 }
214 }
215 ShiftOperation::InsertColumns {
216 sheet_id,
217 before,
218 count,
219 } if cell_ref.sheet_id == *sheet_id => {
220 if coord.col_abs() || coord.col() < *before {
221 coord
223 } else {
224 Coord::new(
226 coord.row(),
227 coord.col() + count,
228 coord.row_abs(),
229 coord.col_abs(),
230 )
231 }
232 }
233 ShiftOperation::DeleteColumns {
234 sheet_id,
235 start,
236 count,
237 } if cell_ref.sheet_id == *sheet_id => {
238 if coord.col_abs() {
239 coord
241 } else if coord.col() >= *start && coord.col() < start + count {
242 return None;
244 } else if coord.col() >= start + count {
245 Coord::new(
247 coord.row(),
248 coord.col() - count,
249 coord.row_abs(),
250 coord.col_abs(),
251 )
252 } else {
253 coord
255 }
256 }
257 _ => coord,
258 };
259
260 Some(CellRef::new(cell_ref.sheet_id, adjusted_coord))
261 }
262
263 fn adjust_reference(
265 &self,
266 reference: &formualizer_parse::parser::ReferenceType,
267 op: &ShiftOperation,
268 ) -> formualizer_parse::parser::ReferenceType {
269 use formualizer_parse::parser::ReferenceType;
270
271 let shared = reference.to_sheet_ref_lossy();
272
273 match (reference, shared) {
274 (
275 ReferenceType::Cell {
276 sheet,
277 row_abs,
278 col_abs,
279 ..
280 },
281 Some(crate::reference::SharedRef::Cell(cell)),
282 ) => {
283 let sheet_id = match op {
284 ShiftOperation::InsertRows { sheet_id, .. }
285 | ShiftOperation::DeleteRows { sheet_id, .. }
286 | ShiftOperation::InsertColumns { sheet_id, .. }
287 | ShiftOperation::DeleteColumns { sheet_id, .. } => *sheet_id,
288 };
289 let temp_ref = CellRef::new(
290 sheet_id,
291 Coord::new(cell.coord.row(), cell.coord.col(), *row_abs, *col_abs),
292 );
293
294 match self.adjust_cell_ref(&temp_ref, op) {
295 None => ReferenceType::Cell {
296 sheet: Some("#REF".to_string()),
297 row: 0,
298 col: 0,
299 row_abs: *row_abs,
300 col_abs: *col_abs,
301 },
302 Some(adjusted) => ReferenceType::Cell {
303 sheet: sheet.clone(),
304 row: adjusted.coord.row() + 1,
305 col: adjusted.coord.col() + 1,
306 row_abs: *row_abs,
307 col_abs: *col_abs,
308 },
309 }
310 }
311 (
312 ReferenceType::Range {
313 sheet,
314 start_row_abs,
315 start_col_abs,
316 end_row_abs,
317 end_col_abs,
318 ..
319 },
320 Some(crate::reference::SharedRef::Range(range)),
321 ) => {
322 let is_unbounded_column = range.start_row.is_none() && range.end_row.is_none();
323 let is_unbounded_row = range.start_col.is_none() && range.end_col.is_none();
324 if is_unbounded_column || is_unbounded_row {
325 return reference.clone();
326 }
327
328 let sr = range.start_row;
329 let sc = range.start_col;
330 let er = range.end_row;
331 let ec = range.end_col;
332
333 let adjust_insert = |b: formualizer_common::AxisBound, before: u32, count: u32| {
334 if b.abs {
335 b.index
336 } else if b.index >= before {
337 b.index + count
338 } else {
339 b.index
340 }
341 };
342
343 let adjust_delete = |idx: u32, abs: bool, start: u32, count: u32| {
344 if abs {
345 idx
346 } else if idx >= start + count {
347 idx - count
348 } else if idx >= start {
349 start
350 } else {
351 idx
352 }
353 };
354
355 let (adj_sr0, adj_er0) = match op {
356 ShiftOperation::InsertRows { before, count, .. } => (
357 sr.map(|b| adjust_insert(b, *before, *count)),
358 er.map(|b| adjust_insert(b, *before, *count)),
359 ),
360 ShiftOperation::DeleteRows { start, count, .. } => match (sr, er) {
361 (Some(range_start), Some(range_end))
362 if !range_start.abs && !range_end.abs =>
363 {
364 let range_start = range_start.index;
365 let range_end = range_end.index;
366 if range_end < *start || range_start >= start + count {
367 let adj_start = if range_start >= start + count {
368 range_start - count
369 } else {
370 range_start
371 };
372 let adj_end = if range_end >= start + count {
373 range_end - count
374 } else {
375 range_end
376 };
377 (Some(adj_start), Some(adj_end))
378 } else if range_start >= *start && range_end < start + count {
379 return ReferenceType::Range {
380 sheet: Some("#REF".to_string()),
381 start_row: Some(0),
382 start_col: Some(0),
383 end_row: Some(0),
384 end_col: Some(0),
385 start_row_abs: *start_row_abs,
386 start_col_abs: *start_col_abs,
387 end_row_abs: *end_row_abs,
388 end_col_abs: *end_col_abs,
389 };
390 } else {
391 let adj_start = if range_start < *start {
392 range_start
393 } else {
394 *start
395 };
396 let adj_end = if range_end >= start + count {
397 range_end - count
398 } else {
399 start.saturating_sub(1)
400 };
401 (Some(adj_start), Some(adj_end))
402 }
403 }
404 (Some(range_start), Some(range_end)) => {
405 let adj_start =
406 adjust_delete(range_start.index, range_start.abs, *start, *count);
407 let adj_end =
408 adjust_delete(range_end.index, range_end.abs, *start, *count);
409 (Some(adj_start), Some(adj_end))
410 }
411 _ => (
412 sr.map(|b| adjust_delete(b.index, b.abs, *start, *count)),
413 er.map(|b| adjust_delete(b.index, b.abs, *start, *count)),
414 ),
415 },
416 _ => (sr.map(|b| b.index), er.map(|b| b.index)),
417 };
418
419 let (adj_sc0, adj_ec0) = match op {
420 ShiftOperation::InsertColumns { before, count, .. } => (
421 sc.map(|b| adjust_insert(b, *before, *count)),
422 ec.map(|b| adjust_insert(b, *before, *count)),
423 ),
424 ShiftOperation::DeleteColumns { start, count, .. } => match (sc, ec) {
425 (Some(range_start), Some(range_end))
426 if !range_start.abs && !range_end.abs =>
427 {
428 let range_start = range_start.index;
429 let range_end = range_end.index;
430 if range_end < *start || range_start >= start + count {
431 let adj_start = if range_start >= start + count {
432 range_start - count
433 } else {
434 range_start
435 };
436 let adj_end = if range_end >= start + count {
437 range_end - count
438 } else {
439 range_end
440 };
441 (Some(adj_start), Some(adj_end))
442 } else if range_start >= *start && range_end < start + count {
443 return ReferenceType::Range {
444 sheet: Some("#REF".to_string()),
445 start_row: Some(0),
446 start_col: Some(0),
447 end_row: Some(0),
448 end_col: Some(0),
449 start_row_abs: *start_row_abs,
450 start_col_abs: *start_col_abs,
451 end_row_abs: *end_row_abs,
452 end_col_abs: *end_col_abs,
453 };
454 } else {
455 let adj_start = if range_start < *start {
456 range_start
457 } else {
458 *start
459 };
460 let adj_end = if range_end >= start + count {
461 range_end - count
462 } else {
463 start.saturating_sub(1)
464 };
465 (Some(adj_start), Some(adj_end))
466 }
467 }
468 (Some(range_start), Some(range_end)) => {
469 let adj_start =
470 adjust_delete(range_start.index, range_start.abs, *start, *count);
471 let adj_end =
472 adjust_delete(range_end.index, range_end.abs, *start, *count);
473 (Some(adj_start), Some(adj_end))
474 }
475 _ => (
476 sc.map(|b| adjust_delete(b.index, b.abs, *start, *count)),
477 ec.map(|b| adjust_delete(b.index, b.abs, *start, *count)),
478 ),
479 },
480 _ => (sc.map(|b| b.index), ec.map(|b| b.index)),
481 };
482
483 ReferenceType::Range {
484 sheet: sheet.clone(),
485 start_row: adj_sr0.map(|i| i + 1),
486 start_col: adj_sc0.map(|i| i + 1),
487 end_row: adj_er0.map(|i| i + 1),
488 end_col: adj_ec0.map(|i| i + 1),
489 start_row_abs: *start_row_abs,
490 start_col_abs: *start_col_abs,
491 end_row_abs: *end_row_abs,
492 end_col_abs: *end_col_abs,
493 }
494 }
495 _ => reference.clone(),
496 }
497 }
498}
499
500impl Default for ReferenceAdjuster {
501 fn default() -> Self {
502 Self::new()
503 }
504}
505
506pub struct RelativeReferenceAdjuster {
508 row_offset: i32,
509 col_offset: i32,
510}
511
512impl RelativeReferenceAdjuster {
513 pub fn new(row_offset: i32, col_offset: i32) -> Self {
514 Self {
515 row_offset,
516 col_offset,
517 }
518 }
519
520 pub fn adjust_formula(&self, ast: &ASTNode) -> ASTNode {
521 match &ast.node_type {
522 ASTNodeType::Reference {
523 original,
524 reference,
525 } => {
526 let adjusted = self.adjust_reference(reference);
527 ASTNode {
528 node_type: ASTNodeType::Reference {
529 original: original.clone(),
530 reference: adjusted,
531 },
532 source_token: ast.source_token.clone(),
533 contains_volatile: ast.contains_volatile,
534 }
535 }
536 ASTNodeType::BinaryOp { op, left, right } => ASTNode {
537 node_type: ASTNodeType::BinaryOp {
538 op: op.clone(),
539 left: Box::new(self.adjust_formula(left)),
540 right: Box::new(self.adjust_formula(right)),
541 },
542 source_token: ast.source_token.clone(),
543 contains_volatile: ast.contains_volatile,
544 },
545 ASTNodeType::UnaryOp { op, expr } => ASTNode {
546 node_type: ASTNodeType::UnaryOp {
547 op: op.clone(),
548 expr: Box::new(self.adjust_formula(expr)),
549 },
550 source_token: ast.source_token.clone(),
551 contains_volatile: ast.contains_volatile,
552 },
553 ASTNodeType::Function { name, args } => ASTNode {
554 node_type: ASTNodeType::Function {
555 name: name.clone(),
556 args: args.iter().map(|arg| self.adjust_formula(arg)).collect(),
557 },
558 source_token: ast.source_token.clone(),
559 contains_volatile: ast.contains_volatile,
560 },
561 _ => ast.clone(),
562 }
563 }
564
565 fn adjust_reference(
566 &self,
567 reference: &formualizer_parse::parser::ReferenceType,
568 ) -> formualizer_parse::parser::ReferenceType {
569 use formualizer_parse::parser::ReferenceType;
570
571 let Some(shared) = reference.to_sheet_ref_lossy() else {
572 return reference.clone();
573 };
574
575 match (reference, shared) {
576 (ReferenceType::Cell { sheet, .. }, crate::reference::SharedRef::Cell(cell)) => {
577 let owned = cell.into_owned();
578 let row0 = owned.coord.row();
579 let col0 = owned.coord.col();
580 let row_abs = owned.coord.row_abs();
581 let col_abs = owned.coord.col_abs();
582
583 let new_row0 = if row_abs {
584 row0
585 } else {
586 (row0 as i32 + self.row_offset).max(0) as u32
587 };
588 let new_col0 = if col_abs {
589 col0
590 } else {
591 (col0 as i32 + self.col_offset).max(0) as u32
592 };
593
594 ReferenceType::Cell {
595 sheet: sheet.clone(),
596 row: new_row0 + 1,
597 col: new_col0 + 1,
598 row_abs,
599 col_abs,
600 }
601 }
602 (ReferenceType::Range { sheet, .. }, crate::reference::SharedRef::Range(range)) => {
603 let owned = range.into_owned();
604
605 let adj_axis = |b: formualizer_common::AxisBound, off: i32| {
606 if b.abs {
607 b.index
608 } else {
609 (b.index as i32 + off).max(0) as u32
610 }
611 };
612
613 let adj_start_row = owned.start_row.map(|b| adj_axis(b, self.row_offset) + 1);
614 let adj_start_col = owned.start_col.map(|b| adj_axis(b, self.col_offset) + 1);
615 let adj_end_row = owned.end_row.map(|b| adj_axis(b, self.row_offset) + 1);
616 let adj_end_col = owned.end_col.map(|b| adj_axis(b, self.col_offset) + 1);
617
618 let start_row_abs = owned.start_row.map(|b| b.abs).unwrap_or(false);
619 let start_col_abs = owned.start_col.map(|b| b.abs).unwrap_or(false);
620 let end_row_abs = owned.end_row.map(|b| b.abs).unwrap_or(false);
621 let end_col_abs = owned.end_col.map(|b| b.abs).unwrap_or(false);
622
623 ReferenceType::Range {
624 sheet: sheet.clone(),
625 start_row: adj_start_row,
626 start_col: adj_start_col,
627 end_row: adj_end_row,
628 end_col: adj_end_col,
629 start_row_abs,
630 start_col_abs,
631 end_row_abs,
632 end_col_abs,
633 }
634 }
635 _ => reference.clone(),
636 }
637 }
638}
639
640pub struct MoveReferenceAdjuster {
644 from_sheet_id: crate::SheetId,
645 from_sheet_name: String,
646 from_start_row: u32,
647 from_start_col: u32,
648 from_end_row: u32,
649 from_end_col: u32,
650 to_sheet_id: crate::SheetId,
651 to_sheet_name: String,
652 row_offset: i32,
653 col_offset: i32,
654}
655
656impl MoveReferenceAdjuster {
657 pub fn new(
658 from_sheet_id: crate::SheetId,
659 from_sheet_name: String,
660 from_start_row: u32,
661 from_start_col: u32,
662 from_end_row: u32,
663 from_end_col: u32,
664 to_sheet_id: crate::SheetId,
665 to_sheet_name: String,
666 row_offset: i32,
667 col_offset: i32,
668 ) -> Self {
669 Self {
670 from_sheet_id,
671 from_sheet_name,
672 from_start_row,
673 from_start_col,
674 from_end_row,
675 from_end_col,
676 to_sheet_id,
677 to_sheet_name,
678 row_offset,
679 col_offset,
680 }
681 }
682
683 pub fn adjust_if_references(
684 &self,
685 formula: &ASTNode,
686 formula_sheet_id: crate::SheetId,
687 ) -> Option<ASTNode> {
688 let (adjusted, changed) = self.adjust_ast_inner(formula, formula_sheet_id);
689 if changed { Some(adjusted) } else { None }
690 }
691
692 fn adjust_ast_inner(&self, ast: &ASTNode, formula_sheet_id: crate::SheetId) -> (ASTNode, bool) {
693 match &ast.node_type {
694 ASTNodeType::Reference {
695 original,
696 reference,
697 } => {
698 let (adjusted_ref, changed) = self.adjust_reference(reference, formula_sheet_id);
699 if !changed {
700 return (ast.clone(), false);
701 }
702 (
703 ASTNode {
704 node_type: ASTNodeType::Reference {
705 original: original.clone(),
706 reference: adjusted_ref,
707 },
708 source_token: ast.source_token.clone(),
709 contains_volatile: ast.contains_volatile,
710 },
711 true,
712 )
713 }
714 ASTNodeType::BinaryOp { op, left, right } => {
715 let (l_adj, l_ch) = self.adjust_ast_inner(left, formula_sheet_id);
716 let (r_adj, r_ch) = self.adjust_ast_inner(right, formula_sheet_id);
717 if !l_ch && !r_ch {
718 return (ast.clone(), false);
719 }
720 (
721 ASTNode {
722 node_type: ASTNodeType::BinaryOp {
723 op: op.clone(),
724 left: Box::new(l_adj),
725 right: Box::new(r_adj),
726 },
727 source_token: ast.source_token.clone(),
728 contains_volatile: ast.contains_volatile,
729 },
730 true,
731 )
732 }
733 ASTNodeType::UnaryOp { op, expr } => {
734 let (e_adj, e_ch) = self.adjust_ast_inner(expr, formula_sheet_id);
735 if !e_ch {
736 return (ast.clone(), false);
737 }
738 (
739 ASTNode {
740 node_type: ASTNodeType::UnaryOp {
741 op: op.clone(),
742 expr: Box::new(e_adj),
743 },
744 source_token: ast.source_token.clone(),
745 contains_volatile: ast.contains_volatile,
746 },
747 true,
748 )
749 }
750 ASTNodeType::Function { name, args } => {
751 let mut any = false;
752 let new_args: Vec<_> = args
753 .iter()
754 .map(|a| {
755 let (adj, ch) = self.adjust_ast_inner(a, formula_sheet_id);
756 any |= ch;
757 adj
758 })
759 .collect();
760 if !any {
761 return (ast.clone(), false);
762 }
763 (
764 ASTNode {
765 node_type: ASTNodeType::Function {
766 name: name.clone(),
767 args: new_args,
768 },
769 source_token: ast.source_token.clone(),
770 contains_volatile: ast.contains_volatile,
771 },
772 true,
773 )
774 }
775 ASTNodeType::Array(rows) => {
776 let mut any = false;
777 let new_rows: Vec<_> = rows
778 .iter()
779 .map(|row| {
780 row.iter()
781 .map(|c| {
782 let (adj, ch) = self.adjust_ast_inner(c, formula_sheet_id);
783 any |= ch;
784 adj
785 })
786 .collect()
787 })
788 .collect();
789 if !any {
790 return (ast.clone(), false);
791 }
792 (
793 ASTNode {
794 node_type: ASTNodeType::Array(new_rows),
795 source_token: ast.source_token.clone(),
796 contains_volatile: ast.contains_volatile,
797 },
798 true,
799 )
800 }
801 _ => (ast.clone(), false),
802 }
803 }
804
805 fn adjust_reference(
806 &self,
807 reference: &formualizer_parse::parser::ReferenceType,
808 formula_sheet_id: crate::SheetId,
809 ) -> (formualizer_parse::parser::ReferenceType, bool) {
810 use formualizer_parse::parser::ReferenceType;
811
812 let sheet_matches_source = |sheet: &Option<String>| {
813 if let Some(name) = sheet.as_deref() {
814 name == self.from_sheet_name
815 } else {
816 formula_sheet_id == self.from_sheet_id
817 }
818 };
819
820 if !sheet_matches_source(match reference {
821 ReferenceType::Cell { sheet, .. } => sheet,
822 ReferenceType::Range { sheet, .. } => sheet,
823 _ => &None,
824 }) {
825 return (reference.clone(), false);
826 }
827
828 let Some(shared) = reference.to_sheet_ref_lossy() else {
829 return (reference.clone(), false);
830 };
831
832 match (reference, shared) {
833 (ReferenceType::Cell { sheet, .. }, crate::reference::SharedRef::Cell(cell)) => {
834 let owned = cell.into_owned();
835 let row0 = owned.coord.row();
836 let col0 = owned.coord.col();
837 let row_abs = owned.coord.row_abs();
838 let col_abs = owned.coord.col_abs();
839
840 if row0 < self.from_start_row
841 || row0 > self.from_end_row
842 || col0 < self.from_start_col
843 || col0 > self.from_end_col
844 {
845 return (reference.clone(), false);
846 }
847
848 let new_row0 = (row0 as i32 + self.row_offset).max(0) as u32;
849 let new_col0 = (col0 as i32 + self.col_offset).max(0) as u32;
850
851 let new_sheet = if self.to_sheet_id != self.from_sheet_id {
852 Some(self.to_sheet_name.clone())
853 } else {
854 sheet.clone()
855 };
856
857 (
858 ReferenceType::Cell {
859 sheet: new_sheet,
860 row: new_row0 + 1,
861 col: new_col0 + 1,
862 row_abs,
863 col_abs,
864 },
865 true,
866 )
867 }
868 (ReferenceType::Range { sheet, .. }, crate::reference::SharedRef::Range(range)) => {
869 let owned = range.into_owned();
870 let (Some(sr), Some(sc), Some(er), Some(ec)) = (
871 owned.start_row,
872 owned.start_col,
873 owned.end_row,
874 owned.end_col,
875 ) else {
876 return (reference.clone(), false);
877 };
878
879 let sr0 = sr.index;
880 let sc0 = sc.index;
881 let er0 = er.index;
882 let ec0 = ec.index;
883 let start_row_abs = sr.abs;
884 let start_col_abs = sc.abs;
885 let end_row_abs = er.abs;
886 let end_col_abs = ec.abs;
887
888 let fully_contained = sr0 >= self.from_start_row
889 && er0 <= self.from_end_row
890 && sc0 >= self.from_start_col
891 && ec0 <= self.from_end_col;
892 if !fully_contained {
893 return (reference.clone(), false);
894 }
895
896 let new_sr0 = (sr0 as i32 + self.row_offset).max(0) as u32;
897 let new_er0 = (er0 as i32 + self.row_offset).max(0) as u32;
898 let new_sc0 = (sc0 as i32 + self.col_offset).max(0) as u32;
899 let new_ec0 = (ec0 as i32 + self.col_offset).max(0) as u32;
900
901 let new_sheet = if self.to_sheet_id != self.from_sheet_id {
902 Some(self.to_sheet_name.clone())
903 } else {
904 sheet.clone()
905 };
906
907 (
908 ReferenceType::Range {
909 sheet: new_sheet,
910 start_row: Some(new_sr0 + 1),
911 start_col: Some(new_sc0 + 1),
912 end_row: Some(new_er0 + 1),
913 end_col: Some(new_ec0 + 1),
914 start_row_abs,
915 start_col_abs,
916 end_row_abs,
917 end_col_abs,
918 },
919 true,
920 )
921 }
922 _ => (reference.clone(), false),
923 }
924 }
925}
926
927#[cfg(test)]
928mod tests {
929 use super::*;
930 use formualizer_parse::parser::parse;
931
932 fn format_formula(ast: &ASTNode) -> String {
933 format!("{ast:?}")
936 }
937
938 #[test]
939 fn adjust_ast_if_changed_returns_none_for_unaffected_column_insert() {
940 let adjuster = ReferenceAdjuster::new();
941 let ast = parse("=A1+1").unwrap();
942
943 let adjusted = adjuster.adjust_ast_if_changed(
944 &ast,
945 &ShiftOperation::InsertColumns {
946 sheet_id: 0,
947 before: 3,
948 count: 1,
949 },
950 );
951
952 assert!(adjusted.is_none());
953 }
954
955 #[test]
956 fn adjust_ast_if_changed_returns_adjusted_for_insert_before_a() {
957 let adjuster = ReferenceAdjuster::new();
958 let ast = parse("=A1+1").unwrap();
959
960 let adjusted = adjuster
961 .adjust_ast_if_changed(
962 &ast,
963 &ShiftOperation::InsertColumns {
964 sheet_id: 0,
965 before: 0,
966 count: 1,
967 },
968 )
969 .expect("A1 reference should shift");
970
971 if let ASTNodeType::BinaryOp { left, .. } = &adjusted.node_type
972 && let ASTNodeType::Reference {
973 reference: formualizer_parse::parser::ReferenceType::Cell { row, col, .. },
974 ..
975 } = &left.node_type
976 {
977 assert_eq!(*row, 1);
978 assert_eq!(*col, 2);
979 return;
980 }
981
982 panic!("expected adjusted A1 reference to become B1");
983 }
984
985 #[test]
986 fn test_reference_adjustment_on_row_insert() {
987 let adjuster = ReferenceAdjuster::new();
988
989 let ast = parse("=A5+B10").unwrap();
991
992 let adjusted = adjuster.adjust_ast(
994 &ast,
995 &ShiftOperation::InsertRows {
996 sheet_id: 0,
997 before: 7,
998 count: 2,
999 },
1000 );
1001
1002 if let ASTNodeType::BinaryOp { left, right, .. } = &adjusted.node_type {
1005 if let ASTNodeType::Reference {
1006 reference: formualizer_parse::parser::ReferenceType::Cell { row, col, .. },
1007 ..
1008 } = &left.node_type
1009 {
1010 assert_eq!(*row, 5); assert_eq!(*col, 1);
1012 }
1013 if let ASTNodeType::Reference {
1014 reference: formualizer_parse::parser::ReferenceType::Cell { row, col, .. },
1015 ..
1016 } = &right.node_type
1017 {
1018 assert_eq!(*row, 12); assert_eq!(*col, 2);
1020 }
1021 }
1022 }
1023
1024 #[test]
1025 fn test_reference_adjustment_on_column_delete() {
1026 let adjuster = ReferenceAdjuster::new();
1027
1028 let ast = parse("=C1+F1").unwrap();
1030
1031 let adjusted = adjuster.adjust_ast(
1033 &ast,
1034 &ShiftOperation::DeleteColumns {
1035 sheet_id: 0,
1036 start: 2, count: 2,
1038 },
1039 );
1040
1041 if let ASTNodeType::BinaryOp { left, right, .. } = &adjusted.node_type {
1043 if let ASTNodeType::Reference {
1044 reference:
1045 formualizer_parse::parser::ReferenceType::Cell {
1046 sheet, row, col, ..
1047 },
1048 ..
1049 } = &left.node_type
1050 {
1051 assert_eq!(sheet.as_deref(), Some("#REF"));
1052 assert_eq!(*row, 0);
1053 assert_eq!(*col, 0);
1054 }
1055 if let ASTNodeType::Reference {
1056 reference: formualizer_parse::parser::ReferenceType::Cell { row, col, .. },
1057 ..
1058 } = &right.node_type
1059 {
1060 assert_eq!(*row, 1); assert_eq!(*col, 4); }
1063 }
1064 }
1065
1066 #[test]
1067 fn test_range_reference_adjustment() {
1068 let adjuster = ReferenceAdjuster::new();
1069
1070 let ast = parse("=SUM(A1:A10)").unwrap();
1072
1073 let adjusted = adjuster.adjust_ast(
1075 &ast,
1076 &ShiftOperation::InsertRows {
1077 sheet_id: 0,
1078 before: 5,
1079 count: 3,
1080 },
1081 );
1082
1083 if let ASTNodeType::Function { args, .. } = &adjusted.node_type
1085 && let Some(ASTNodeType::Reference {
1086 reference:
1087 formualizer_parse::parser::ReferenceType::Range {
1088 start_row, end_row, ..
1089 },
1090 ..
1091 }) = args.first().map(|arg| &arg.node_type)
1092 {
1093 assert_eq!(start_row.unwrap_or(0), 1); assert_eq!(end_row.unwrap_or(0), 13); }
1096 }
1097
1098 #[test]
1099 fn test_relative_reference_copy() {
1100 let adjuster = RelativeReferenceAdjuster::new(2, 3); let ast = parse("=A1+B2").unwrap();
1104 let adjusted = adjuster.adjust_formula(&ast);
1105
1106 if let ASTNodeType::BinaryOp { left, right, .. } = &adjusted.node_type {
1108 if let ASTNodeType::Reference {
1109 reference: formualizer_parse::parser::ReferenceType::Cell { row, col, .. },
1110 ..
1111 } = &left.node_type
1112 {
1113 assert_eq!(*row, 3); assert_eq!(*col, 4);
1115 }
1116 if let ASTNodeType::Reference {
1117 reference: formualizer_parse::parser::ReferenceType::Cell { row, col, .. },
1118 ..
1119 } = &right.node_type
1120 {
1121 assert_eq!(*row, 4); assert_eq!(*col, 5);
1123 }
1124 }
1125 }
1126
1127 #[test]
1128 fn test_absolute_reference_preservation() {
1129 let adjuster = ReferenceAdjuster::new();
1130
1131 let cell_abs_row = CellRef::new(
1133 0,
1134 Coord::new(5, 2, true, false), );
1136
1137 let result = adjuster.adjust_cell_ref(
1139 &cell_abs_row,
1140 &ShiftOperation::InsertRows {
1141 sheet_id: 0,
1142 before: 3,
1143 count: 2,
1144 },
1145 );
1146
1147 assert!(result.is_some());
1149 let adjusted = result.unwrap();
1150 assert_eq!(adjusted.coord.row(), 5); assert_eq!(adjusted.coord.col(), 2); assert!(adjusted.coord.row_abs());
1153 assert!(!adjusted.coord.col_abs());
1154 }
1155
1156 #[test]
1157 fn test_absolute_column_preservation() {
1158 let adjuster = ReferenceAdjuster::new();
1159
1160 let cell_abs_col = CellRef::new(
1162 0,
1163 Coord::new(5, 2, false, true), );
1165
1166 let result = adjuster.adjust_cell_ref(
1168 &cell_abs_col,
1169 &ShiftOperation::DeleteColumns {
1170 sheet_id: 0,
1171 start: 1,
1172 count: 1,
1173 },
1174 );
1175
1176 assert!(result.is_some());
1178 let adjusted = result.unwrap();
1179 assert_eq!(adjusted.coord.row(), 5); assert_eq!(adjusted.coord.col(), 2); assert!(!adjusted.coord.row_abs());
1182 assert!(adjusted.coord.col_abs());
1183 }
1184
1185 #[test]
1186 fn test_mixed_absolute_relative_references() {
1187 let adjuster = ReferenceAdjuster::new();
1188
1189 let mixed1 = CellRef::new(
1191 0,
1192 Coord::new(5, 1, false, true), );
1194
1195 let result1 = adjuster.adjust_cell_ref(
1196 &mixed1,
1197 &ShiftOperation::InsertRows {
1198 sheet_id: 0,
1199 before: 3,
1200 count: 2,
1201 },
1202 );
1203
1204 assert!(result1.is_some());
1205 let adj1 = result1.unwrap();
1206 assert_eq!(adj1.coord.row(), 7); assert_eq!(adj1.coord.col(), 1); let mixed2 = CellRef::new(
1211 0,
1212 Coord::new(10, 3, true, false), );
1214
1215 let result2 = adjuster.adjust_cell_ref(
1216 &mixed2,
1217 &ShiftOperation::DeleteColumns {
1218 sheet_id: 0,
1219 start: 1,
1220 count: 1,
1221 },
1222 );
1223
1224 assert!(result2.is_some());
1225 let adj2 = result2.unwrap();
1226 assert_eq!(adj2.coord.row(), 10); assert_eq!(adj2.coord.col(), 2); }
1229
1230 #[test]
1231 fn test_fully_absolute_reference() {
1232 let adjuster = ReferenceAdjuster::new();
1233
1234 let fully_abs = CellRef::new(
1236 0,
1237 Coord::new(1, 1, true, true), );
1239
1240 let result1 = adjuster.adjust_cell_ref(
1244 &fully_abs,
1245 &ShiftOperation::InsertRows {
1246 sheet_id: 0,
1247 before: 1,
1248 count: 5,
1249 },
1250 );
1251 assert!(result1.is_some());
1252 assert_eq!(result1.unwrap().coord.row(), 1);
1253 assert_eq!(result1.unwrap().coord.col(), 1);
1254
1255 let result2 = adjuster.adjust_cell_ref(
1257 &fully_abs,
1258 &ShiftOperation::DeleteColumns {
1259 sheet_id: 0,
1260 start: 0,
1261 count: 1,
1262 },
1263 );
1264 assert!(result2.is_some());
1265 assert_eq!(result2.unwrap().coord.row(), 1);
1266 assert_eq!(result2.unwrap().coord.col(), 1);
1267 }
1268
1269 #[test]
1270 fn test_deleted_reference_becomes_ref_error() {
1271 let adjuster = ReferenceAdjuster::new();
1272
1273 let cell = CellRef::new(
1275 0,
1276 Coord::new(5, 3, false, false), );
1278
1279 let result = adjuster.adjust_cell_ref(
1281 &cell,
1282 &ShiftOperation::DeleteRows {
1283 sheet_id: 0,
1284 start: 5,
1285 count: 1,
1286 },
1287 );
1288
1289 assert!(result.is_none());
1291
1292 let result2 = adjuster.adjust_cell_ref(
1294 &cell,
1295 &ShiftOperation::DeleteColumns {
1296 sheet_id: 0,
1297 start: 3,
1298 count: 1,
1299 },
1300 );
1301
1302 assert!(result2.is_none());
1304 }
1305
1306 #[test]
1307 fn test_range_expansion_on_insert() {
1308 let adjuster = ReferenceAdjuster::new();
1309
1310 let ast = parse("=SUM(B2:D10)").unwrap();
1312
1313 let adjusted = adjuster.adjust_ast(
1315 &ast,
1316 &ShiftOperation::InsertRows {
1317 sheet_id: 0,
1318 before: 5,
1319 count: 3,
1320 },
1321 );
1322
1323 if let ASTNodeType::Function { args, .. } = &adjusted.node_type
1325 && let Some(ASTNodeType::Reference {
1326 reference:
1327 formualizer_parse::parser::ReferenceType::Range {
1328 start_row,
1329 end_row,
1330 start_col,
1331 end_col,
1332 ..
1333 },
1334 ..
1335 }) = args.first().map(|arg| &arg.node_type)
1336 {
1337 assert_eq!(*start_row, Some(2)); assert_eq!(*end_row, Some(13)); assert_eq!(*start_col, Some(2)); assert_eq!(*end_col, Some(4)); }
1342 }
1343
1344 #[test]
1345 fn test_range_contraction_on_delete() {
1346 let adjuster = ReferenceAdjuster::new();
1347
1348 let ast = parse("=SUM(A5:A20)").unwrap();
1350
1351 let adjusted = adjuster.adjust_ast(
1353 &ast,
1354 &ShiftOperation::DeleteRows {
1355 sheet_id: 0,
1356 start: 10,
1357 count: 5,
1358 },
1359 );
1360
1361 if let ASTNodeType::Function { args, .. } = &adjusted.node_type
1363 && let Some(ASTNodeType::Reference {
1364 reference:
1365 formualizer_parse::parser::ReferenceType::Range {
1366 start_row, end_row, ..
1367 },
1368 ..
1369 }) = args.first().map(|arg| &arg.node_type)
1370 {
1371 assert_eq!(*start_row, Some(5)); assert_eq!(*end_row, Some(15)); }
1374 }
1375}