1use std::sync::Arc;
5
6use crate::base::date::{MONTHS, WEEKDAYS, datetime_from_serial, days_in_month, serial_from_ymd};
7use crate::base::{CellRef, RangeRef, Value};
8use crate::model::{Cell, Formula, Workbook};
9use crate::numfmt::{FormatKind, NumberFormat};
10
11use crate::engine::Result;
12use crate::engine::cmd::Ctx;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum FillMode {
16 Series,
17 Alternate,
20 Copy,
21 Formats,
22 ValuesOnly,
24 Extend,
26 Dates(DateUnit),
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum DateUnit {
36 Day,
37 Weekday,
39 Month,
40 Year,
41}
42
43impl FillMode {
44 fn extends_series(self) -> bool {
46 matches!(self, FillMode::Series | FillMode::Extend | FillMode::ValuesOnly | FillMode::Dates(_))
47 }
48}
49
50fn is_date_format(fmt: &str) -> bool {
53 NumberFormat::parse(fmt).kind() == FormatKind::Date
54}
55
56pub fn starts_with_date(wb: &Workbook, sheet: usize, src: RangeRef) -> bool {
59 let Some(cell) = wb.sheet(sheet).and_then(|sh| sh.cell(src.start)) else { return false };
60 cell.formula.is_none() && cell.value.is_number() && is_date_format(wb.styles.get(cell.style).num_fmt.as_str())
61}
62
63fn lists() -> Vec<Vec<String>> {
65 let short_days: Vec<String> = WEEKDAYS.iter().map(|d| d.get(..3).unwrap_or(d).to_string()).collect();
66 let short_months: Vec<String> = MONTHS.iter().map(|m| m.get(..3).unwrap_or(m).to_string()).collect();
67 vec![
68 short_days,
69 WEEKDAYS.iter().map(|s| s.to_string()).collect(),
70 short_months,
71 MONTHS.iter().map(|s| s.to_string()).collect(),
72 vec!["Q1".into(), "Q2".into(), "Q3".into(), "Q4".into()],
73 ]
74}
75
76pub const MAX_FILL_CELLS: u64 = 5_000_000;
78
79pub fn holds_source_in_corner(src: RangeRef, target: RangeRef) -> bool {
83 target.contains_range(&src)
84 && (target.start.row == src.start.row || target.end.row == src.end.row)
85 && (target.start.col == src.start.col || target.end.col == src.end.col)
86}
87
88pub fn fill(cx: &mut Ctx, sheet: usize, src: RangeRef, target: RangeRef, mode: FillMode) -> Result<()> {
90 let custom = cx.wb.custom_lists.clone();
91 let Some(sh) = cx.wb.sheet(sheet) else { return Ok(()) };
92 let vertical = target.height() != src.height() || target.width() == src.width();
94 let forward = if vertical {
95 target.end.row > src.end.row || target.start.row == src.start.row
96 } else {
97 target.end.col > src.end.col || target.start.col == src.start.col
98 };
99 let (lane_count, steps) = if vertical {
102 let steps = if forward { target.end.row.saturating_sub(src.end.row) } else { src.start.row.saturating_sub(target.start.row) };
103 (src.width(), steps)
104 } else {
105 let steps = if forward { target.end.col.saturating_sub(src.end.col) } else { src.start.col.saturating_sub(target.start.col) };
106 (src.height(), steps)
107 };
108 if u64::from(lane_count).saturating_mul(u64::from(steps)) > MAX_FILL_CELLS {
109 return Err(crate::engine::EngineError::Other("The fill is too large.".into()));
110 }
111 let lanes: Vec<u32> = if vertical { (src.start.col..=src.end.col).collect() } else { (src.start.row..=src.end.row).collect() };
112 let src_len = if vertical { src.height() } else { src.width() };
113 let mut writes: Vec<(CellRef, Option<Cell>)> = Vec::new();
114 let mut pictures = Vec::new();
115 for lane in lanes {
116 let src_cells: Vec<(CellRef, Option<Cell>)> = (0..src_len)
117 .map(|i| {
118 let c = if vertical { CellRef::new(src.start.row + i, lane) } else { CellRef::new(lane, src.start.col + i) };
119 (c, sh.cell(c).cloned())
120 })
121 .collect();
122 let targets: Vec<CellRef> = if vertical {
123 let rows: Vec<u32> =
124 if forward { (src.end.row + 1..=target.end.row).collect() } else { (target.start.row..src.start.row).rev().collect() };
125 rows.into_iter().map(|r| CellRef::new(r, lane)).collect()
126 } else {
127 let cols: Vec<u32> =
128 if forward { (src.end.col + 1..=target.end.col).collect() } else { (target.start.col..src.start.col).rev().collect() };
129 cols.into_iter().map(|c| CellRef::new(lane, c)).collect()
130 };
131 let fmt = |c: &(CellRef, Option<Cell>)| cx.wb.styles.get(c.1.as_ref().map(|c| c.style).unwrap_or_default()).num_fmt.as_str();
132 let pattern = if mode.extends_series() || mode == FillMode::Alternate {
133 let first_fmt = src_cells.first().map(fmt).unwrap_or_default();
134 let dates = match mode {
135 FillMode::Dates(unit) => Pattern::dates(&src_cells, unit, cx.wb.date_system, first_fmt),
136 _ => None,
137 };
138 let detected = dates.unwrap_or_else(|| Pattern::detect(&src_cells, &custom, cx.wb.date_system, first_fmt));
139 match (mode, detected) {
140 (FillMode::Alternate, p) | (FillMode::Extend, p @ Pattern::Copy) => p.alternate(&src_cells),
141 (_, p) => p,
142 }
143 } else {
144 Pattern::Copy
145 };
146 let per_cell: Vec<Pattern> = if mode.extends_series() && matches!(pattern, Pattern::Copy) && src_cells.len() > 1 {
149 src_cells.iter().map(|c| Pattern::detect(std::slice::from_ref(c), &custom, cx.wb.date_system, fmt(c))).collect()
150 } else {
151 Vec::new()
152 };
153 for (k, t) in targets.iter().enumerate() {
154 let step = k as i64 + 1;
155 let idx = k % src_cells.len().max(1);
156 let src_i = if forward { idx } else { src_cells.len() - 1 - idx };
157 let (sc, scell) = src_cells.get(src_i).cloned().unwrap_or((src.start, None));
158 let mut cell = scell.clone().unwrap_or_default();
159 match mode {
160 FillMode::Formats => {
161 let mut existing = sh.cell(*t).cloned().unwrap_or_default();
162 existing.style = cell.style;
163 writes.push((*t, Some(existing)));
164 continue;
165 }
166 FillMode::ValuesOnly | FillMode::Series | FillMode::Alternate | FillMode::Copy | FillMode::Extend | FillMode::Dates(_) => {
167 let style = if mode == FillMode::ValuesOnly { sh.cell(*t).map(|c| c.style).unwrap_or_default() } else { cell.style };
168 if let Some(f) = &cell.formula {
169 if let Some(e) = f.expr() {
170 let shifted = crate::formula::adjust::shift_relative(e, t.row as i64 - sc.row as i64, t.col as i64 - sc.col as i64);
171 cell.formula = Some(Arc::new(Formula::from_expr(shifted)));
172 cell.value = Value::Empty;
173 }
174 } else if cell.py.is_none()
175 && let Some(v) = match per_cell.get(src_i) {
176 Some(p) => p.value(k as i64 / src_cells.len() as i64 + 1, forward, 1),
177 None => pattern.value(step, forward, src_cells.len()),
178 }
179 {
180 cell.value = v;
181 }
182 cell.style = style;
183 }
184 }
185 if let Some(picture) = sh.cell_pictures.get(&sc) {
186 pictures.push((*t, picture.clone()));
187 }
188 writes.push((*t, if cell.is_blank() { None } else { Some(cell) }));
189 }
190 }
191 let sh = cx.sheet_mut(sheet)?;
192 for (c, cell) in &writes {
193 match cell {
194 Some(x) if mode == FillMode::Formats => sh.cells.set(*c, x.clone()),
195 Some(x) => sh.set_cell(*c, x.clone()),
196 None => {
197 sh.remove_cell(*c);
198 }
199 }
200 }
201 sh.cell_pictures.extend(pictures);
202 for (c, _) in writes {
203 cx.touch(sheet, c);
204 }
205 Ok(())
206}
207
208#[derive(Clone, Debug)]
209enum Pattern {
210 Copy,
211 Linear {
213 last: f64,
214 first: f64,
215 step: f64,
216 },
217 TextNum {
219 prefix: String,
220 suffix: String,
221 last: i64,
222 first: i64,
223 step: i64,
224 width: usize,
225 },
226 List {
228 list: Vec<String>,
229 last: usize,
230 first: usize,
231 step: i64,
232 upper: bool,
233 lower: bool,
234 },
235 Months {
237 last: f64,
238 first: f64,
239 months: i64,
240 sys: crate::base::DateSystem,
241 },
242 Weekdays {
244 last: f64,
245 first: f64,
246 step: i64,
247 sys: crate::base::DateSystem,
248 },
249}
250
251impl Pattern {
252 fn dates(cells: &[(CellRef, Option<Cell>)], unit: DateUnit, sys: crate::base::DateSystem, fmt: &str) -> Option<Pattern> {
255 if !is_date_format(fmt) {
256 return None;
257 }
258 let mut nums = Vec::with_capacity(cells.len());
259 for (_, cell) in cells {
260 let cell = cell.as_ref()?;
261 if cell.formula.is_some() || !cell.value.is_number() {
262 return None;
263 }
264 nums.push(cell.value.as_f64()?);
265 }
266 let first = *nums.first()?;
267 let last = *nums.last()?;
268 let second = nums.get(1).copied();
269 let at_least_one = |n: i64| if n == 0 { 1 } else { n };
272 Some(match unit {
273 DateUnit::Day => {
274 let days = second.map_or(1.0, |b| b.floor() - first.floor());
275 Pattern::Linear { last, first, step: if days == 0.0 { 1.0 } else { days } }
276 }
277 DateUnit::Weekday => {
278 let step = match second {
279 Some(b) => at_least_one(weekdays_between(sys, first, b)?),
280 None => 1,
281 };
282 Pattern::Weekdays { last, first, step, sys }
283 }
284 DateUnit::Month | DateUnit::Year => {
285 let months = match second {
286 Some(b) => {
287 let (a, b) = (datetime_from_serial(sys, first)?, datetime_from_serial(sys, b)?);
288 let years = b.year as i64 - a.year as i64;
289 if unit == DateUnit::Year { at_least_one(years) * 12 } else { at_least_one(years * 12 + b.month as i64 - a.month as i64) }
290 }
291 None if unit == DateUnit::Year => 12,
292 None => 1,
293 };
294 Pattern::Months { last, first, months, sys }
295 }
296 })
297 }
298
299 fn detect(cells: &[(CellRef, Option<Cell>)], custom: &[Vec<String>], sys: crate::base::DateSystem, fmt: &str) -> Pattern {
300 let vals: Vec<Value> = cells.iter().map(|(_, c)| c.as_ref().map(|c| c.value.clone()).unwrap_or_default()).collect();
301 if cells.iter().any(|(_, c)| c.as_ref().is_some_and(|c| c.formula.is_some() || c.py.is_some())) {
303 return Pattern::Copy;
304 }
305 if !vals.is_empty() && vals.iter().all(|v| v.is_number()) {
307 let nums: Vec<f64> = vals.iter().filter_map(Value::as_f64).collect();
308 let first = nums.first().copied().unwrap_or(0.0);
309 let last = nums.last().copied().unwrap_or(0.0);
310 let is_date = fmt.contains('d') || fmt.contains('y') || (fmt.contains('m') && !fmt.contains('h'));
311 if nums.len() == 1 {
312 return if is_date {
314 Pattern::Linear { last, first, step: 1.0 }
315 } else if NumberFormat::parse(fmt).kind() == FormatKind::Time {
316 Pattern::Linear { last, first, step: 1.0 / 24.0 }
317 } else {
318 Pattern::Copy
319 };
320 }
321 if is_date && nums.len() >= 2 {
323 let d0 = datetime_from_serial(sys, first);
324 let d1 = datetime_from_serial(sys, nums.get(1).copied().unwrap_or(first));
325 if let (Some(a), Some(b)) = (d0, d1)
326 && a.day == b.day
327 && (a.month != b.month || a.year != b.year)
328 {
329 let months = (b.year as i64 - a.year as i64) * 12 + b.month as i64 - a.month as i64;
330 return Pattern::Months { last, first, months, sys };
331 }
332 }
333 let n = nums.len() as f64;
335 let mean_x = (n - 1.0) / 2.0;
336 let mean_y = nums.iter().sum::<f64>() / n;
337 let mut num = 0.0;
338 let mut den = 0.0;
339 for (i, y) in nums.iter().enumerate() {
340 num += (i as f64 - mean_x) * (y - mean_y);
341 den += (i as f64 - mean_x).powi(2);
342 }
343 let step = if den == 0.0 { 0.0 } else { num / den };
344 let fitted_last = mean_y + step * (n - 1.0 - mean_x);
345 let fitted_first = mean_y - step * mean_x;
346 return Pattern::Linear { last: fitted_last, first: fitted_first, step };
347 }
348 if !vals.is_empty() && vals.iter().all(|v| v.is_text()) {
350 let texts: Vec<String> = vals.iter().filter_map(|v| v.as_text().map(str::to_string)).collect();
351 let all_lists: Vec<Vec<String>> = custom.iter().cloned().chain(lists()).collect();
352 for list in &all_lists {
353 let idx: Vec<Option<usize>> = texts.iter().map(|t| list.iter().position(|l| l.eq_ignore_ascii_case(t))).collect();
354 if idx.iter().all(Option::is_some) {
355 let idx: Vec<usize> = idx.into_iter().flatten().collect();
356 let first = idx.first().copied().unwrap_or(0);
357 let last = idx.last().copied().unwrap_or(0);
358 let step = if idx.len() >= 2 { idx[1] as i64 - idx[0] as i64 } else { 1 };
359 let t0 = texts.first().cloned().unwrap_or_default();
360 let upper = t0.chars().all(|c| !c.is_lowercase()) && t0.chars().any(char::is_alphabetic) && t0.len() > 1;
361 let lower = t0.chars().all(|c| !c.is_uppercase());
362 return Pattern::List { list: list.clone(), last, first, step, upper, lower };
363 }
364 }
365 let split: Vec<Option<(String, i64, String, usize)>> = texts.iter().map(|t| split_num(t)).collect();
367 if split.iter().all(Option::is_some) {
368 let parts: Vec<(String, i64, String, usize)> = split.into_iter().flatten().collect();
369 if parts.iter().all(|p| p.0 == parts[0].0 && p.2 == parts[0].2) {
370 let first = parts.first().map(|p| p.1).unwrap_or(0);
371 let last = parts.last().map(|p| p.1).unwrap_or(0);
372 let step = if parts.len() >= 2 { parts[1].1 - parts[0].1 } else { 1 };
373 return Pattern::TextNum { prefix: parts[0].0.clone(), suffix: parts[0].2.clone(), last, first, step, width: parts[0].3 };
374 }
375 }
376 }
377 Pattern::Copy
378 }
379
380 fn alternate(self, cells: &[(CellRef, Option<Cell>)]) -> Pattern {
384 match (self, cells) {
385 (Pattern::Copy, [(_, Some(cell))]) if cell.formula.is_none() => match cell.value.as_f64() {
386 Some(n) if cell.value.is_number() => Pattern::Linear { last: n, first: n, step: 1.0 },
387 _ => Pattern::Copy,
388 },
389 _ => Pattern::Copy,
390 }
391 }
392
393 fn value(&self, k: i64, forward: bool, _len: usize) -> Option<Value> {
395 let sign = if forward { 1 } else { -1 };
396 match self {
397 Pattern::Copy => None,
398 Pattern::Linear { last, first, step } => {
399 let base = if forward { *last } else { *first };
400 Some(Value::number(round15(base + step * (k * sign) as f64)))
401 }
402 Pattern::TextNum { prefix, suffix, last, first, step, width } => {
403 let base = if forward { *last } else { *first };
404 let n = step.checked_mul(k)?.checked_mul(sign)?.checked_add(base)?;
408 let quarter = suffix.is_empty()
410 && (1..=4).contains(&base)
411 && ["q", "qtr", "quarter"].contains(&prefix.trim_end().to_ascii_lowercase().as_str());
412 let n = if quarter { n.checked_sub(1)?.rem_euclid(4) + 1 } else { n.checked_abs()? };
413 Some(Value::text(format!("{prefix}{n:0width$}{}", ordinal_suffix(n, suffix), width = *width)))
414 }
415 Pattern::List { list, last, first, step, upper, lower } => {
416 let len = list.len() as i64;
417 if len == 0 {
418 return None;
419 }
420 let base = if forward { *last } else { *first } as i64;
421 let i = (base + step * k * sign).rem_euclid(len) as usize;
422 let s = list.get(i)?.clone();
423 Some(Value::text(if *upper {
424 s.to_uppercase()
425 } else if *lower && !s.chars().next().is_some_and(char::is_uppercase) {
426 s.to_lowercase()
427 } else {
428 s
429 }))
430 }
431 Pattern::Months { last, first, months, sys } => {
432 let base = if forward { *last } else { *first };
433 let s = add_months_clamped(*sys, base, months.checked_mul(k * sign)?)?;
434 Some(Value::number(s))
435 }
436 Pattern::Weekdays { last, first, step, sys } => {
437 let base = if forward { *last } else { *first };
438 Some(Value::number(add_weekdays(*sys, base, step.checked_mul(k)?.checked_mul(sign)?)?))
439 }
440 }
441 }
442}
443
444fn is_weekday(sys: crate::base::DateSystem, serial: f64) -> Option<bool> {
446 datetime_from_serial(sys, serial).map(|d| d.weekday != 0 && d.weekday != 6)
447}
448
449fn add_weekdays(sys: crate::base::DateSystem, serial: f64, n: i64) -> Option<f64> {
452 if n == 0 {
453 return Some(serial);
454 }
455 let dir = n.signum() as f64;
456 let mut d = serial;
457 let next_weekday = |d: &mut f64| -> Option<()> {
458 loop {
459 *d += dir;
460 if is_weekday(sys, *d)? {
461 return Some(());
462 }
463 }
464 };
465 next_weekday(&mut d)?;
466 let rest = n.unsigned_abs() - 1;
468 d += dir * (rest / 5 * 7) as f64;
469 for _ in 0..rest % 5 {
470 next_weekday(&mut d)?;
471 }
472 datetime_from_serial(sys, d)?;
473 Some(d)
474}
475
476fn weekdays_between(sys: crate::base::DateSystem, a: f64, b: f64) -> Option<i64> {
479 let (lo, hi, sign) = if b >= a { (a.floor(), b.floor(), 1) } else { (b.floor(), a.floor(), -1) };
480 let days = (hi - lo) as i64;
481 let mut count = days / 7 * 5;
482 let mut d = lo + (days / 7 * 7) as f64;
483 for _ in 0..days % 7 {
484 d += 1.0;
485 if is_weekday(sys, d)? {
486 count += 1;
487 }
488 }
489 Some(count * sign)
490}
491
492fn add_months_clamped(sys: crate::base::DateSystem, serial: f64, months: i64) -> Option<f64> {
496 let d = datetime_from_serial(sys, serial)?;
497 let total = (d.year as i64).checked_mul(12)?.checked_add(d.month as i64 - 1)?.checked_add(months)?;
498 let year = total.div_euclid(12);
499 let month = total.rem_euclid(12) + 1;
500 let last = days_in_month(i32::try_from(year).ok()?, month as u32);
501 serial_from_ymd(sys, year, month, (d.day as i64).min(last as i64)).map(|day| day + serial - serial.floor())
502}
503
504fn round15(x: f64) -> f64 {
505 if x == 0.0 || !x.is_finite() {
506 return x;
507 }
508 let mag = x.abs().log10().floor() as i32;
509 let p = 10f64.powi(14 - mag);
510 if !p.is_finite() || p == 0.0 { x } else { (x * p).round() / p }
511}
512
513fn ordinal_suffix(n: i64, suffix: &str) -> String {
515 let (Some(end), Some(rest)) = (suffix.get(..2), suffix.get(2..)) else { return suffix.to_string() };
516 if !["st", "nd", "rd", "th"].contains(&end.to_ascii_lowercase().as_str()) || rest.starts_with(char::is_alphanumeric) {
517 return suffix.to_string();
518 }
519 let new = match (n % 10, n % 100) {
520 (_, 11..=13) => "th",
521 (1, _) => "st",
522 (2, _) => "nd",
523 (3, _) => "rd",
524 _ => "th",
525 };
526 let new = if end.chars().all(|c| c.is_ascii_uppercase()) { new.to_ascii_uppercase() } else { new.to_string() };
527 format!("{new}{rest}")
528}
529
530fn split_num(t: &str) -> Option<(String, i64, String, usize)> {
532 let chars: Vec<char> = t.chars().collect();
533 let end = chars.iter().rposition(char::is_ascii_digit)?;
534 let mut start = end;
535 while start > 0 && chars.get(start - 1).is_some_and(char::is_ascii_digit) {
536 start -= 1;
537 }
538 let digits: String = chars.get(start..=end)?.iter().collect();
539 let n: i64 = digits.parse().ok()?;
540 let prefix: String = chars.get(..start)?.iter().collect();
541 let suffix: String = chars.get(end + 1..)?.iter().collect();
542 let width = if digits.starts_with('0') && digits.len() > 1 { digits.len() } else { 1 };
543 Some((prefix, n, suffix, width))
544}
545
546pub struct SeriesOptions<'a> {
548 pub range: RangeRef,
549 pub rows: bool,
550 pub kind: &'a str,
551 pub step: f64,
552 pub stop: Option<f64>,
553 pub unit: &'a str,
554}
555
556pub fn series(cx: &mut Ctx, sheet: usize, options: SeriesOptions<'_>) -> Result<()> {
558 let SeriesOptions { range: r, rows, kind, step, stop, unit } = options;
559 let sys = cx.wb.date_system;
560 let Some(sh) = cx.wb.sheet(sheet) else { return Ok(()) };
561 let lanes: Vec<u32> = if rows { (r.start.row..=r.end.row).collect() } else { (r.start.col..=r.end.col).collect() };
562 let len = if rows { r.width() } else { r.height() };
563 let mut writes = Vec::new();
564 for lane in lanes {
565 let first_cell = if rows { CellRef::new(lane, r.start.col) } else { CellRef::new(r.start.row, lane) };
566 let Some(start) = sh.value(first_cell).as_f64() else { continue };
567 let style = sh.style_id(first_cell);
568 let mut v = start;
569 for i in 1..len {
570 let prev = v;
571 v = match kind {
572 "growth" => v * step,
573 "date" => {
574 let d = datetime_from_serial(sys, v);
575 match (unit, d) {
576 ("month", Some(_)) => (step as i64).checked_mul(i as i64).and_then(|m| add_months_clamped(sys, start, m)).unwrap_or(v),
577 ("year", Some(_)) => (step as i64).checked_mul(i as i64 * 12).and_then(|m| add_months_clamped(sys, start, m)).unwrap_or(v),
578 ("weekday", Some(_)) => add_weekdays(sys, v, step as i64)
580 .ok_or_else(|| crate::engine::EngineError::Other("The weekday series runs past the supported date range.".into()))?,
581 _ => v + step,
582 }
583 }
584 _ => v + step,
585 };
586 if let Some(s) = stop
588 && ((v >= prev && round15(v) > s) || (v < prev && round15(v) < s))
589 {
590 break;
591 }
592 let c = if rows { CellRef::new(lane, r.start.col + i) } else { CellRef::new(r.start.row + i, lane) };
593 writes.push((c, Cell { value: Value::number(round15(v)), formula: None, style, py: None }));
594 }
595 }
596 let sh = cx.sheet_mut(sheet)?;
597 for (c, cell) in &writes {
598 sh.set_cell(*c, cell.clone());
599 }
600 for (c, _) in writes {
601 cx.touch(sheet, c);
602 }
603 Ok(())
604}
605
606pub fn flash_fill(cx: &mut Ctx, sheet: usize, at: CellRef) -> Result<usize> {
611 let Some(sh) = cx.wb.sheet(sheet) else { return Ok(0) };
612 if at.col == 0 {
613 return Ok(0);
614 }
615 let src_col = at.col - 1;
616 let mut top = at.row;
618 while top > 0 && !sh.value(CellRef::new(top - 1, src_col)).is_empty() {
619 top -= 1;
620 }
621 let mut bottom = at.row;
622 while bottom + 1 < crate::base::MAX_ROWS && !sh.value(CellRef::new(bottom + 1, src_col)).is_empty() && bottom - top < 1_000_000 {
623 bottom += 1;
624 }
625 let mut examples = Vec::new();
627 let mut todo = Vec::new();
628 for r in top..=bottom {
630 let src = crate::engine::display::cell_text(&cx.wb, sh, CellRef::new(r, src_col));
631 if sh.value(CellRef::new(r, at.col)).is_empty() {
632 todo.push((r, src));
633 } else {
634 examples.push((src, crate::engine::display::cell_text(&cx.wb, sh, CellRef::new(r, at.col))));
635 }
636 }
637 if examples.is_empty() {
638 return Ok(0);
639 }
640 let Some(program) = learn(&examples) else { return Ok(0) };
641 let mut n = 0;
642 let style = sh.style_id(CellRef::new(top, at.col));
643 let mut writes = Vec::new();
644 for (r, src) in todo {
645 if let Some(out) = program.apply(&src) {
646 writes.push((CellRef::new(r, at.col), out));
647 n += 1;
648 }
649 }
650 let sh = cx.sheet_mut(sheet)?;
651 for (c, out) in &writes {
652 sh.set_cell(*c, Cell { value: Value::text(out.as_str()), formula: None, style, py: None });
653 }
654 for (c, _) in writes {
655 cx.touch(sheet, c);
656 }
657 Ok(n)
658}
659
660#[derive(Clone, Debug, PartialEq)]
661enum Piece {
662 Lit(String),
663 Tok(i32, Case),
665 Initial(i32, Case),
667}
668
669#[derive(Clone, Copy, Debug, PartialEq)]
670enum Case {
671 Same,
672 Upper,
673 Lower,
674 Proper,
675}
676
677#[derive(Clone, Debug)]
678struct Program(Vec<Piece>);
679
680fn tokens(s: &str) -> Vec<String> {
681 s.split(|c: char| !c.is_alphanumeric() && c != '\'').filter(|t| !t.is_empty()).map(str::to_string).collect()
682}
683
684fn apply_case(s: &str, c: Case) -> String {
685 match c {
686 Case::Same => s.to_string(),
687 Case::Upper => s.to_uppercase(),
688 Case::Lower => s.to_lowercase(),
689 Case::Proper => {
690 let mut out = String::new();
691 for (i, ch) in s.chars().enumerate() {
692 if i == 0 {
693 out.extend(ch.to_uppercase());
694 } else {
695 out.extend(ch.to_lowercase());
696 }
697 }
698 out
699 }
700 }
701}
702
703impl Program {
704 fn apply(&self, src: &str) -> Option<String> {
705 let toks = tokens(src);
706 let get = |i: i32| -> Option<&String> {
707 if i >= 0 { toks.get(i as usize) } else { toks.len().checked_sub(i.unsigned_abs() as usize).and_then(|j| toks.get(j)) }
708 };
709 let mut out = String::new();
710 for p in &self.0 {
711 match p {
712 Piece::Lit(l) => out.push_str(l),
713 Piece::Tok(i, c) => out.push_str(&apply_case(get(*i)?, *c)),
714 Piece::Initial(i, c) => out.push_str(&apply_case(&get(*i)?.chars().next()?.to_string(), *c)),
715 }
716 }
717 Some(out)
718 }
719}
720
721fn learn(examples: &[(String, String)]) -> Option<Program> {
723 let (src, dst) = examples.first()?;
724 let candidates = synthesize(src, dst);
725 candidates.into_iter().find(|p| examples.iter().all(|(s, d)| p.apply(s).as_deref() == Some(d.as_str())))
726}
727
728fn synthesize(src: &str, dst: &str) -> Vec<Program> {
731 let toks = tokens(src);
732 let n = toks.len() as i32;
733 let mut out = Vec::new();
734 for prefer_neg in [false, true] {
735 let mut pieces = Vec::new();
736 let mut rest = dst;
737 let mut guard = 0;
738 while !rest.is_empty() && guard < 64 {
739 guard += 1;
740 let mut best: Option<(usize, Piece)> = None;
742 for (i, t) in toks.iter().enumerate() {
743 let idx = if prefer_neg { i as i32 - n } else { i as i32 };
744 for c in [Case::Same, Case::Upper, Case::Lower, Case::Proper] {
745 let cand = apply_case(t, c);
746 if !cand.is_empty() && rest.starts_with(&cand) && best.as_ref().is_none_or(|(l, _)| cand.len() > *l) {
747 best = Some((cand.len(), Piece::Tok(idx, c)));
748 }
749 }
750 }
751 if best.as_ref().is_none_or(|(l, _)| *l <= 1) {
752 for (i, t) in toks.iter().enumerate() {
754 let idx = if prefer_neg { i as i32 - n } else { i as i32 };
755 if let Some(ch) = t.chars().next() {
756 for c in [Case::Same, Case::Upper, Case::Lower] {
757 let cand = apply_case(&ch.to_string(), c);
758 if rest.starts_with(&cand) && best.is_none() {
759 best = Some((cand.len(), Piece::Initial(idx, c)));
760 }
761 }
762 }
763 }
764 }
765 match best {
766 Some((len, piece)) => {
767 pieces.push(piece);
768 rest = rest.get(len..).unwrap_or("");
769 }
770 None => {
771 let ch = rest.chars().next().unwrap_or(' ');
772 match pieces.last_mut() {
773 Some(Piece::Lit(l)) => l.push(ch),
774 _ => pieces.push(Piece::Lit(ch.to_string())),
775 }
776 rest = rest.get(ch.len_utf8()..).unwrap_or("");
777 }
778 }
779 }
780 out.push(Program(pieces));
781 }
782 out
783}
784
785#[cfg(test)]
786mod tests {
787 use super::*;
788
789 #[test]
790 fn flash_fill_reads_shown_text() {
791 let mut s = crate::engine::Session::new();
792 s.new_workbook();
793 let values = serde_json::json!([["2024-01-15", "15/01/2024"], ["2023-12-03", null]]);
794 s.execute("range.setValues", serde_json::json!({"range": "A1", "values": values})).unwrap();
795 s.execute("selection.set", serde_json::json!({"cell": "B2"})).unwrap();
796 s.execute("data.flashFill", serde_json::json!({})).unwrap();
797 let sh = s.doc().unwrap().wb.active().unwrap();
798 assert_eq!(sh.value(CellRef::new(1, 1)), Value::from("03/12/2023"));
799 }
800
801 #[test]
802 fn series_keeps_value_equal_to_stop_after_float_drift() {
803 let mut s = crate::engine::Session::new();
804 s.new_workbook();
805 s.execute("cell.set", serde_json::json!({"cell": "A1", "input": "1"})).unwrap();
806 s.execute("edit.fillSeries", serde_json::json!({"range": "A1:A6", "type": "linear", "step": 0.1, "stop": 1.3})).unwrap();
807 s.execute("cell.set", serde_json::json!({"cell": "B1", "input": "0.1"})).unwrap();
808 s.execute("edit.fillSeries", serde_json::json!({"range": "B1:B4", "type": "growth", "step": 3, "stop": 0.3})).unwrap();
809 let sh = s.doc().unwrap().wb.active().unwrap();
810 let col = |c: u32, n: u32| (0..n).map(|r| sh.value(CellRef::new(r, c))).collect::<Vec<_>>();
811 let n = Value::number;
812 assert_eq!(col(0, 6), vec![n(1.0), n(1.1), n(1.2), n(1.3), Value::Empty, Value::Empty]);
813 assert_eq!(col(1, 4), vec![n(0.1), n(0.3), Value::Empty, Value::Empty]);
814 }
815
816 #[test]
817 fn flash_programs() {
818 let p = learn(&[("John Smith".into(), "Smith, John".into())]).unwrap();
819 assert_eq!(p.apply("Ada Lovelace").as_deref(), Some("Lovelace, Ada"));
820 let p = learn(&[("jane doe".into(), "JD".into())]).unwrap();
821 assert_eq!(p.apply("alan turing").as_deref(), Some("AT"));
822 let p = learn(&[("alice@example.com".into(), "alice".into())]).unwrap();
823 assert_eq!(p.apply("bob@example.com").as_deref(), Some("bob"));
824 }
825
826 #[test]
827 fn split_numbers() {
828 assert_eq!(split_num("Item 7"), Some(("Item ".into(), 7, "".into(), 1)));
829 assert_eq!(split_num("Q003x"), Some(("Q".into(), 3, "x".into(), 3)));
830 assert_eq!(split_num("abc"), None);
831 }
832
833 #[test]
834 fn times_quarters_ordinals_and_mixed_sources() {
835 let mut s = crate::engine::Session::new();
836 s.new_workbook();
837 let mut fill = |values: serde_json::Value, source: &str, target: &str| {
838 s.execute("range.setValues", serde_json::json!({"range": source.get(..2).unwrap(), "values": values})).unwrap();
839 s.execute("edit.autoFill", serde_json::json!({"source": source, "target": target})).unwrap();
840 let sh = s.doc().unwrap().wb.active().unwrap();
841 let r = RangeRef::parse(target).unwrap();
842 r.iter().map(|c| crate::engine::display::cell_text(&s.doc().unwrap().wb, sh, c)).collect::<Vec<_>>()
843 };
844 assert_eq!(fill(serde_json::json!([["9:00"]]), "A1", "A1:A3"), ["9:00", "10:00", "11:00"]);
845 assert_eq!(fill(serde_json::json!([["Qtr3"]]), "B1", "B1:B3"), ["Qtr3", "Qtr4", "Qtr1"]);
846 assert_eq!(fill(serde_json::json!([["Quarter 4"]]), "C1", "C1:C2"), ["Quarter 4", "Quarter 1"]);
847 assert_eq!(fill(serde_json::json!([["1st Period"]]), "D1", "D1:D4"), ["1st Period", "2nd Period", "3rd Period", "4th Period"]);
848 assert_eq!(
849 fill(serde_json::json!([["Product 1"], ["On backorder"]]), "E1:E2", "E1:E6"),
850 ["Product 1", "On backorder", "Product 2", "On backorder", "Product 3", "On backorder"]
851 );
852 }
853
854 #[test]
855 fn falling_growth_series_stops_at_its_stop_value() {
856 let mut s = crate::engine::Session::new();
857 s.new_workbook();
858 s.execute("cell.set", serde_json::json!({"cell": "A1", "input": "5"})).unwrap();
859 s.execute("edit.fillSeries", serde_json::json!({"range": "A1:A6", "type": "growth", "step": 0.5, "stop": 1})).unwrap();
860 let d = s.doc().unwrap();
861 let sh = d.wb.active().unwrap();
862 let growth: Vec<String> = (1..6).map(|r| crate::engine::display::cell_text(&d.wb, sh, CellRef::new(r, 0))).collect();
863 assert_eq!(growth, ["2.5", "1.25", "", "", ""]);
864 }
865
866 #[test]
867 fn text_series_near_i64_max_neither_wraps_nor_panics() {
868 let mut s = crate::engine::Session::new();
869 s.new_workbook();
870 s.execute("cell.set", serde_json::json!({"cell": "A1", "input": "X 0"})).unwrap();
871 s.execute("cell.set", serde_json::json!({"cell": "A2", "input": "X 9223372036854775807"})).unwrap();
872 s.execute("edit.autoFill", serde_json::json!({"source": "A1:A2", "target": "A1:A3"})).unwrap();
875 let d = s.doc().unwrap();
876 let sh = d.wb.active().unwrap();
877 assert_eq!(crate::engine::display::cell_text(&d.wb, sh, CellRef::new(2, 0)), "X 0");
878 }
879
880 #[test]
881 fn autofill_refuses_targets_that_do_not_extend_the_source() {
882 let mut s = crate::engine::Session::new();
886 s.new_workbook();
887 s.execute("range.setValues", serde_json::json!({"range": "A2", "values": [[1, "Item 1"], [2, "Item 2"]]})).unwrap();
888 s.execute("range.setValues", serde_json::json!({"range": "H4", "values": [[10], [20]]})).unwrap();
889 let before = s.doc().unwrap().wb.active().unwrap().cells.len();
890 for (mode, source, target) in [
891 ("series", "2:3", "A:A"),
892 ("alternate", "2:3", "A:A"),
893 ("series", "8:10", "C:E"),
894 ("copy", "24:24", "B:D"),
895 ("values", "H4:H9", "D:F"),
896 ("series", "H4:H9", "H:H"),
897 ("series", "A2:A3", "A1:A5"),
898 ("series", "A2:A6", "A2:A4"),
899 ("series", "B2:B3", "A2:A3"),
900 ] {
901 let r = s.execute("edit.autoFill", serde_json::json!({"mode": mode, "source": source, "target": target}));
902 assert!(r.is_err(), "{source} -> {target} should be refused");
903 }
904 assert_eq!(s.doc().unwrap().wb.active().unwrap().cells.len(), before, "a refused fill writes nothing");
905 s.execute("edit.autoFill", serde_json::json!({"source": "2:3", "target": "2:6"})).unwrap();
907 s.execute("edit.autoFill", serde_json::json!({"source": "A2:A3", "target": "A1:A3"})).unwrap();
908 let sh = s.doc().unwrap().wb.active().unwrap();
909 let text = |a: &str| crate::engine::display::cell_text(&s.doc().unwrap().wb, sh, CellRef::parse(a).unwrap());
910 assert_eq!([text("A1"), text("A6"), text("B6")], ["0", "5", "Item 5"]);
911 assert!(holds_source_in_corner(RangeRef::parse("B5").unwrap(), RangeRef::parse("B3:B5").unwrap()));
912 assert!(holds_source_in_corner(RangeRef::parse("A:A").unwrap(), RangeRef::parse("A:C").unwrap()));
913 assert!(holds_source_in_corner(RangeRef::parse("B5:C9").unwrap(), RangeRef::parse("B5:D13").unwrap()));
914 assert!(holds_source_in_corner(RangeRef::parse("B1:B2").unwrap(), RangeRef::parse("A1:B4").unwrap()));
915 assert!(!holds_source_in_corner(RangeRef::parse("A:A").unwrap(), RangeRef::parse("1:1").unwrap()));
916 }
917}