diff --git a/apps/ely-table/src/page.rs b/apps/ely-table/src/page.rs index 92f7963..830e56b 100644 --- a/apps/ely-table/src/page.rs +++ b/apps/ely-table/src/page.rs @@ -82,6 +82,15 @@ fn cell_ok(cell: &str) -> bool { && row_number(digits).is_some() } +/// Whole columns or rows, like `$A:$D`; one `$` each. +fn whole(from: &str, to: &str, number: fn(&str) -> Option) -> bool { + let end = |end: &str| { + let end = end.strip_prefix('$').unwrap_or(end); + end.bytes().all(|byte| byte.is_ascii_alphanumeric()).then(|| number(&end.to_ascii_uppercase()))? + }; + matches!((end(from), end(to)), (Some(first), Some(last)) if first <= last) +} + /// Areas as the kernel takes them, or None. fn areas(text: &str) -> Option { let tokens: Vec<&str> = text @@ -90,7 +99,9 @@ fn areas(text: &str) -> Option { .filter(|token| !token.is_empty()) .collect(); let ok = |token: &&str| match token.split_once(':') { - Some((from, to)) => cell_ok(from) && cell_ok(to), + Some((from, to)) => { + cell_ok(from) && cell_ok(to) || whole(from, to, column_number) || whole(from, to, row_number) + } None => cell_ok(token), }; tokens.iter().all(ok).then(|| tokens.join(",")) @@ -228,6 +239,11 @@ mod tests { assert_eq!(areas("A1:B"), None); assert_eq!(areas("XFE1"), None); assert_eq!(areas("S!A1"), None); + assert_eq!(areas("$A:$D, 1:3").as_deref(), Some("$A:$D,1:3")); + assert_eq!(areas("A:3"), None); + for bad in ["$$A:$$D", "$$1:$$3", "$A:$$D", "A:D,$$E:$$F", "+1:2", "D:A"] { + assert_eq!(areas(bad), None, "{bad}"); + } assert_eq!(span("$1:$3", row_number), Some(Some((1, 3)))); assert_eq!(span("2", row_number), Some(Some((2, 2)))); assert_eq!(span("3:1", row_number), None); diff --git a/apps/ely-table/src/tests/output.rs b/apps/ely-table/src/tests/output.rs index 6df3abc..8ae2d00 100644 --- a/apps/ely-table/src/tests/output.rs +++ b/apps/ely-table/src/tests/output.rs @@ -436,3 +436,36 @@ fn closing_a_tab_takes_its_dialog_and_frees_the_import() { update(&mut cx, &book, |book, cx| book.ask_import(path.clone(), cx)); asked(&cx, &book, |dialog| matches!(dialog, Dialog::Import(_))); } + +#[test] +fn a_whole_column_print_area_still_applies() { + let dir = tempfile::tempdir().expect("a temp dir"); + let mut cx = TestAppContext::single(); + let Some((_window, book)) = book(&mut cx, dir.path()) else { + eprintln!("SKIP: no LOK build"); + return; + }; + blank(&mut cx, &book); + update(&mut cx, &book, |book, cx| book.ask_page(cx)); + asked(&cx, &book, |dialog| matches!(dialog, Dialog::Page { .. })); + type_in(&mut cx, &book, area, "A:D"); + update(&mut cx, &book, |book, cx| book.page_asked(cx)); + let layout = read(&mut cx, &book, sheet::print_layout, sheet::LAYOUT); + assert_eq!(layout.areas.len(), 1, "set: {:?}", layout.areas); + // The kernel's own text comes back into the dialog. + update(&mut cx, &book, |book, cx| book.ask_page(cx)); + asked(&cx, &book, |dialog| matches!(dialog, Dialog::Page { .. })); + let shown = cx.update(|app| match book.read(app).dialog.as_ref() { + Some(dialog) => area(dialog).read(app).text().to_string(), + None => unreachable!("a page dialog"), + }); + assert_eq!(shown, layout.areas[0]); + update(&mut cx, &book, |book, cx| { + if let Some(Dialog::Page { landscape, .. }) = &mut book.dialog { + *landscape = true; + } + book.page_asked(cx) + }); + let layout = read(&mut cx, &book, sheet::print_layout, sheet::LAYOUT); + assert!(layout.landscape, "applied with {shown}"); +} diff --git a/tasks/BUGS.md b/tasks/BUGS.md index e3f335e..4ba0bd5 100644 --- a/tasks/BUGS.md +++ b/tasks/BUGS.md @@ -19,7 +19,7 @@ - [x] T-09 导入文本文件时整文件的 UTF-8 校验在界面线程跑 — 编码与分隔符的猜测随读文件在后台;R1 PASS - [x] T-10 CSV 导入对话框开着时打开任何别的对话框,待导入的文件静默消失 — 被挤开的导入回到队头,已读完的保留列类型;R2 PASS - [x] T-11 对话框比其标签活得久:导出、定义名称、重命名不可见却“开着”,挡住导入;名称表的“转到”激活已关闭的标签 — 关标签时一并关掉其对话框与选项列表;R1 PASS -- [ ] T-12 打印区域是整列或整行时页面设置无法应用 +- [x] T-12 打印区域是整列或整行时页面设置无法应用 — 打印区域可为整列、整行(每端至多一个 $);R2 PASS - [ ] T-13 打印失败报成“导出失败”(非 macOS 必现) - [ ] T-14 导入扫描把整个文件的每个字段都建成字符串 - [ ] T-15 非 UTF-8 文本的默认编码跟系统区域而不是界面语言 diff --git a/tasks/reviews/T-12-r1.md b/tasks/reviews/T-12-r1.md new file mode 100644 index 0000000..67a8946 --- /dev/null +++ b/tasks/reviews/T-12-r1.md @@ -0,0 +1,3 @@ +FAIL + +apps/ely-table/src/page.rs:96 — 新增整列、整行分支复用 `span()`,其中 `trim_start_matches('$')` 会剥掉重复的 `$`,校验通过后却发送未经规范化的原字符串。具体场景:已有打印区域 `A1:B2`,在页面设置输入 `$$A:$$D`、`$$1:$$3` 或 `$A:$$D` 后应用;`areas()` 接受输入,实际内核的 `print.area` 返回 `Ok(())`,重读 `.uno:PrintLayout` 得到 `areas=[]`,原打印区域被静默清空,打印范围恢复为已用区域。输入 `A:D,$$E:$$F` 也会静默丢弃第二段。独立内核探针已复现;针对重复 `$` 的拒绝断言在改动前通过、当前版本失败,现有 47 个 ElyTable 测试仍全部通过。修复方向:整列、整行的端点仅接受至多一个前导 `$`,将非法输入保留在对话框并报引用错误,补充重复 `$` 的校验和原打印区域保持测试。证据:`/tmp/ely-t12-r1-probe.log`、`/tmp/ely-t12-r1-original-invalid.log`、`/tmp/ely-t12-r1-current-invalid.log`。 diff --git a/tasks/reviews/T-12-r2.md b/tasks/reviews/T-12-r2.md new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/tasks/reviews/T-12-r2.md @@ -0,0 +1 @@ +PASS