1#![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 self.fill_cell_link(sheet, source_row, column, row, column, &mut diff_list)?;
218
219 index = (index + sign) % source_area.height;
220 }
221 }
222 self.push_diff_list(diff_list);
223 self.evaluate();
224 Ok(())
225 }
226
227 pub fn auto_fill_columns(&mut self, source_area: &Area, to_column: i32) -> Result<(), String> {
230 let mut diff_list = Vec::new();
231 let sheet = source_area.sheet;
232 let row1 = source_area.row;
233 let column1 = source_area.column;
234 let width = source_area.width;
235 let height = source_area.height;
236
237 if self.model.workbook.worksheet(sheet).is_err() {
239 return Err(format!("Invalid worksheet index: '{sheet}'"));
240 }
241
242 if !is_valid_column_number(column1) {
243 return Err(format!("Invalid column: '{column1}'"));
244 }
245 if !is_valid_row(row1) {
246 return Err(format!("Invalid row: '{row1}'"));
247 }
248 if width <= 0 || height <= 0 {
249 return Err(format!("Invalid width='{}' or height='{}'", width, height));
250 }
251
252 let last_column = column1 + width - 1;
253 let last_row = row1 + height - 1;
254
255 if !is_valid_column_number(last_column) {
256 return Err(format!("Invalid column: '{last_column}'"));
257 }
258 if !is_valid_row(last_row) {
259 return Err(format!("Invalid row: '{last_row}'"));
260 }
261
262 if !is_valid_column_number(to_column) {
263 return Err(format!("Invalid column: '{to_column}'"));
264 }
265
266 let anchor_column;
268 let sign;
269 let column_range: Vec<i32>;
271
272 if to_column > last_column {
273 anchor_column = column1;
275 sign = 1;
276 column_range = (last_column + 1..to_column + 1).collect();
277 } else if to_column < column1 {
278 anchor_column = last_column;
280 sign = -1;
281 column_range = (to_column..column1).rev().collect();
282 } else {
283 return Err("Invalid parameters for autofill".to_string());
284 }
285
286 let fill_col_start = if sign < 0 { to_column } else { last_column + 1 };
288 let fill_col_end = if sign < 0 { column1 - 1 } else { to_column };
289 let saved_cse = self.collect_and_clear_cse_in_fill_target(
290 sheet,
291 row1,
292 last_row,
293 fill_col_start,
294 fill_col_end,
295 )?;
296
297 for row in row1..=last_row {
298 let mut index = 0;
299 let locale = &self.model.locale;
300 let values = if sign < 0 {
301 (column1..=last_column)
302 .rev()
303 .map(|column| self.get_cell_content(sheet, row, column))
304 .collect::<Result<Vec<_>, _>>()?
305 } else {
306 (column1..=last_column)
307 .map(|column| self.get_cell_content(sheet, row, column))
308 .collect::<Result<Vec<_>, _>>()?
309 };
310 let case_seed = self.get_cell_content(sheet, row, column1)?;
311 let possible_progression = detect_progression(&values, locale, &case_seed);
312 for (range_idx, column_ref) in column_range.iter().enumerate() {
313 let column = *column_ref;
314
315 let old_value = saved_cse.get(&(row, column)).cloned().unwrap_or_else(|| {
317 self.model
318 .workbook
319 .worksheet(sheet)
320 .ok()
321 .and_then(|ws| ws.cell(row, column).cloned())
322 });
323 let old_style = self.model.get_cell_style_or_none(sheet, row, column)?;
324
325 let source_column = anchor_column + index;
326 let target_value;
327
328 if let Some(ref detected_progression) = possible_progression {
330 target_value = detected_progression.next(range_idx);
331 } else {
332 target_value = self
333 .model
334 .extend_to(sheet, row, source_column, row, column)?;
335 }
336
337 self.model
338 .set_user_input(sheet, row, column, target_value.to_string())?;
339
340 let new_style = self.model.get_style_for_cell(sheet, row, source_column)?;
341 self.model.set_cell_style(sheet, row, column, &new_style)?;
344
345 diff_list.push(Diff::SetCellStyle {
347 sheet,
348 row,
349 column,
350 old_value: Box::new(old_style),
351 new_value: Box::new(new_style),
352 });
353
354 diff_list.push(Diff::SetCellValue {
355 sheet,
356 row,
357 column,
358 new_value: target_value.to_string(),
359 old_value: Box::new(old_value),
360 });
361
362 self.fill_cell_link(sheet, row, source_column, row, column, &mut diff_list)?;
363
364 index = (index + sign) % source_area.width;
365 }
366 }
367 self.push_diff_list(diff_list);
368 self.evaluate();
369 Ok(())
370 }
371
372 fn fill_cell_link(
376 &mut self,
377 sheet: u32,
378 source_row: i32,
379 source_column: i32,
380 row: i32,
381 column: i32,
382 diff_list: &mut Vec<Diff>,
383 ) -> Result<(), String> {
384 let new_link = self.model.get_cell_link(sheet, source_row, source_column)?;
385 let old_link = self.model.get_cell_link(sheet, row, column)?;
386 if old_link == new_link {
387 return Ok(());
388 }
389 match &new_link {
390 Some(link) => self.model.set_cell_link(sheet, row, column, link.clone())?,
391 None => self.model.delete_cell_link(sheet, row, column)?,
392 }
393 diff_list.push(Diff::SetCellLink {
394 sheet,
395 row,
396 column,
397 old_value: Box::new(old_link),
398 new_value: Box::new(new_link),
399 });
400 Ok(())
401 }
402}