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_cell_ref(&self, cell_ref: &CellRef, op: &ShiftOperation) -> Option<CellRef> {
89 let coord = cell_ref.coord;
90 let adjusted_coord = match op {
91 ShiftOperation::InsertRows {
92 sheet_id,
93 before,
94 count,
95 } if cell_ref.sheet_id == *sheet_id => {
96 if coord.row_abs() || coord.row() < *before {
97 coord
99 } else {
100 Coord::new(
102 coord.row() + count,
103 coord.col(),
104 coord.row_abs(),
105 coord.col_abs(),
106 )
107 }
108 }
109 ShiftOperation::DeleteRows {
110 sheet_id,
111 start,
112 count,
113 } if cell_ref.sheet_id == *sheet_id => {
114 if coord.row_abs() {
115 coord
117 } else if coord.row() >= *start && coord.row() < start + count {
118 return None;
120 } else if coord.row() >= start + count {
121 Coord::new(
123 coord.row() - count,
124 coord.col(),
125 coord.row_abs(),
126 coord.col_abs(),
127 )
128 } else {
129 coord
131 }
132 }
133 ShiftOperation::InsertColumns {
134 sheet_id,
135 before,
136 count,
137 } if cell_ref.sheet_id == *sheet_id => {
138 if coord.col_abs() || coord.col() < *before {
139 coord
141 } else {
142 Coord::new(
144 coord.row(),
145 coord.col() + count,
146 coord.row_abs(),
147 coord.col_abs(),
148 )
149 }
150 }
151 ShiftOperation::DeleteColumns {
152 sheet_id,
153 start,
154 count,
155 } if cell_ref.sheet_id == *sheet_id => {
156 if coord.col_abs() {
157 coord
159 } else if coord.col() >= *start && coord.col() < start + count {
160 return None;
162 } else if coord.col() >= start + count {
163 Coord::new(
165 coord.row(),
166 coord.col() - count,
167 coord.row_abs(),
168 coord.col_abs(),
169 )
170 } else {
171 coord
173 }
174 }
175 _ => coord,
176 };
177
178 Some(CellRef::new(cell_ref.sheet_id, adjusted_coord))
179 }
180
181 fn adjust_reference(
183 &self,
184 reference: &formualizer_parse::parser::ReferenceType,
185 op: &ShiftOperation,
186 ) -> formualizer_parse::parser::ReferenceType {
187 use formualizer_parse::parser::ReferenceType;
188
189 let shared = reference.to_sheet_ref_lossy();
190
191 match (reference, shared) {
192 (
193 ReferenceType::Cell {
194 sheet,
195 row_abs,
196 col_abs,
197 ..
198 },
199 Some(crate::reference::SharedRef::Cell(cell)),
200 ) => {
201 let sheet_id = match op {
202 ShiftOperation::InsertRows { sheet_id, .. }
203 | ShiftOperation::DeleteRows { sheet_id, .. }
204 | ShiftOperation::InsertColumns { sheet_id, .. }
205 | ShiftOperation::DeleteColumns { sheet_id, .. } => *sheet_id,
206 };
207 let temp_ref = CellRef::new(
208 sheet_id,
209 Coord::new(cell.coord.row(), cell.coord.col(), *row_abs, *col_abs),
210 );
211
212 match self.adjust_cell_ref(&temp_ref, op) {
213 None => ReferenceType::Cell {
214 sheet: Some("#REF".to_string()),
215 row: 0,
216 col: 0,
217 row_abs: *row_abs,
218 col_abs: *col_abs,
219 },
220 Some(adjusted) => ReferenceType::Cell {
221 sheet: sheet.clone(),
222 row: adjusted.coord.row() + 1,
223 col: adjusted.coord.col() + 1,
224 row_abs: *row_abs,
225 col_abs: *col_abs,
226 },
227 }
228 }
229 (
230 ReferenceType::Range {
231 sheet,
232 start_row_abs,
233 start_col_abs,
234 end_row_abs,
235 end_col_abs,
236 ..
237 },
238 Some(crate::reference::SharedRef::Range(range)),
239 ) => {
240 let is_unbounded_column = range.start_row.is_none() && range.end_row.is_none();
241 let is_unbounded_row = range.start_col.is_none() && range.end_col.is_none();
242 if is_unbounded_column || is_unbounded_row {
243 return reference.clone();
244 }
245
246 let sr = range.start_row;
247 let sc = range.start_col;
248 let er = range.end_row;
249 let ec = range.end_col;
250
251 let adjust_insert = |b: formualizer_common::AxisBound, before: u32, count: u32| {
252 if b.abs {
253 b.index
254 } else if b.index >= before {
255 b.index + count
256 } else {
257 b.index
258 }
259 };
260
261 let adjust_delete = |idx: u32, abs: bool, start: u32, count: u32| {
262 if abs {
263 idx
264 } else if idx >= start + count {
265 idx - count
266 } else if idx >= start {
267 start
268 } else {
269 idx
270 }
271 };
272
273 let (adj_sr0, adj_er0) = match op {
274 ShiftOperation::InsertRows { before, count, .. } => (
275 sr.map(|b| adjust_insert(b, *before, *count)),
276 er.map(|b| adjust_insert(b, *before, *count)),
277 ),
278 ShiftOperation::DeleteRows { start, count, .. } => match (sr, er) {
279 (Some(range_start), Some(range_end))
280 if !range_start.abs && !range_end.abs =>
281 {
282 let range_start = range_start.index;
283 let range_end = range_end.index;
284 if range_end < *start || range_start >= start + count {
285 let adj_start = if range_start >= start + count {
286 range_start - count
287 } else {
288 range_start
289 };
290 let adj_end = if range_end >= start + count {
291 range_end - count
292 } else {
293 range_end
294 };
295 (Some(adj_start), Some(adj_end))
296 } else if range_start >= *start && range_end < start + count {
297 return ReferenceType::Range {
298 sheet: Some("#REF".to_string()),
299 start_row: Some(0),
300 start_col: Some(0),
301 end_row: Some(0),
302 end_col: Some(0),
303 start_row_abs: *start_row_abs,
304 start_col_abs: *start_col_abs,
305 end_row_abs: *end_row_abs,
306 end_col_abs: *end_col_abs,
307 };
308 } else {
309 let adj_start = if range_start < *start {
310 range_start
311 } else {
312 *start
313 };
314 let adj_end = if range_end >= start + count {
315 range_end - count
316 } else {
317 start.saturating_sub(1)
318 };
319 (Some(adj_start), Some(adj_end))
320 }
321 }
322 (Some(range_start), Some(range_end)) => {
323 let adj_start =
324 adjust_delete(range_start.index, range_start.abs, *start, *count);
325 let adj_end =
326 adjust_delete(range_end.index, range_end.abs, *start, *count);
327 (Some(adj_start), Some(adj_end))
328 }
329 _ => (
330 sr.map(|b| adjust_delete(b.index, b.abs, *start, *count)),
331 er.map(|b| adjust_delete(b.index, b.abs, *start, *count)),
332 ),
333 },
334 _ => (sr.map(|b| b.index), er.map(|b| b.index)),
335 };
336
337 let (adj_sc0, adj_ec0) = match op {
338 ShiftOperation::InsertColumns { before, count, .. } => (
339 sc.map(|b| adjust_insert(b, *before, *count)),
340 ec.map(|b| adjust_insert(b, *before, *count)),
341 ),
342 ShiftOperation::DeleteColumns { start, count, .. } => match (sc, ec) {
343 (Some(range_start), Some(range_end))
344 if !range_start.abs && !range_end.abs =>
345 {
346 let range_start = range_start.index;
347 let range_end = range_end.index;
348 if range_end < *start || range_start >= start + count {
349 let adj_start = if range_start >= start + count {
350 range_start - count
351 } else {
352 range_start
353 };
354 let adj_end = if range_end >= start + count {
355 range_end - count
356 } else {
357 range_end
358 };
359 (Some(adj_start), Some(adj_end))
360 } else if range_start >= *start && range_end < start + count {
361 return ReferenceType::Range {
362 sheet: Some("#REF".to_string()),
363 start_row: Some(0),
364 start_col: Some(0),
365 end_row: Some(0),
366 end_col: Some(0),
367 start_row_abs: *start_row_abs,
368 start_col_abs: *start_col_abs,
369 end_row_abs: *end_row_abs,
370 end_col_abs: *end_col_abs,
371 };
372 } else {
373 let adj_start = if range_start < *start {
374 range_start
375 } else {
376 *start
377 };
378 let adj_end = if range_end >= start + count {
379 range_end - count
380 } else {
381 start.saturating_sub(1)
382 };
383 (Some(adj_start), Some(adj_end))
384 }
385 }
386 (Some(range_start), Some(range_end)) => {
387 let adj_start =
388 adjust_delete(range_start.index, range_start.abs, *start, *count);
389 let adj_end =
390 adjust_delete(range_end.index, range_end.abs, *start, *count);
391 (Some(adj_start), Some(adj_end))
392 }
393 _ => (
394 sc.map(|b| adjust_delete(b.index, b.abs, *start, *count)),
395 ec.map(|b| adjust_delete(b.index, b.abs, *start, *count)),
396 ),
397 },
398 _ => (sc.map(|b| b.index), ec.map(|b| b.index)),
399 };
400
401 ReferenceType::Range {
402 sheet: sheet.clone(),
403 start_row: adj_sr0.map(|i| i + 1),
404 start_col: adj_sc0.map(|i| i + 1),
405 end_row: adj_er0.map(|i| i + 1),
406 end_col: adj_ec0.map(|i| i + 1),
407 start_row_abs: *start_row_abs,
408 start_col_abs: *start_col_abs,
409 end_row_abs: *end_row_abs,
410 end_col_abs: *end_col_abs,
411 }
412 }
413 _ => reference.clone(),
414 }
415 }
416}
417
418impl Default for ReferenceAdjuster {
419 fn default() -> Self {
420 Self::new()
421 }
422}
423
424pub struct RelativeReferenceAdjuster {
426 row_offset: i32,
427 col_offset: i32,
428}
429
430impl RelativeReferenceAdjuster {
431 pub fn new(row_offset: i32, col_offset: i32) -> Self {
432 Self {
433 row_offset,
434 col_offset,
435 }
436 }
437
438 pub fn adjust_formula(&self, ast: &ASTNode) -> ASTNode {
439 match &ast.node_type {
440 ASTNodeType::Reference {
441 original,
442 reference,
443 } => {
444 let adjusted = self.adjust_reference(reference);
445 ASTNode {
446 node_type: ASTNodeType::Reference {
447 original: original.clone(),
448 reference: adjusted,
449 },
450 source_token: ast.source_token.clone(),
451 contains_volatile: ast.contains_volatile,
452 }
453 }
454 ASTNodeType::BinaryOp { op, left, right } => ASTNode {
455 node_type: ASTNodeType::BinaryOp {
456 op: op.clone(),
457 left: Box::new(self.adjust_formula(left)),
458 right: Box::new(self.adjust_formula(right)),
459 },
460 source_token: ast.source_token.clone(),
461 contains_volatile: ast.contains_volatile,
462 },
463 ASTNodeType::UnaryOp { op, expr } => ASTNode {
464 node_type: ASTNodeType::UnaryOp {
465 op: op.clone(),
466 expr: Box::new(self.adjust_formula(expr)),
467 },
468 source_token: ast.source_token.clone(),
469 contains_volatile: ast.contains_volatile,
470 },
471 ASTNodeType::Function { name, args } => ASTNode {
472 node_type: ASTNodeType::Function {
473 name: name.clone(),
474 args: args.iter().map(|arg| self.adjust_formula(arg)).collect(),
475 },
476 source_token: ast.source_token.clone(),
477 contains_volatile: ast.contains_volatile,
478 },
479 _ => ast.clone(),
480 }
481 }
482
483 fn adjust_reference(
484 &self,
485 reference: &formualizer_parse::parser::ReferenceType,
486 ) -> formualizer_parse::parser::ReferenceType {
487 use formualizer_parse::parser::ReferenceType;
488
489 let Some(shared) = reference.to_sheet_ref_lossy() else {
490 return reference.clone();
491 };
492
493 match (reference, shared) {
494 (ReferenceType::Cell { sheet, .. }, crate::reference::SharedRef::Cell(cell)) => {
495 let owned = cell.into_owned();
496 let row0 = owned.coord.row();
497 let col0 = owned.coord.col();
498 let row_abs = owned.coord.row_abs();
499 let col_abs = owned.coord.col_abs();
500
501 let new_row0 = if row_abs {
502 row0
503 } else {
504 (row0 as i32 + self.row_offset).max(0) as u32
505 };
506 let new_col0 = if col_abs {
507 col0
508 } else {
509 (col0 as i32 + self.col_offset).max(0) as u32
510 };
511
512 ReferenceType::Cell {
513 sheet: sheet.clone(),
514 row: new_row0 + 1,
515 col: new_col0 + 1,
516 row_abs,
517 col_abs,
518 }
519 }
520 (ReferenceType::Range { sheet, .. }, crate::reference::SharedRef::Range(range)) => {
521 let owned = range.into_owned();
522
523 let adj_axis = |b: formualizer_common::AxisBound, off: i32| {
524 if b.abs {
525 b.index
526 } else {
527 (b.index as i32 + off).max(0) as u32
528 }
529 };
530
531 let adj_start_row = owned.start_row.map(|b| adj_axis(b, self.row_offset) + 1);
532 let adj_start_col = owned.start_col.map(|b| adj_axis(b, self.col_offset) + 1);
533 let adj_end_row = owned.end_row.map(|b| adj_axis(b, self.row_offset) + 1);
534 let adj_end_col = owned.end_col.map(|b| adj_axis(b, self.col_offset) + 1);
535
536 let start_row_abs = owned.start_row.map(|b| b.abs).unwrap_or(false);
537 let start_col_abs = owned.start_col.map(|b| b.abs).unwrap_or(false);
538 let end_row_abs = owned.end_row.map(|b| b.abs).unwrap_or(false);
539 let end_col_abs = owned.end_col.map(|b| b.abs).unwrap_or(false);
540
541 ReferenceType::Range {
542 sheet: sheet.clone(),
543 start_row: adj_start_row,
544 start_col: adj_start_col,
545 end_row: adj_end_row,
546 end_col: adj_end_col,
547 start_row_abs,
548 start_col_abs,
549 end_row_abs,
550 end_col_abs,
551 }
552 }
553 _ => reference.clone(),
554 }
555 }
556}
557
558pub struct MoveReferenceAdjuster {
562 from_sheet_id: crate::SheetId,
563 from_sheet_name: String,
564 from_start_row: u32,
565 from_start_col: u32,
566 from_end_row: u32,
567 from_end_col: u32,
568 to_sheet_id: crate::SheetId,
569 to_sheet_name: String,
570 row_offset: i32,
571 col_offset: i32,
572}
573
574impl MoveReferenceAdjuster {
575 pub fn new(
576 from_sheet_id: crate::SheetId,
577 from_sheet_name: String,
578 from_start_row: u32,
579 from_start_col: u32,
580 from_end_row: u32,
581 from_end_col: u32,
582 to_sheet_id: crate::SheetId,
583 to_sheet_name: String,
584 row_offset: i32,
585 col_offset: i32,
586 ) -> Self {
587 Self {
588 from_sheet_id,
589 from_sheet_name,
590 from_start_row,
591 from_start_col,
592 from_end_row,
593 from_end_col,
594 to_sheet_id,
595 to_sheet_name,
596 row_offset,
597 col_offset,
598 }
599 }
600
601 pub fn adjust_if_references(
602 &self,
603 formula: &ASTNode,
604 formula_sheet_id: crate::SheetId,
605 ) -> Option<ASTNode> {
606 let (adjusted, changed) = self.adjust_ast_inner(formula, formula_sheet_id);
607 if changed { Some(adjusted) } else { None }
608 }
609
610 fn adjust_ast_inner(&self, ast: &ASTNode, formula_sheet_id: crate::SheetId) -> (ASTNode, bool) {
611 match &ast.node_type {
612 ASTNodeType::Reference {
613 original,
614 reference,
615 } => {
616 let (adjusted_ref, changed) = self.adjust_reference(reference, formula_sheet_id);
617 if !changed {
618 return (ast.clone(), false);
619 }
620 (
621 ASTNode {
622 node_type: ASTNodeType::Reference {
623 original: original.clone(),
624 reference: adjusted_ref,
625 },
626 source_token: ast.source_token.clone(),
627 contains_volatile: ast.contains_volatile,
628 },
629 true,
630 )
631 }
632 ASTNodeType::BinaryOp { op, left, right } => {
633 let (l_adj, l_ch) = self.adjust_ast_inner(left, formula_sheet_id);
634 let (r_adj, r_ch) = self.adjust_ast_inner(right, formula_sheet_id);
635 if !l_ch && !r_ch {
636 return (ast.clone(), false);
637 }
638 (
639 ASTNode {
640 node_type: ASTNodeType::BinaryOp {
641 op: op.clone(),
642 left: Box::new(l_adj),
643 right: Box::new(r_adj),
644 },
645 source_token: ast.source_token.clone(),
646 contains_volatile: ast.contains_volatile,
647 },
648 true,
649 )
650 }
651 ASTNodeType::UnaryOp { op, expr } => {
652 let (e_adj, e_ch) = self.adjust_ast_inner(expr, formula_sheet_id);
653 if !e_ch {
654 return (ast.clone(), false);
655 }
656 (
657 ASTNode {
658 node_type: ASTNodeType::UnaryOp {
659 op: op.clone(),
660 expr: Box::new(e_adj),
661 },
662 source_token: ast.source_token.clone(),
663 contains_volatile: ast.contains_volatile,
664 },
665 true,
666 )
667 }
668 ASTNodeType::Function { name, args } => {
669 let mut any = false;
670 let new_args: Vec<_> = args
671 .iter()
672 .map(|a| {
673 let (adj, ch) = self.adjust_ast_inner(a, formula_sheet_id);
674 any |= ch;
675 adj
676 })
677 .collect();
678 if !any {
679 return (ast.clone(), false);
680 }
681 (
682 ASTNode {
683 node_type: ASTNodeType::Function {
684 name: name.clone(),
685 args: new_args,
686 },
687 source_token: ast.source_token.clone(),
688 contains_volatile: ast.contains_volatile,
689 },
690 true,
691 )
692 }
693 ASTNodeType::Array(rows) => {
694 let mut any = false;
695 let new_rows: Vec<_> = rows
696 .iter()
697 .map(|row| {
698 row.iter()
699 .map(|c| {
700 let (adj, ch) = self.adjust_ast_inner(c, formula_sheet_id);
701 any |= ch;
702 adj
703 })
704 .collect()
705 })
706 .collect();
707 if !any {
708 return (ast.clone(), false);
709 }
710 (
711 ASTNode {
712 node_type: ASTNodeType::Array(new_rows),
713 source_token: ast.source_token.clone(),
714 contains_volatile: ast.contains_volatile,
715 },
716 true,
717 )
718 }
719 _ => (ast.clone(), false),
720 }
721 }
722
723 fn adjust_reference(
724 &self,
725 reference: &formualizer_parse::parser::ReferenceType,
726 formula_sheet_id: crate::SheetId,
727 ) -> (formualizer_parse::parser::ReferenceType, bool) {
728 use formualizer_parse::parser::ReferenceType;
729
730 let sheet_matches_source = |sheet: &Option<String>| {
731 if let Some(name) = sheet.as_deref() {
732 name == self.from_sheet_name
733 } else {
734 formula_sheet_id == self.from_sheet_id
735 }
736 };
737
738 if !sheet_matches_source(match reference {
739 ReferenceType::Cell { sheet, .. } => sheet,
740 ReferenceType::Range { sheet, .. } => sheet,
741 _ => &None,
742 }) {
743 return (reference.clone(), false);
744 }
745
746 let Some(shared) = reference.to_sheet_ref_lossy() else {
747 return (reference.clone(), false);
748 };
749
750 match (reference, shared) {
751 (ReferenceType::Cell { sheet, .. }, crate::reference::SharedRef::Cell(cell)) => {
752 let owned = cell.into_owned();
753 let row0 = owned.coord.row();
754 let col0 = owned.coord.col();
755 let row_abs = owned.coord.row_abs();
756 let col_abs = owned.coord.col_abs();
757
758 if row0 < self.from_start_row
759 || row0 > self.from_end_row
760 || col0 < self.from_start_col
761 || col0 > self.from_end_col
762 {
763 return (reference.clone(), false);
764 }
765
766 let new_row0 = (row0 as i32 + self.row_offset).max(0) as u32;
767 let new_col0 = (col0 as i32 + self.col_offset).max(0) as u32;
768
769 let new_sheet = if self.to_sheet_id != self.from_sheet_id {
770 Some(self.to_sheet_name.clone())
771 } else {
772 sheet.clone()
773 };
774
775 (
776 ReferenceType::Cell {
777 sheet: new_sheet,
778 row: new_row0 + 1,
779 col: new_col0 + 1,
780 row_abs,
781 col_abs,
782 },
783 true,
784 )
785 }
786 (ReferenceType::Range { sheet, .. }, crate::reference::SharedRef::Range(range)) => {
787 let owned = range.into_owned();
788 let (Some(sr), Some(sc), Some(er), Some(ec)) = (
789 owned.start_row,
790 owned.start_col,
791 owned.end_row,
792 owned.end_col,
793 ) else {
794 return (reference.clone(), false);
795 };
796
797 let sr0 = sr.index;
798 let sc0 = sc.index;
799 let er0 = er.index;
800 let ec0 = ec.index;
801 let start_row_abs = sr.abs;
802 let start_col_abs = sc.abs;
803 let end_row_abs = er.abs;
804 let end_col_abs = ec.abs;
805
806 let fully_contained = sr0 >= self.from_start_row
807 && er0 <= self.from_end_row
808 && sc0 >= self.from_start_col
809 && ec0 <= self.from_end_col;
810 if !fully_contained {
811 return (reference.clone(), false);
812 }
813
814 let new_sr0 = (sr0 as i32 + self.row_offset).max(0) as u32;
815 let new_er0 = (er0 as i32 + self.row_offset).max(0) as u32;
816 let new_sc0 = (sc0 as i32 + self.col_offset).max(0) as u32;
817 let new_ec0 = (ec0 as i32 + self.col_offset).max(0) as u32;
818
819 let new_sheet = if self.to_sheet_id != self.from_sheet_id {
820 Some(self.to_sheet_name.clone())
821 } else {
822 sheet.clone()
823 };
824
825 (
826 ReferenceType::Range {
827 sheet: new_sheet,
828 start_row: Some(new_sr0 + 1),
829 start_col: Some(new_sc0 + 1),
830 end_row: Some(new_er0 + 1),
831 end_col: Some(new_ec0 + 1),
832 start_row_abs,
833 start_col_abs,
834 end_row_abs,
835 end_col_abs,
836 },
837 true,
838 )
839 }
840 _ => (reference.clone(), false),
841 }
842 }
843}
844
845#[cfg(test)]
846mod tests {
847 use super::*;
848 use formualizer_parse::parser::parse;
849
850 fn format_formula(ast: &ASTNode) -> String {
851 format!("{ast:?}")
854 }
855
856 #[test]
857 fn test_reference_adjustment_on_row_insert() {
858 let adjuster = ReferenceAdjuster::new();
859
860 let ast = parse("=A5+B10").unwrap();
862
863 let adjusted = adjuster.adjust_ast(
865 &ast,
866 &ShiftOperation::InsertRows {
867 sheet_id: 0,
868 before: 7,
869 count: 2,
870 },
871 );
872
873 if let ASTNodeType::BinaryOp { left, right, .. } = &adjusted.node_type {
876 if let ASTNodeType::Reference {
877 reference: formualizer_parse::parser::ReferenceType::Cell { row, col, .. },
878 ..
879 } = &left.node_type
880 {
881 assert_eq!(*row, 5); assert_eq!(*col, 1);
883 }
884 if let ASTNodeType::Reference {
885 reference: formualizer_parse::parser::ReferenceType::Cell { row, col, .. },
886 ..
887 } = &right.node_type
888 {
889 assert_eq!(*row, 12); assert_eq!(*col, 2);
891 }
892 }
893 }
894
895 #[test]
896 fn test_reference_adjustment_on_column_delete() {
897 let adjuster = ReferenceAdjuster::new();
898
899 let ast = parse("=C1+F1").unwrap();
901
902 let adjusted = adjuster.adjust_ast(
904 &ast,
905 &ShiftOperation::DeleteColumns {
906 sheet_id: 0,
907 start: 2, count: 2,
909 },
910 );
911
912 if let ASTNodeType::BinaryOp { left, right, .. } = &adjusted.node_type {
914 if let ASTNodeType::Reference {
915 reference:
916 formualizer_parse::parser::ReferenceType::Cell {
917 sheet, row, col, ..
918 },
919 ..
920 } = &left.node_type
921 {
922 assert_eq!(sheet.as_deref(), Some("#REF"));
923 assert_eq!(*row, 0);
924 assert_eq!(*col, 0);
925 }
926 if let ASTNodeType::Reference {
927 reference: formualizer_parse::parser::ReferenceType::Cell { row, col, .. },
928 ..
929 } = &right.node_type
930 {
931 assert_eq!(*row, 1); assert_eq!(*col, 4); }
934 }
935 }
936
937 #[test]
938 fn test_range_reference_adjustment() {
939 let adjuster = ReferenceAdjuster::new();
940
941 let ast = parse("=SUM(A1:A10)").unwrap();
943
944 let adjusted = adjuster.adjust_ast(
946 &ast,
947 &ShiftOperation::InsertRows {
948 sheet_id: 0,
949 before: 5,
950 count: 3,
951 },
952 );
953
954 if let ASTNodeType::Function { args, .. } = &adjusted.node_type
956 && let Some(ASTNodeType::Reference {
957 reference:
958 formualizer_parse::parser::ReferenceType::Range {
959 start_row, end_row, ..
960 },
961 ..
962 }) = args.first().map(|arg| &arg.node_type)
963 {
964 assert_eq!(start_row.unwrap_or(0), 1); assert_eq!(end_row.unwrap_or(0), 13); }
967 }
968
969 #[test]
970 fn test_relative_reference_copy() {
971 let adjuster = RelativeReferenceAdjuster::new(2, 3); let ast = parse("=A1+B2").unwrap();
975 let adjusted = adjuster.adjust_formula(&ast);
976
977 if let ASTNodeType::BinaryOp { left, right, .. } = &adjusted.node_type {
979 if let ASTNodeType::Reference {
980 reference: formualizer_parse::parser::ReferenceType::Cell { row, col, .. },
981 ..
982 } = &left.node_type
983 {
984 assert_eq!(*row, 3); assert_eq!(*col, 4);
986 }
987 if let ASTNodeType::Reference {
988 reference: formualizer_parse::parser::ReferenceType::Cell { row, col, .. },
989 ..
990 } = &right.node_type
991 {
992 assert_eq!(*row, 4); assert_eq!(*col, 5);
994 }
995 }
996 }
997
998 #[test]
999 fn test_absolute_reference_preservation() {
1000 let adjuster = ReferenceAdjuster::new();
1001
1002 let cell_abs_row = CellRef::new(
1004 0,
1005 Coord::new(5, 2, true, false), );
1007
1008 let result = adjuster.adjust_cell_ref(
1010 &cell_abs_row,
1011 &ShiftOperation::InsertRows {
1012 sheet_id: 0,
1013 before: 3,
1014 count: 2,
1015 },
1016 );
1017
1018 assert!(result.is_some());
1020 let adjusted = result.unwrap();
1021 assert_eq!(adjusted.coord.row(), 5); assert_eq!(adjusted.coord.col(), 2); assert!(adjusted.coord.row_abs());
1024 assert!(!adjusted.coord.col_abs());
1025 }
1026
1027 #[test]
1028 fn test_absolute_column_preservation() {
1029 let adjuster = ReferenceAdjuster::new();
1030
1031 let cell_abs_col = CellRef::new(
1033 0,
1034 Coord::new(5, 2, false, true), );
1036
1037 let result = adjuster.adjust_cell_ref(
1039 &cell_abs_col,
1040 &ShiftOperation::DeleteColumns {
1041 sheet_id: 0,
1042 start: 1,
1043 count: 1,
1044 },
1045 );
1046
1047 assert!(result.is_some());
1049 let adjusted = result.unwrap();
1050 assert_eq!(adjusted.coord.row(), 5); assert_eq!(adjusted.coord.col(), 2); assert!(!adjusted.coord.row_abs());
1053 assert!(adjusted.coord.col_abs());
1054 }
1055
1056 #[test]
1057 fn test_mixed_absolute_relative_references() {
1058 let adjuster = ReferenceAdjuster::new();
1059
1060 let mixed1 = CellRef::new(
1062 0,
1063 Coord::new(5, 1, false, true), );
1065
1066 let result1 = adjuster.adjust_cell_ref(
1067 &mixed1,
1068 &ShiftOperation::InsertRows {
1069 sheet_id: 0,
1070 before: 3,
1071 count: 2,
1072 },
1073 );
1074
1075 assert!(result1.is_some());
1076 let adj1 = result1.unwrap();
1077 assert_eq!(adj1.coord.row(), 7); assert_eq!(adj1.coord.col(), 1); let mixed2 = CellRef::new(
1082 0,
1083 Coord::new(10, 3, true, false), );
1085
1086 let result2 = adjuster.adjust_cell_ref(
1087 &mixed2,
1088 &ShiftOperation::DeleteColumns {
1089 sheet_id: 0,
1090 start: 1,
1091 count: 1,
1092 },
1093 );
1094
1095 assert!(result2.is_some());
1096 let adj2 = result2.unwrap();
1097 assert_eq!(adj2.coord.row(), 10); assert_eq!(adj2.coord.col(), 2); }
1100
1101 #[test]
1102 fn test_fully_absolute_reference() {
1103 let adjuster = ReferenceAdjuster::new();
1104
1105 let fully_abs = CellRef::new(
1107 0,
1108 Coord::new(1, 1, true, true), );
1110
1111 let result1 = adjuster.adjust_cell_ref(
1115 &fully_abs,
1116 &ShiftOperation::InsertRows {
1117 sheet_id: 0,
1118 before: 1,
1119 count: 5,
1120 },
1121 );
1122 assert!(result1.is_some());
1123 assert_eq!(result1.unwrap().coord.row(), 1);
1124 assert_eq!(result1.unwrap().coord.col(), 1);
1125
1126 let result2 = adjuster.adjust_cell_ref(
1128 &fully_abs,
1129 &ShiftOperation::DeleteColumns {
1130 sheet_id: 0,
1131 start: 0,
1132 count: 1,
1133 },
1134 );
1135 assert!(result2.is_some());
1136 assert_eq!(result2.unwrap().coord.row(), 1);
1137 assert_eq!(result2.unwrap().coord.col(), 1);
1138 }
1139
1140 #[test]
1141 fn test_deleted_reference_becomes_ref_error() {
1142 let adjuster = ReferenceAdjuster::new();
1143
1144 let cell = CellRef::new(
1146 0,
1147 Coord::new(5, 3, false, false), );
1149
1150 let result = adjuster.adjust_cell_ref(
1152 &cell,
1153 &ShiftOperation::DeleteRows {
1154 sheet_id: 0,
1155 start: 5,
1156 count: 1,
1157 },
1158 );
1159
1160 assert!(result.is_none());
1162
1163 let result2 = adjuster.adjust_cell_ref(
1165 &cell,
1166 &ShiftOperation::DeleteColumns {
1167 sheet_id: 0,
1168 start: 3,
1169 count: 1,
1170 },
1171 );
1172
1173 assert!(result2.is_none());
1175 }
1176
1177 #[test]
1178 fn test_range_expansion_on_insert() {
1179 let adjuster = ReferenceAdjuster::new();
1180
1181 let ast = parse("=SUM(B2:D10)").unwrap();
1183
1184 let adjusted = adjuster.adjust_ast(
1186 &ast,
1187 &ShiftOperation::InsertRows {
1188 sheet_id: 0,
1189 before: 5,
1190 count: 3,
1191 },
1192 );
1193
1194 if let ASTNodeType::Function { args, .. } = &adjusted.node_type
1196 && let Some(ASTNodeType::Reference {
1197 reference:
1198 formualizer_parse::parser::ReferenceType::Range {
1199 start_row,
1200 end_row,
1201 start_col,
1202 end_col,
1203 ..
1204 },
1205 ..
1206 }) = args.first().map(|arg| &arg.node_type)
1207 {
1208 assert_eq!(*start_row, Some(2)); assert_eq!(*end_row, Some(13)); assert_eq!(*start_col, Some(2)); assert_eq!(*end_col, Some(4)); }
1213 }
1214
1215 #[test]
1216 fn test_range_contraction_on_delete() {
1217 let adjuster = ReferenceAdjuster::new();
1218
1219 let ast = parse("=SUM(A5:A20)").unwrap();
1221
1222 let adjusted = adjuster.adjust_ast(
1224 &ast,
1225 &ShiftOperation::DeleteRows {
1226 sheet_id: 0,
1227 start: 10,
1228 count: 5,
1229 },
1230 );
1231
1232 if let ASTNodeType::Function { args, .. } = &adjusted.node_type
1234 && let Some(ASTNodeType::Reference {
1235 reference:
1236 formualizer_parse::parser::ReferenceType::Range {
1237 start_row, end_row, ..
1238 },
1239 ..
1240 }) = args.first().map(|arg| &arg.node_type)
1241 {
1242 assert_eq!(*start_row, Some(5)); assert_eq!(*end_row, Some(15)); }
1245 }
1246}