mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-15 02:24:38 +00:00
Merge upstream/main into feat/wordart-784
This commit is contained in:
@@ -239,7 +239,12 @@ fn redo(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
|
||||
/// The rows of `range` an applied filter hides (rows hidden by hand are not among them).
|
||||
fn filtered_rows(sh: &gridcraft_model::Sheet, range: RangeRef) -> std::collections::BTreeSet<u32> {
|
||||
sh.rows.range(range.start.row..=range.end.row).filter(|(r, info)| info.hidden && sh.is_row_filtered(**r)).map(|(r, _)| *r).collect()
|
||||
let spans = sh.filter_row_spans();
|
||||
if spans.is_empty() {
|
||||
return Default::default();
|
||||
}
|
||||
let filtered = |row: u32| spans.iter().any(|&(first, last)| row >= first && row <= last);
|
||||
sh.rows.range(range.start.row..=range.end.row).filter(|(r, info)| info.hidden && filtered(**r)).map(|(r, _)| *r).collect()
|
||||
}
|
||||
|
||||
/// The Clear commands (Delete is Clear Contents). As in Excel, the rows an applied filter hides
|
||||
|
||||
@@ -328,7 +328,11 @@ pub(super) fn range_fill(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
if r.count() > 2_000_000 {
|
||||
return Err(bad("range.fill", "range too large"));
|
||||
}
|
||||
let skip = cx.wb.sheet(sheet).map(|sh| super::filtered_rows(sh, *r)).unwrap_or_default();
|
||||
for at in r.iter() {
|
||||
if skip.contains(&at.row) {
|
||||
continue;
|
||||
}
|
||||
let style = cx.wb.sheet(sheet).map(|sh| sh.style_id(at)).unwrap_or_default();
|
||||
let base = match by_style.get(&style) {
|
||||
Some(c) => c.clone(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::cell::input_to_cell;
|
||||
use super::paste_special::paste_special;
|
||||
use super::{
|
||||
Cell, Clipboard, EngineError, Json, RangeRef, Result, Session, Value, bad, bool_param, cell_param_on, edit, json, ok, str_param, target_ranges,
|
||||
target_sheet,
|
||||
Cell, CellRef, Clipboard, EngineError, Json, RangeRef, Result, Session, Value, bad, bool_param, cell_param_on, edit, json, ok, str_param,
|
||||
target_ranges, target_sheet,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------- clipboard
|
||||
@@ -15,7 +15,7 @@ fn copy_impl(s: &mut Session, p: &Json, cut: bool) -> Result<Json> {
|
||||
return Err(multiple());
|
||||
}
|
||||
// The selection as made (untrimmed), for Insert Copied Cells.
|
||||
let selected = ranges.first().copied().unwrap_or_default();
|
||||
let mut selected = ranges.first().copied().unwrap_or_default();
|
||||
let d = s.doc()?;
|
||||
let Some(sh) = d.wb.sheet(sheet) else { return Err(EngineError::NoDocument) };
|
||||
let trim = |range: RangeRef| {
|
||||
@@ -31,7 +31,27 @@ fn copy_impl(s: &mut Session, p: &Json, cut: bool) -> Result<Json> {
|
||||
let (wb, block) = slide_together(&d.wb, sheet, &areas).ok_or_else(multiple)?;
|
||||
(std::sync::Arc::new(wb), block, areas)
|
||||
} else {
|
||||
(d.wb.clone(), trim(ranges.first().copied().unwrap_or_default()), Vec::new())
|
||||
let range = trim(ranges.first().copied().unwrap_or_default());
|
||||
// A copied range with rows a filter hides takes only the rows showing: they are copied
|
||||
// like a multi-area selection stacked top to bottom (formulas as values), as in Excel.
|
||||
// A cut still takes every row.
|
||||
let hidden = if cut { Default::default() } else { super::filtered_rows(sh, range) };
|
||||
let visible = if hidden.is_empty() { Vec::new() } else { visible_row_runs(range, &hidden) };
|
||||
let any_visible = !visible.is_empty();
|
||||
let packed = match visible.as_slice() {
|
||||
[] => (d.wb.clone(), range, Vec::new()),
|
||||
[only] => (d.wb.clone(), *only, Vec::new()),
|
||||
_ => {
|
||||
let (wb, block) = slide_together(&d.wb, sheet, &visible).ok_or_else(multiple)?;
|
||||
(std::sync::Arc::new(wb), block, visible)
|
||||
}
|
||||
};
|
||||
// Whole rows copied with some hidden by a filter come as the block of rows showing, so
|
||||
// Insert Copied Cells makes room for those rows, not for the hidden ones too.
|
||||
if !hidden.is_empty() && any_visible && selected.is_full_rows() {
|
||||
selected = RangeRef::rows(packed.1.start.row, packed.1.end.row);
|
||||
}
|
||||
packed
|
||||
};
|
||||
let text = crate::display::range_text(&wb, sheet, range);
|
||||
// The HTML rendering is for the host clipboard (UI); it can reach 4 MB, so agents and
|
||||
@@ -46,6 +66,56 @@ fn copy_impl(s: &mut Session, p: &Json, cut: bool) -> Result<Json> {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// The runs of consecutive rows of `range` not in `hidden`, as ranges of `range`'s columns.
|
||||
fn visible_row_runs(range: RangeRef, hidden: &std::collections::BTreeSet<u32>) -> Vec<RangeRef> {
|
||||
let mut runs = Vec::new();
|
||||
let mut start = None;
|
||||
for row in range.start.row..=range.end.row {
|
||||
match (hidden.contains(&row), start) {
|
||||
(false, None) => start = Some(row),
|
||||
(true, Some(first)) => {
|
||||
runs.push(RangeRef::new(CellRef::new(first, range.start.col), CellRef::new(row - 1, range.end.col)));
|
||||
start = None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(first) = start {
|
||||
runs.push(RangeRef::new(CellRef::new(first, range.start.col), CellRef::new(range.end.row, range.end.col)));
|
||||
}
|
||||
runs
|
||||
}
|
||||
|
||||
/// The validation rules of `src` cut into the areas of a packed block: one rule per area a rule
|
||||
/// touches, over that area's part moved by the area's shift. `sorted` areas run in order.
|
||||
fn pack_validations(src: &gridcraft_model::Sheet, sorted: &[RangeRef], shifts: &[(i64, i64)], across: bool) -> Vec<gridcraft_model::Validation> {
|
||||
let key = |c: CellRef| if across { c.col } else { c.row };
|
||||
let mut packed_rules = Vec::new();
|
||||
for dv in &src.validations {
|
||||
let Some(from) = dv.ranges.first().map(|r| r.start) else { continue };
|
||||
// The areas each of the rule's ranges touches lie side by side in the sorted list.
|
||||
let end_key = |a: &RangeRef| if across { a.end.col } else { a.end.row };
|
||||
let mut by_area: std::collections::BTreeMap<usize, Vec<RangeRef>> = Default::default();
|
||||
for r in &dv.ranges {
|
||||
let first = sorted.partition_point(|a| end_key(a) < key(r.start));
|
||||
for (i, a) in sorted.iter().enumerate().skip(first).take_while(|(_, a)| key(a.start) <= end_key(r)) {
|
||||
let (dr, dc) = shifts[i];
|
||||
let moved = r.intersection(a).and_then(|x| Some(RangeRef::new(x.start.offset(dr, dc)?, x.end.offset(dr, dc)?)));
|
||||
by_area.entry(i).or_default().extend(moved);
|
||||
}
|
||||
}
|
||||
for ranges in by_area.into_values() {
|
||||
let Some(to) = ranges.first().map(|r| r.start) else { continue };
|
||||
let mut rule = dv.clone();
|
||||
rule.f1 = super::super::data::move_rule_formula(&dv.f1, from, to);
|
||||
rule.f2 = dv.f2.as_ref().map(|f| super::super::data::move_rule_formula(f, from, to));
|
||||
rule.ranges = ranges;
|
||||
packed_rules.push(rule);
|
||||
}
|
||||
}
|
||||
packed_rules
|
||||
}
|
||||
|
||||
/// Excel copies a multi-area selection only when the areas line up: all cover the same rows (they
|
||||
/// slide together side by side, left to right) or all cover the same columns (stacked, top to
|
||||
/// bottom), without overlapping. Returns a copy of the workbook whose sheet holds the areas packed
|
||||
@@ -80,17 +150,35 @@ fn slide_together(wb: &gridcraft_model::Workbook, sheet: usize, areas: &[RangeRe
|
||||
dst.spill.retain(|c, _| !block.contains(*c));
|
||||
dst.spill_ranges.retain(|c, _| !block.contains(*c));
|
||||
dst.comments.retain(|c, _| !block.contains(*c));
|
||||
dst.hyperlinks.retain(|c, _| !block.contains(*c));
|
||||
super::super::data::remove_validation_cells(&mut dst.validations, &[block]);
|
||||
dst.merges.retain(|m| !m.intersects(&block));
|
||||
if across {
|
||||
dst.cols.retain(|c, _| !(block.start.col..=block.end.col).contains(c));
|
||||
} else {
|
||||
dst.rows.retain(|r, _| !(block.start.row..=block.end.row).contains(r));
|
||||
}
|
||||
// Where each area lands: the shift of its cells, and its first row or column in the block.
|
||||
let key = |c: CellRef| if across { c.col } else { c.row };
|
||||
let mut next = if across { anchor.col } else { anchor.row };
|
||||
let mut shifts: Vec<(i64, i64)> = Vec::with_capacity(sorted.len());
|
||||
for a in &sorted {
|
||||
let (dr, dc) = if across { (0, i64::from(next) - i64::from(a.start.col)) } else { (i64::from(next) - i64::from(a.start.row), 0) };
|
||||
let cells = src.cells.iter_range(*a).map(|(c, _)| c).chain(src.spill.keys().copied().filter(|c| a.contains(*c)));
|
||||
for c in cells {
|
||||
let to = i64::from(next);
|
||||
shifts.push(if across { (0, to - i64::from(a.start.col)) } else { (to - i64::from(a.start.row), 0) });
|
||||
next = next.saturating_add(if across { a.width() } else { a.height() });
|
||||
}
|
||||
// The areas never overlap and run in order, so the area holding a cell is found by search:
|
||||
// each map below is read once, whatever the number of areas.
|
||||
let area_of = |c: CellRef| -> Option<usize> {
|
||||
let i = sorted.partition_point(|a| key(a.start) <= key(c)).checked_sub(1)?;
|
||||
sorted.get(i)?.contains(c).then_some(i)
|
||||
};
|
||||
let moved = |c: CellRef| -> Option<CellRef> {
|
||||
let (dr, dc) = shifts[area_of(c)?];
|
||||
c.offset(dr, dc)
|
||||
};
|
||||
for (a, &(dr, dc)) in sorted.iter().zip(&shifts) {
|
||||
for (c, _) in src.cells.iter_range(*a) {
|
||||
let Some(to) = c.offset(dr, dc) else { continue };
|
||||
let style = src.cell(c).map(|x| x.style).unwrap_or_default();
|
||||
dst.set_cell(to, Cell { value: src.value(c), formula: None, style, py: None });
|
||||
@@ -98,17 +186,38 @@ fn slide_together(wb: &gridcraft_model::Workbook, sheet: usize, areas: &[RangeRe
|
||||
dst.cell_pictures.insert(to, picture.clone());
|
||||
}
|
||||
}
|
||||
for (c, comment) in src.comments.iter().filter(|(c, _)| a.contains(**c)) {
|
||||
if let Some(to) = c.offset(dr, dc) {
|
||||
dst.comments.insert(to, comment.clone());
|
||||
}
|
||||
}
|
||||
for c in src.spill.keys().copied() {
|
||||
let Some(to) = moved(c) else { continue };
|
||||
let style = src.cell(c).map(|x| x.style).unwrap_or_default();
|
||||
dst.set_cell(to, Cell { value: src.value(c), formula: None, style, py: None });
|
||||
if let Some(picture) = src.cell_pictures.get(&c) {
|
||||
dst.cell_pictures.insert(to, picture.clone());
|
||||
}
|
||||
for m in src.merges.iter().filter(|m| a.contains_range(m)) {
|
||||
if let (Some(s), Some(e)) = (m.start.offset(dr, dc), m.end.offset(dr, dc)) {
|
||||
dst.merges.push(RangeRef::new(s, e));
|
||||
}
|
||||
}
|
||||
// Links and validation rules follow their cells too, so a row the filter hides leaves its own
|
||||
// behind.
|
||||
for (c, link) in &src.hyperlinks {
|
||||
if let Some(to) = moved(*c) {
|
||||
dst.hyperlinks.insert(to, link.clone());
|
||||
}
|
||||
// Column widths (side by side) or row heights and hidden rows (stacked) follow their cells.
|
||||
}
|
||||
let packed_rules = pack_validations(src, &sorted, &shifts, across);
|
||||
for (c, comment) in &src.comments {
|
||||
if let Some(to) = moved(*c) {
|
||||
dst.comments.insert(to, comment.clone());
|
||||
}
|
||||
}
|
||||
for m in &src.merges {
|
||||
let Some(i) = area_of(m.start).filter(|&i| sorted[i].contains_range(m)) else { continue };
|
||||
let (dr, dc) = shifts[i];
|
||||
if let (Some(s), Some(e)) = (m.start.offset(dr, dc), m.end.offset(dr, dc)) {
|
||||
dst.merges.push(RangeRef::new(s, e));
|
||||
}
|
||||
}
|
||||
// Column widths (side by side) or row heights and hidden rows (stacked) follow their cells.
|
||||
let mut next = if across { anchor.col } else { anchor.row };
|
||||
for a in &sorted {
|
||||
if across {
|
||||
for (c, info) in src.cols.range(a.start.col..=a.end.col) {
|
||||
dst.cols.insert(next.saturating_add(c.saturating_sub(a.start.col)), *info);
|
||||
@@ -120,6 +229,7 @@ fn slide_together(wb: &gridcraft_model::Workbook, sheet: usize, areas: &[RangeRe
|
||||
}
|
||||
next = next.saturating_add(if across { a.width() } else { a.height() });
|
||||
}
|
||||
dst.validations.extend(packed_rules);
|
||||
Some((out, block))
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,9 @@ pub(super) fn fill_dir(s: &mut Session, p: &Json, dr: i64, dc: i64) -> Result<Js
|
||||
} else {
|
||||
RangeRef::new(CellRef::new(r.start.row, r.end.col), r.end)
|
||||
};
|
||||
let skip = s.doc()?.wb.sheet(sheet).map(|sh| super::filtered_rows(sh, r)).unwrap_or_default();
|
||||
edit(s, |cx| {
|
||||
crate::fill::fill(cx, sheet, src, r, crate::fill::FillMode::Copy)?;
|
||||
crate::fill::fill_except(cx, sheet, src, r, crate::fill::FillMode::Copy, &skip)?;
|
||||
Ok(Json::Null)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,6 +45,12 @@ pub(super) fn paste_special(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
&& !sel.is_full_rows()
|
||||
&& sel.count() <= 4_000_000;
|
||||
let (tiles_r, tiles_c) = if tile { (sel.height() / h, sel.width() / w) } else { (1, 1) };
|
||||
// A single cell or row repeated down a filtered selection skips the rows the filter hides; a
|
||||
// taller block is pasted straight down, hidden rows included, as in Excel.
|
||||
let skip_rows = match s.doc()?.wb.sheet(dest_sheet) {
|
||||
Some(sh) if tile && h == 1 => super::filtered_rows(sh, sel),
|
||||
_ => Default::default(),
|
||||
};
|
||||
// Excel refuses a paste that would run off the sheet (whole columns pasted below row 1, whole
|
||||
// rows right of column A, a whole column transposed) instead of pasting part of it.
|
||||
if at.offset(i64::from(tiles_r * h) - 1, i64::from(tiles_c * w) - 1).is_none() {
|
||||
@@ -134,6 +140,9 @@ pub(super) fn paste_special(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let sc = CellRef::new(src.start.row + r, src.start.col + c);
|
||||
let (dr, dc) = if transpose { (c, r) } else { (r, c) };
|
||||
let Some(dest) = at.offset((tr * h + dr) as i64, (tc * w + dc) as i64) else { continue };
|
||||
if skip_rows.contains(&dest.row) {
|
||||
continue;
|
||||
}
|
||||
let src_cell = if cut_move { moved_formulas.get(&sc).cloned() } else { src_sheet.cell(sc).cloned() };
|
||||
let src_val = src_sheet.value(sc);
|
||||
let src_picture = if cut_move { moved_pictures.get(&sc) } else { src_sheet.cell_pictures.get(&sc) }.cloned();
|
||||
@@ -326,7 +335,7 @@ pub(super) fn paste_special(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
}
|
||||
let pasted = RangeRef::new(at, at.offset_clamped(i64::from(tiles_r * h) - 1, i64::from(tiles_c * w) - 1));
|
||||
if !link && matches!(what, What::All | What::AllExceptBorders | What::Validation) {
|
||||
paste_validations(cx, src_sheet, src, dest_sheet, pasted, transpose, skip_blanks)?;
|
||||
paste_validations(cx, src_sheet, src, dest_sheet, pasted, PasteMode { transpose, skip_blanks, skip_rows: &skip_rows })?;
|
||||
}
|
||||
if what == What::ColumnWidths {
|
||||
// Each pasted column takes the width of the column copied to it (transposed, the
|
||||
@@ -394,6 +403,31 @@ const MAX_PASTE_LINK_CELLS: u64 = 1_048_576;
|
||||
/// selection makes one range per tile).
|
||||
const MAX_PASTE_VALIDATION_RANGES: usize = 100_000;
|
||||
|
||||
/// How a validation paste lays the rules out: transposed or not, keeping the destination's rule under
|
||||
/// blank source cells, and leaving the rows a filter hides as they are.
|
||||
#[derive(Clone, Copy)]
|
||||
struct PasteMode<'a> {
|
||||
transpose: bool,
|
||||
skip_blanks: bool,
|
||||
skip_rows: &'a std::collections::BTreeSet<u32>,
|
||||
}
|
||||
|
||||
/// `range` less the rows in `skip`: the runs of rows between them, as ranges of its columns.
|
||||
fn without_rows(range: RangeRef, skip: &std::collections::BTreeSet<u32>) -> Vec<RangeRef> {
|
||||
let mut out = Vec::new();
|
||||
let mut next = range.start.row;
|
||||
for &row in skip.range(range.start.row..=range.end.row) {
|
||||
if row > next {
|
||||
out.push(RangeRef::new(CellRef::new(next, range.start.col), CellRef::new(row - 1, range.end.col)));
|
||||
}
|
||||
next = row + 1;
|
||||
}
|
||||
if next <= range.end.row {
|
||||
out.push(RangeRef::new(CellRef::new(next, range.start.col), CellRef::new(range.end.row, range.end.col)));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Pasting validation works on ranges, as Excel stores it: the pasted rectangle is cut out of the
|
||||
/// destination's rules (cells whose source had no rule end up with none), then each source rule
|
||||
/// that covers part of the copied block `src` comes along as one rule over the translated parts.
|
||||
@@ -404,9 +438,9 @@ fn paste_validations(
|
||||
src: RangeRef,
|
||||
dest_sheet: usize,
|
||||
pasted: RangeRef,
|
||||
transpose: bool,
|
||||
skip_blanks: bool,
|
||||
mode: PasteMode<'_>,
|
||||
) -> Result<()> {
|
||||
let PasteMode { transpose, skip_blanks, skip_rows } = mode;
|
||||
let (h, w) = if transpose { (src.width(), src.height()) } else { (src.height(), src.width()) };
|
||||
let (tiles_r, tiles_c) = (pasted.height() / h.max(1), pasted.width() / w.max(1));
|
||||
let mut rules = Vec::new();
|
||||
@@ -426,7 +460,9 @@ fn paste_validations(
|
||||
let (r, c) = if transpose { (c, r) } else { (r, c) };
|
||||
for tr in 0..tiles_r {
|
||||
for tc in 0..tiles_c {
|
||||
if let Some(d) = pasted.start.offset(i64::from(tr * h + r), i64::from(tc * w + c)) {
|
||||
if let Some(d) = pasted.start.offset(i64::from(tr * h + r), i64::from(tc * w + c))
|
||||
&& !skip_rows.contains(&d.row)
|
||||
{
|
||||
cells.push((d.row, d.col));
|
||||
}
|
||||
}
|
||||
@@ -490,6 +526,8 @@ fn paste_validations(
|
||||
}
|
||||
}
|
||||
}
|
||||
// The rows a filter hides keep what they have.
|
||||
let ranges: Vec<RangeRef> = ranges.into_iter().flat_map(|r| without_rows(r, skip_rows)).collect();
|
||||
let Some(anchor) = ranges.first().map(|r| r.start) else { continue };
|
||||
let mut rule = dv.clone();
|
||||
// Relative references move with the rule, as pasted formulas do: they are written for the
|
||||
@@ -507,7 +545,7 @@ fn paste_validations(
|
||||
cleared.extend(rules.iter().flat_map(|r| r.ranges.iter().copied()));
|
||||
super::super::data::remove_validation_cells(&mut sh.validations, &cleared);
|
||||
} else {
|
||||
super::super::data::remove_validation_cells(&mut sh.validations, &[pasted]);
|
||||
super::super::data::remove_validation_cells(&mut sh.validations, &without_rows(pasted, skip_rows));
|
||||
}
|
||||
sh.validations.extend(rules);
|
||||
Ok(())
|
||||
@@ -529,19 +567,19 @@ pub(crate) fn rewrite_all_formulas(wb: &mut gridcraft_model::Workbook, target: &
|
||||
let on_target = moves_arrays && host.eq_ignore_ascii_case(target);
|
||||
let Some(sheet) = wb.sheet_mut(si) else { continue };
|
||||
for (c, f) in keys {
|
||||
let Some(expr) = f.expr() else { continue };
|
||||
let new = gridcraft_formula::adjust::adjust(expr, &host, target, e);
|
||||
let text = gridcraft_formula::print(&new);
|
||||
// Borrows the tree and copies and prints only the formulas the edit changes. A formula
|
||||
// the edit leaves alone keeps its stored text, even if that text is not canonical.
|
||||
let Some(new) = f.with_expr(|x| gridcraft_formula::adjust::adjust_if_changed(x, &host, target, e)) else { continue };
|
||||
let array = match f.array {
|
||||
Some(a) if on_target => crate::cmd::sheet::map_range(a, e),
|
||||
a => a,
|
||||
};
|
||||
if (text != f.text || array != f.array)
|
||||
&& let Some(cell) = sheet.cells.get_mut(c)
|
||||
{
|
||||
let mut nf = Formula::from_expr(new);
|
||||
nf.array = array;
|
||||
nf.dynamic = f.dynamic;
|
||||
let nf = match new {
|
||||
Some(new) => Formula { array, dynamic: f.dynamic, ..Formula::from_expr(new) },
|
||||
None if array != f.array => Formula { array, ..(*f).clone() },
|
||||
None => continue,
|
||||
};
|
||||
if let Some(cell) = sheet.cells.get_mut(c) {
|
||||
cell.formula = Some(std::sync::Arc::new(nf));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,11 +55,12 @@ pub(super) fn delete_sheet(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
cx.wb.sheets.get(si).map(|s| s.cells.iter().filter_map(|(c, x)| x.formula.clone().map(|f| (c, f))).collect()).unwrap_or_default();
|
||||
let Some(sh) = cx.wb.sheet_mut(si) else { continue };
|
||||
for (c, f) in keys {
|
||||
if let Some(e) = f.expr() {
|
||||
let ne = gridcraft_formula::adjust::delete_sheet(e, &name, &order);
|
||||
if let Some(cell) = sh.cells.get_mut(c) {
|
||||
cell.formula = Some(Arc::new(gridcraft_model::Formula::from_expr(ne)));
|
||||
}
|
||||
// Only formulas whose tree changes are rewritten; the others keep their text.
|
||||
let changed = f.with_expr(|e| Some(gridcraft_formula::adjust::delete_sheet(e.clone(), &name, &order)).filter(|ne| ne != e));
|
||||
if let Some(Some(ne)) = changed
|
||||
&& let Some(cell) = sh.cells.get_mut(c)
|
||||
{
|
||||
cell.formula = Some(Arc::new(gridcraft_model::Formula::from_expr(ne)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,14 +110,12 @@ pub(super) fn rename_sheet(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
cx.wb.sheets.get(si).map(|s| s.cells.iter().filter_map(|(c, x)| x.formula.clone().map(|f| (c, f))).collect()).unwrap_or_default();
|
||||
let Some(sh) = cx.wb.sheet_mut(si) else { continue };
|
||||
for (c, f) in keys {
|
||||
if let Some(e) = f.expr() {
|
||||
let ne = gridcraft_formula::adjust::rename_sheet(e, &old, &name);
|
||||
let text = gridcraft_formula::print(&ne);
|
||||
if text != f.text
|
||||
&& let Some(cell) = sh.cells.get_mut(c)
|
||||
{
|
||||
cell.formula = Some(Arc::new(gridcraft_model::Formula::from_expr(ne)));
|
||||
}
|
||||
// Compares trees, so unchanged formulas are not printed; they keep their text.
|
||||
let changed = f.with_expr(|e| Some(gridcraft_formula::adjust::rename_sheet(e.clone(), &old, &name)).filter(|ne| ne != e));
|
||||
if let Some(Some(ne)) = changed
|
||||
&& let Some(cell) = sh.cells.get_mut(c)
|
||||
{
|
||||
cell.formula = Some(Arc::new(gridcraft_model::Formula::from_expr(ne)));
|
||||
}
|
||||
}
|
||||
for ch in sh.charts.iter_mut() {
|
||||
|
||||
Reference in New Issue
Block a user