ironcalc_base/user_model/
autofill.rs1#![deny(missing_docs)]
2
3use std::collections::HashMap;
4
5use crate::{
6 expressions::{
7 types::Area,
8 utils::{is_valid_column_number, is_valid_row},
9 },
10 model::CellStructure,
11 user_model::sequence_detector::detect_progression,
12 UserModel,
13};
14
15use crate::user_model::history::Diff;
16
17impl<'a> UserModel<'a> {
18 fn collect_and_clear_cse_in_fill_target(
25 &mut self,
26 sheet: u32,
27 row_start: i32,
28 row_end: i32,
29 col_start: i32,
30 col_end: i32,
31 ) -> Result<HashMap<(i32, i32), Option<crate::types::Cell>>, String> {
32 let mut anchors: Vec<(i32, i32, i32, i32)> = Vec::new();
35 let mut handled: Vec<(i32, i32)> = Vec::new();
36 for row in row_start..=row_end {
37 for col in col_start..=col_end {
38 let (ar, ac, w, h) = match self.model.get_cell_structure(sheet, row, col)? {
39 CellStructure::ArrayFormula { range: (w, h) } if w > 1 || h > 1 => {
40 (row, col, w, h)
41 }
42 CellStructure::SpillArray {
43 anchor: (ar, ac),
44 range: (w, h),
45 } => (ar, ac, w, h),
46 _ => continue,
47 };
48 if handled.contains(&(ar, ac)) {
49 continue;
50 }
51 handled.push((ar, ac));
52 let completely_covered = ar >= row_start
53 && ar + h - 1 <= row_end
54 && ac >= col_start
55 && ac + w - 1 <= col_end;
56 if !completely_covered {
57 return Err(
58 "Cannot autofill: selection partially overlaps an array formula"
59 .to_string(),
60 );
61 }
62 anchors.push((ar, ac, w, h));
63 }
64 }
65
66 let mut saved: HashMap<(i32, i32), Option<crate::types::Cell>> = HashMap::new();
68 for (ar, ac, w, h) in anchors {
69 for r in ar..ar + h {
70 for c in ac..ac + w {
71 let cell = self.model.workbook.worksheet(sheet)?.cell(r, c).cloned();
72 saved.insert((r, c), cell);
73 }
74 }
75 let ws = self.model.workbook.worksheet_mut(sheet)?;
76 for r in ar..ar + h {
77 for c in ac..ac + w {
78 let _ = ws.cell_clear_contents(r, c);
79 }
80 }
81 }
82 Ok(saved)
83 }
84
85 pub fn auto_fill_rows(&mut self, source_area: &Area, to_row: i32) -> Result<(), String> {
88 let mut diff_list = Vec::new();
89 let sheet = source_area.sheet;
90 let row1 = source_area.row;
91 let column1 = source_area.column;
92 let width = source_area.width;
93 let height = source_area.height;
94
95 if self.model.workbook.worksheet(sheet).is_err() {
97 return Err(format!("Invalid worksheet index: '{sheet}'"));
98 }
99
100 if !is_valid_column_number(column1) {
101 return Err(format!("Invalid column: '{column1}'"));
102 }
103 if !is_valid_row(row1) {
104 return Err(format!("Invalid row: '{row1}'"));
105 }
106 if width <= 0 || height <= 0 {
107 return Err(format!("Invalid width='{}' or height='{}'", width, height));
108 }
109
110 let last_column = column1 + width - 1;
111 let last_row = row1 + height - 1;
112
113 if !is_valid_column_number(last_column) {
114 return Err(format!("Invalid column: '{last_column}'"));
115 }
116 if !is_valid_row(last_row) {
117 return Err(format!("Invalid row: '{last_row}'"));
118 }
119
120 if !is_valid_row(to_row) {
121 return Err(format!("Invalid row: '{to_row}'"));
122 }
123
124 let anchor_row;
126 let sign;
127 let row_range: Vec<i32>;
129
130 if to_row > last_row {
131 anchor_row = row1;
133 sign = 1;
134 row_range = (last_row + 1..=to_row).collect();
135 } else if to_row < row1 {
136 anchor_row = last_row;
138 sign = -1;
139 row_range = (to_row..row1).rev().collect();
140 } else {
141 return Err("Invalid parameters for autofill".to_string());
142 }
143
144 let fill_row_start = if sign < 0 { to_row } else { last_row + 1 };
146 let fill_row_end = if sign < 0 { row1 - 1 } else { to_row };
147 let saved_cse = self.collect_and_clear_cse_in_fill_target(
148 sheet,
149 fill_row_start,
150 fill_row_end,
151 column1,
152 last_column,
153 )?;
154
155 for column in column1..=last_column {
156 let mut index = 0;
157 let locale = &self.model.locale;
158 let values = if sign < 0 {
159 (row1..=last_row)
160 .rev()
161 .map(|row| self.get_cell_content(sheet, row, column))
162 .collect::<Result<Vec<_>, _>>()?
163 } else {
164 (row1..=last_row)
165 .map(|row| self.get_cell_content(sheet, row, column))
166 .collect::<Result<Vec<_>, _>>()?
167 };
168 let case_seed = self.get_cell_content(sheet, row1, column)?;
169 let possible_progression = detect_progression(&values, locale, &case_seed);
170 for (range_idx, row_ref) in row_range.iter().enumerate() {
171 let row = *row_ref;
172
173 let old_value = saved_cse.get(&(row, column)).cloned().unwrap_or_else(|| {
174 self.model
175 .workbook
176 .worksheet(sheet)
177 .ok()
178 .and_then(|ws| ws.cell(row, column).cloned())
179 });
180 let old_style = self.model.get_cell_style_or_none(sheet, row, column)?;
181
182 let source_row = anchor_row + index;
183 let target_value;
184
185 if let Some(ref detected_progression) = possible_progression {
187 target_value = detected_progression.next(range_idx);
188 } else {
189 target_value = self
190 .model
191 .extend_to(sheet, source_row, column, row, column)?;
192 }
193
194 self.model
195 .set_user_input(sheet, row, column, target_value.to_string())?;
196
197 let new_style = self.model.get_style_for_cell(sheet, source_row, column)?;
199 self.model.set_cell_style(sheet, row, column, &new_style)?;
200
201 diff_list.push(Diff::SetCellStyle {
203 sheet,
204 row,
205 column,
206 old_value: Box::new(old_style),
207 new_value: Box::new(new_style),
208 });
209 diff_list.push(Diff::SetCellValue {
210 sheet,
211 row,
212 column,
213 new_value: target_value.to_string(),
214 old_value: Box::new(old_value),
215 });
216
217 index = (index + sign) % source_area.height;
218 }
219 }
220 self.push_diff_list(diff_list);
221 self.evaluate();
222 Ok(())
223 }
224
225 pub fn auto_fill_columns(&mut self, source_area: &Area, to_column: i32) -> Result<(), String> {
228 let mut diff_list = Vec::new();
229 let sheet = source_area.sheet;
230 let row1 = source_area.row;
231 let column1 = source_area.column;
232 let width = source_area.width;
233 let height = source_area.height;
234
235 if self.model.workbook.worksheet(sheet).is_err() {
237 return Err(format!("Invalid worksheet index: '{sheet}'"));
238 }
239
240 if !is_valid_column_number(column1) {
241 return Err(format!("Invalid column: '{column1}'"));
242 }
243 if !is_valid_row(row1) {
244 return Err(format!("Invalid row: '{row1}'"));
245 }
246 if width <= 0 || height <= 0 {
247 return Err(format!("Invalid width='{}' or height='{}'", width, height));
248 }
249
250 let last_column = column1 + width - 1;
251 let last_row = row1 + height - 1;
252
253 if !is_valid_column_number(last_column) {
254 return Err(format!("Invalid column: '{last_column}'"));
255 }
256 if !is_valid_row(last_row) {
257 return Err(format!("Invalid row: '{last_row}'"));
258 }
259
260 if !is_valid_column_number(to_column) {
261 return Err(format!("Invalid column: '{to_column}'"));
262 }
263
264 let anchor_column;
266 let sign;
267 let column_range: Vec<i32>;
269
270 if to_column > last_column {
271 anchor_column = column1;
273 sign = 1;
274 column_range = (last_column + 1..to_column + 1).collect();
275 } else if to_column < column1 {
276 anchor_column = last_column;
278 sign = -1;
279 column_range = (to_column..column1).rev().collect();
280 } else {
281 return Err("Invalid parameters for autofill".to_string());
282 }
283
284 let fill_col_start = if sign < 0 { to_column } else { last_column + 1 };
286 let fill_col_end = if sign < 0 { column1 - 1 } else { to_column };
287 let saved_cse = self.collect_and_clear_cse_in_fill_target(
288 sheet,
289 row1,
290 last_row,
291 fill_col_start,
292 fill_col_end,
293 )?;
294
295 for row in row1..=last_row {
296 let mut index = 0;
297 let locale = &self.model.locale;
298 let values = if sign < 0 {
299 (column1..=last_column)
300 .rev()
301 .map(|column| self.get_cell_content(sheet, row, column))
302 .collect::<Result<Vec<_>, _>>()?
303 } else {
304 (column1..=last_column)
305 .map(|column| self.get_cell_content(sheet, row, column))
306 .collect::<Result<Vec<_>, _>>()?
307 };
308 let case_seed = self.get_cell_content(sheet, row, column1)?;
309 let possible_progression = detect_progression(&values, locale, &case_seed);
310 for (range_idx, column_ref) in column_range.iter().enumerate() {
311 let column = *column_ref;
312
313 let old_value = saved_cse.get(&(row, column)).cloned().unwrap_or_else(|| {
315 self.model
316 .workbook
317 .worksheet(sheet)
318 .ok()
319 .and_then(|ws| ws.cell(row, column).cloned())
320 });
321 let old_style = self.model.get_cell_style_or_none(sheet, row, column)?;
322
323 let source_column = anchor_column + index;
324 let target_value;
325
326 if let Some(ref detected_progression) = possible_progression {
328 target_value = detected_progression.next(range_idx);
329 } else {
330 target_value = self
331 .model
332 .extend_to(sheet, row, source_column, row, column)?;
333 }
334
335 self.model
336 .set_user_input(sheet, row, column, target_value.to_string())?;
337
338 let new_style = self.model.get_style_for_cell(sheet, row, source_column)?;
339 self.model.set_cell_style(sheet, row, column, &new_style)?;
342
343 diff_list.push(Diff::SetCellStyle {
345 sheet,
346 row,
347 column,
348 old_value: Box::new(old_style),
349 new_value: Box::new(new_style),
350 });
351
352 diff_list.push(Diff::SetCellValue {
353 sheet,
354 row,
355 column,
356 new_value: target_value.to_string(),
357 old_value: Box::new(old_value),
358 });
359
360 index = (index + sign) % source_area.width;
361 }
362 }
363 self.push_diff_list(diff_list);
364 self.evaluate();
365 Ok(())
366 }
367}