Table keys and cell commands: footnote text isn't a table cell

The caret in a footnote has a CellAddr too (FOOTNOTE_TABLE). Shift+Tab
and Esc there now act as outside a table, and table.nextCell,
table.prevCell and table.toggleSelection refuse it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
moppeeerandClaude Opus 5.5 committed 2026-10-25 17:13:33 +01:00
1 parent 0014243ac2
commit 2e44b19096
4 files changed
+28 -7

No files matched your search

+4 -2
View File
@@ -438,7 +438,9 @@ fn in_table_or_ids(s: &Session) -> std::result::Result<(), String> {
fn in_cell(s: &Session) -> std::result::Result<(), String> {
has_doc(s)?;
if s.active().is_some_and(|d| d.selection.text.is_some_and(|t| t.cell.is_some())) { Ok(()) } else { Err("not in a table cell".into()) }
// Footnote text is addressed as a cell too, but isn't in a table.
let in_table = |c: CellAddr| c.footnote_id().is_none();
if s.active().is_some_and(|d| d.selection.text.is_some_and(|t| t.cell.is_some_and(in_table))) { Ok(()) } else { Err("not in a table cell".into()) }
}
/// The table a command acts on and its target cell range.
@@ -1594,7 +1596,7 @@ fn toggle_selection(s: &mut Session) -> Result<Value> {
const ID: &str = "table.toggleSelection";
let st = s.doc_mut()?;
let t = st.selection.text.ok_or_else(|| bad(ID, "no caret"))?;
let c = t.cell.ok_or_else(|| bad(ID, "not in a cell"))?;
let c = t.cell.filter(|c| c.footnote_id().is_none()).ok_or_else(|| bad(ID, "not in a cell"))?;
let cells = match st.selection.cells {
Some(cs) => {
// A merged cell's text belongs to the cell at its top left.
+15
View File
@@ -127,6 +127,21 @@ fn table_edit_after_anchor_removal_floors_parent_caret() {
}
}
/// Footnote text is addressed as a cell, but the table commands don't take it for one.
#[test]
fn the_cell_commands_refuse_footnote_text() {
let (mut s, sid, _) = session_with_frame();
s.execute("story.setText", &json!({"story": sid, "text": "Hello"})).unwrap();
s.execute("text.select", &json!({"story": sid, "anchor": 5, "focus": 5})).unwrap();
s.execute("footnote.insert", &json!({"text": "Note"})).unwrap();
let before = s.doc().unwrap().selection.clone();
assert!(before.text.and_then(|t| t.cell).and_then(|c| c.footnote_id()).is_some(), "the caret is in the footnote");
for cmd in ["table.toggleSelection", "table.prevCell", "table.nextCell"] {
assert!(s.execute(cmd, &json!({})).is_err(), "{cmd}");
}
assert_eq!(s.doc().unwrap().selection, before);
}
/// Esc in a table toggles between the text and the cell (InDesign's Esc): the caret in a cell
/// selects that cell, and selected cells select the first one's text; Shift+Tab goes back a cell.
#[test]
+7 -4
View File
@@ -1469,10 +1469,13 @@ fn type_tool_keys_in_a_table_cell() {
assert_eq!(t.key(&cx, ToolKey::Tab, shift), exec("table.prevCell", serde_json::json!({})));
assert_eq!(t.key(&cx, ToolKey::Tab, Mods::default()), exec("text.insert", serde_json::json!({"text": "\t"})));
assert_eq!(t.key(&cx, ToolKey::Escape, Mods::default()), exec("table.toggleSelection", serde_json::json!({})));
let s = Selection::text(designcraft_doc::TextSel::caret(sid, 0));
let cx = ctx(&d, &s, &c, &l);
assert_eq!(t.key(&cx, ToolKey::Tab, shift), exec("text.insert", serde_json::json!({"text": "\t"})));
assert!(matches!(t.key(&cx, ToolKey::Escape, Mods::default()).first(), Some(Action::SwitchTool(_))));
// Out of a table, and in footnote text (addressed as a cell too).
for cell in [None, Some(designcraft_doc::CellAddr::footnote(1))] {
let s = Selection::text(designcraft_doc::TextSel { cell, ..designcraft_doc::TextSel::caret(sid, 0) });
let cx = ctx(&d, &s, &c, &l);
assert_eq!(t.key(&cx, ToolKey::Tab, shift), exec("text.insert", serde_json::json!({"text": "\t"})), "{cell:?}");
assert!(matches!(t.key(&cx, ToolKey::Escape, Mods::default()).first(), Some(Action::SwitchTool(_))), "{cell:?}");
}
}
#[test]
+2 -1
View File
@@ -364,7 +364,8 @@ impl Tool for TypeTool {
let steps = if mods.cmd || mods.ctrl { 5.0 } else { 1.0 };
vec![Action::Exec("type.kernStep".into(), json!({"steps": sign * steps}))]
};
let in_cell = cx.selection.text.is_some_and(|t| t.cell.is_some());
// A table cell's text, not a footnote's (which is addressed as a cell too).
let in_cell = cx.selection.text.is_some_and(|t| t.cell.is_some_and(|c| c.footnote_id().is_none()));
match key {
ToolKey::Left if mods.alt && !mods.shift => kern(-1.0),
ToolKey::Right if mods.alt && !mods.shift => kern(1.0),