zudo-text

検索したい単語を入力

いつでも検索バーを開ける

Spreadsheet Helper

The Spreadsheet Helper turns a GFM table in your note into a small, Google-Sheets-like editing surface — drag-select, formulas, fill, sort, number formats, a formula bar, and a right-click context menu — while the saved file stays plain Markdown. Nothing binary or hidden is written; the sheet's data model is the table itself plus one trailing HTML comment.

What a sheet block is

A spreadsheet block is exactly one GFM pipe table immediately followed by a <!-- SHEET --> (or <!-- SHEET: {...} -->) HTML comment:

| Name        | Price | Qty |
| ----------- | ----- | --- |
| Widget Pro  | 29.99 | 150 |
| Comfort Mug | 12.50 | 320 |

<!-- SHEET: {"colWidths":[200,80,60],"merges":["A1:B1"]} -->
  • The GFM header row holds your real column headers (Name, Price, Qty, …) — it is ordinary table content, not spreadsheet chrome.

  • A sheet with no column widths, row heights, merges, or number formats serializes with a bare <!-- SHEET --> marker (no JSON payload).

  • The trailing comment carries only sizing/merge/format metadata (colWidths, rowHeights, merges, formats) — never cell values. Formula cells store their literal =-prefixed text; the computed result is never written to the file.

  • Markdown is the source of truth. The Spreadsheet Helper is an editing surface over this table; nothing about the format requires the dialog — you can hand-edit the table and marker directly in the editor.

A1 addresses body rows — not the header row

Diverges from Google Sheets

In the Spreadsheet Helper, A1 is the table's first BODY row, not the GFM header row. The header row (Name, Price, Qty in the example above) is real column-title content and is never addressable in formulas.

Column letters (A, B, C, …) and row numbers (1, 2, 3, …) shown in the board are UI-only gutters over the table body: A1 maps totable.rows[0][0] — the first row of DATA, one row below the header you see on screen. This is the opposite of Google Sheets, where row 1 is whatever is visually on top (often itself a header row you typed).

If you are used to Sheets, double-check formulas that reference "the row above the data" — in a zudo-text sheet there is no row above A1 to reference; A1 already is the topmost data row.

Opening the Spreadsheet Helper

Four ways to open the dialog, all routing through the same classifySpreadsheetCursor detection (cursor/click position → does this table carry a <!-- SHEET --> marker?):

  • Command palette — open the palette and search "Spreadsheet Helper". Hidden when the feature is disabled (see below) or on mobile viewports — the feature is desktop-only.

  • Keyboard shortcutCmd+Alt+S (default; rebindable in Settings → Shortcuts). Opens with the sheet under the cursor loaded, or an empty 3×2 grid when the cursor isn't inside a marked table.

  • Preview click-to-edit — clicking anywhere inside a rendered sheet table in the preview pane opens the dialog for that block, pre-selecting the clicked cell if you clicked a body cell.

  • Table Helper handoff — if you invoke the plain Table Helper on a table that already carries a <!-- SHEET --> marker, a confirm prompt ("Open in Spreadsheet Helper?") offers to route you to the Spreadsheet Helper instead, since it "owns" that block. Choosing Cancel falls through to the plain Table Helper, leaving the marker untouched.

Every entry point above is gated on Settings → General → "Enable Spreadsheet Helper" (useSpreadsheet, on by default). Turning it off hides the palette entry, disables the shortcut, and makes the preview render sheet tables as plain read-only tables (no click-to-edit affordance) — the Table Helper handoff prompt also stops appearing, so Table Helper edits any table, marked or not, directly.

Editing

  • Select a cell — click it, or use the arrow keys when a cell already has focus.

  • Begin editing — double-click, press Enter or F2, or just type a character (which replaces the cell's content, the standard spreadsheet convention).

  • Commit and moveEnter commits and moves down (Shift+Enter moves up); Tab commits and moves right (Shift+Tab moves left) — both stay inside the grid rather than blurring out of it.

  • CancelEscape while editing discards the in-progress edit.

  • Drag-select a rectangular range, or click a column/row gutter to select the whole column/row.

  • Undo / redoCmd+Z / Cmd+Shift+Z (also available as ghost icon buttons at the end of the number-format toolbar), disabled while focus is in a text input.

  • Row/column structure — grow the grid by clicking the faint dashed ghost rows/column along its bottom-right edge (they materialize into real cells), or use the right-click context menu's Insert/Delete entries on a row or column gutter (which report the exact row/column count affected).

  • Resize — drag a column's right edge or a row's bottom edge; or focus the resize handle (a keyboard-operable separator) and use Arrow keys to step the size, Home/End for the min/max bound, Enter to commit, Escape to cancel. Double-click a resize handle to reset that column/row back to auto (content-driven) sizing.

  • Reorder — drag a row/column gutter, or focus it and press Alt+Left/Right (columns) / Alt+Up/Down (rows) to move it one slot; Enter/Space on a focused gutter selects the whole row/column.

  • Merge / unmerge cells — from the right-click context menu: "Merge cells" appears for a multi-cell selection that doesn't already touch a merge; "Unmerge cells" appears when the selection contains one. Inserting, deleting, or moving rows/columns automatically re-normalizes existing merges (shifting or dropping them) in the same undo step as the structural edit.

  • Column alignment — "Align left / center / right" in the context menu, applied to every column in the current selection; the active alignment shows a checkmark.

  • Number formats — a toolbar row above the grid offers one-click presets (applied to every column touched by the current selection): Auto (clear), 1,234, 1,234.00, 12%, 12.0%, ¥1,235, $1,234.56. Number formats only change the DISPLAY string — they never change a cell's stored text or a formula's computed value. Percent multiplies the value by 100 for display (0.550%); currency prefixes a literal symbol with the negative sign outside it (-¥1,235, never locale currency formatting); there is no date format — dates always stay opaque text.

  • Sort — "Sort sheet A → Z" / "Z → A" in the context menu sorts the whole sheet's body rows by the column you right-clicked. It refuses (with a toast) rather than sort when any merged cell overlaps the sheet body, since there's no sane way to carry a merge's span through an arbitrary row reorder.

Point mode: click cells to build a formula

While editing a formula (in a cell or in the formula bar), clicking or dragging over other cells inserts their A1 reference (or range) into the formula at the caret instead of committing your edit — the same "point and click to build a reference" flow Sheets/Excel offer. It arms right after a token that can legally be followed by a reference (=, an operator, (, ,, or :), or when the caret sits inside/after an existing reference token (in which case the click re-points that reference).

Formula bar and name box

A Sheets-style bar sits between the header and the grid:

  • Name box (left) — shows the active cell/range's A1 name (B3, A1:C4, 3:3, B:B). Type a reference and press Enter to jump/select it; Escape or blur reverts an uncommitted edit.

  • Formula bar (right) — mirrors the active cell's raw text and IS an editor for it: typing here edits the cell exactly like the in-grid editor, including point mode and F4 anchor-cycling.

Formula reference

Absolute references ($A$1) and F4

A reference can lock its column, its row, both, or neither:

FormMeaning
A1Fully relative — both axes shift when copied/filled
$A$1Fully absolute — neither axis shifts
A$1Row-locked — the row stays fixed, the column shifts
$A1Column-locked — the column stays fixed, the row shifts

With the caret on (or inside) a reference while editing a formula, press F4 to cycle it through all four forms in this order: A1$A$1A$1$A1A1. For a range, both endpoints cycle together.

Absolute anchors matter when you copy, fill, or move cells: an anchored axis never shifts; only a relative axis shifts with the operation. If a relative axis would shift past the top/left edge (or a structural delete removes the row/column it points to), the whole reference is poisoned to the literal text #REF! — Sheets' behavior of baking the error into the formula rather than silently clamping it.

Blank and text handling

ValueArithmetic (+ - * / ^ %)& concatAggregate member (SUM, …)IF condition
numberitselfstringifiedincluded≠ 0
blank cell0""skippedfalse
numeric text ("5")coerces (="5"+1"6)itselfskipped#VALUE!
other text ("abc", "")#VALUE!itselfskipped#VALUE!
boolean1 / 0TRUE/FALSEskippeditself

A blank referenced cell (=A1 where A1 is empty) displays as 0. SUM, AVERAGE/AVG, MIN, and MAX follow Google Sheets tolerance: a blank or text member is skipped rather than poisoning the whole aggregate with #VALUE! — a genuine error cell still propagates its error code.

IF is lazy — only the taken branch runs

=IF(cond, a, b) only evaluates whichever of a/b the condition selects, so =IF(B1=0, 0, A1/B1) returns 0 when B1 is 0 instead of erroring — the other branch's division is never attempted. IFERROR, IFNA, and ISERROR are lazy the same way (they must catch a thrown error from their own argument); every other function evaluates its arguments up front.

Function library

FunctionNotes
SUM(v1, …)Variadic; flattens ranges; blank/text members skipped.
AVERAGE(v1, …) / AVG(v1, …)Same tolerance as SUM; an all-blank average is #DIV/0!.
MIN(v1, …) / MAX(v1, …)Same tolerance; an all-blank result is #VALUE!.
IF(cond, a, b)Lazy — see above. May return a string.
COUNT(v1, …)Counts only numeric members.
COUNTA(v1, …)Counts every non-blank member.
COUNTBLANK(v1, …)Counts only blank members.
COUNTIF(range, criteria)Counts members matching criteria (see below).
SUMIF(range, criteria, [sum_range])Sums the matching positions of sum_range (defaults to range).
AVERAGEIF(range, criteria, [average_range])Averages the matching positions; zero matches is #DIV/0!.
ROUND(value, [digits=0])Rounds half away from zero (ROUND(-2.5,0) = -3) — not JS's round-half-up.
ROUNDUP(value, [digits=0])Rounds away from zero regardless of sign.
ROUNDDOWN(value, [digits=0])Truncates toward zero regardless of sign.
ABS(value)Absolute value.
INT(value)Floors toward -Infinity (INT(-1.5) = -2).
MOD(n, divisor)Result takes the sign of the divisor (Sheets semantics, unlike JS %); divide-by-zero is #DIV/0!.
POWER(base, exponent)Same rules as ^; a non-finite result is #NUM!.
SQRT(value)A negative argument is #NUM!.
MEDIAN(v1, …)Average of the two middle values for an even count; zero numeric values is #VALUE!.
AND(v1, …) / OR(v1, …) / XOR(v1, …)Strict — an erroring argument propagates its code. XOR is true for an odd count of truthy args.
NOT(value)Logical negation.
IFERROR(value, [value_if_error])Lazy — any error in value is replaced (default "").
IFNA(value, value_if_na)Lazy — substitutes ONLY on #N/A; any other error still propagates.
ISBLANK(value) / ISNUMBER(value) / ISTEXT(value)Type predicates.
ISERROR(value)Lazy — catches the argument's own error to report TRUE.
CONCATENATE(v1, …)Joins the string form of every scalar/range member (blank → "").
TEXTJOIN(delimiter, ignore_empty, v1, …)Joins with delimiter; ignore_empty=TRUE skips empty/blank members.
LEFT(text, [n=1]) / RIGHT(text, [n=1])First/last n characters; negative n is #VALUE!.
MID(text, start, num_chars)1-based start; start < 1 or num_chars < 0 is #VALUE!.
LEN(text)Character count.
TRIM(text)Removes leading/trailing whitespace; collapses internal runs to one space.
UPPER(text) / LOWER(text)Case conversion.
SUBSTITUTE(text, old_text, new_text, [occurrence])Replaces every occurrence, or only the 1-based occurrence-th.
VLOOKUP(key, range, index, [is_sorted=TRUE])Searches the FIRST COLUMN; returns column index (1-based) of the match. TRUE (default) = largest key <= search (ascending, no match → #N/A); FALSE = unsorted exact search.
HLOOKUP(key, range, index, [is_sorted=TRUE])Transposed VLOOKUP — searches the FIRST ROW.
INDEX(range, row, [col])1-based offsets into range's own extent; out-of-bounds is #REF!.
MATCH(key, range, [match_type=1])0 = exact (#N/A if absent); 1 (default) = largest <= key (ascending); -1 = smallest >= key (descending). Sortedness is not verified.

Function names are matched case-insensitively; an unknown name yields #NAME?. No date/time functions — dates stay opaque text.

Criteria strings (COUNTIF/SUMIF/AVERAGEIF)

A criteria argument is either a bare value (a number compares numerically; anything else compares as case-insensitive text) or a string with a leading comparison operator: ">5", "<=10", "<>x". No wildcards (*/?).

Operators and precedence

Lowest to highest binding:

TierOperatorsNotes
comparison= <> < > <= >=non-chained; =/<> case-insensitive, </>/<=/>= lexicographic on text
concatenation&always yields a string
additive+ -
multiplicative* //0#DIV/0!
unaryunary - / +above power, so -2^2 = -4 (Sheets, not 4)
power^right-associative (2^3^2 = 2^(3^2)); non-finite result → #NUM!
postfix%50% = 0.5

TRUE/FALSE (case-insensitive) are boolean literals; "…" strings escape an embedded quote as "".

Error values

CodeWhen
#ERROR!Parse/syntax error (bad token, unbalanced parens, …).
#NAME?Unknown function name, or an unknown bare identifier.
#REF!A reference points outside the table body, a structural delete removed the referenced row/column, or a circular dependency.
#VALUE!Text used where a number was required, or a text logical condition.
#DIV/0!Division by zero (or an empty AVERAGE).
#NUM!A numeric operation produced a non-finite result.
#N/AMATCH/VLOOKUP/HLOOKUP found no matching entry.

A cyclic reference (a formula that depends, directly or indirectly, on its own cell) evaluates as #REF! rather than hanging or crashing.

Fill and series

Drag the small handle at the bottom-right corner of a selection (or press Cmd+D to fill down / Cmd+R to fill right from the selection's top row / left column) to extend a pattern:

  • A formula cell is relative-reference-translated for its new position (absolute-anchored axes stay put, per the $ rules above).

  • A numeric column/row (e.g. 1, 2, 3) continues its linear step.

  • Text with a trailing number (e.g. Item 1, Item 2) continues the same prefix and step, padding to match the source's digit width.

  • Anything else (mixed content, or a source with only one non-series value) repeats the source block verbatim ("cyclic" tiling).

  • Double-click the fill handle to fill down to the data extent of the adjacent column (left, or right at column 0) — the Sheets shortcut for "fill to where my data ends."

The fill handle is a mouse-only affordance; there is currently no keyboard equivalent for starting a fill drag (Cmd+D/Cmd+R above cover the keyboard case for a selection you've already made).

Clipboard behavior

  • Cmd+C / Cmd+X / Cmd+V are standard browser clipboard events and always work, including across zudo-text windows.

  • Copy writes the computed VALUES as tab-separated text (so pasting into another app gives it numbers, not =SUM(...)), plus an internal text/html payload that additionally carries each cell's raw formula-or-plain text — this is what lets a same- or cross-window zudo-text paste restore formulas instead of just values.

  • Pasting back into a zudo-text sheet restores formulas (relative- translated to the new location) when the clipboard content is recognized as a zudo-text-internal copy; otherwise (content from another app, or the system clipboard changed since the copy) only values are pasted. Pasting into a selection that is an exact whole multiple of the copied block's size tiles the source to fill it, translating each repetition's formulas independently.

  • Cut moves formulas verbatim — it does NOT rewrite references. A moved formula still means exactly what it always meant (the reference is to the same cell it always was); only a Copy shifts relative references to account for the new position. This matches Sheets/Excel.

  • The right-click context menu's Paste / Paste values only entries may show as disabled (with a Cmd+V / Cmd+Shift+V hint) on this app's WebKit runtime, which doesn't support the on-demand clipboard-read API those menu items need — keyboard Cmd+V is unaffected, since it fires from the browser's native paste event instead.

Keyboard reference

Navigation and selection (cell focused, not editing)

KeyAction
Arrow keysMove the active cell
Shift+ArrowExtend the selection
Cmd+ArrowJump to the data edge in that direction
Cmd+Shift+ArrowExtend the selection to the data edge
Home / Shift+HomeMove / extend to the start of the row
End / Shift+EndMove / extend to the end of the row
Cmd+Home / Cmd+Shift+HomeMove / extend to A1
Cmd+End / Cmd+Shift+EndMove / extend to the last data cell
Page Up / Page Down (+Shift extends)Move 10 rows up/down
Tab / Shift+TabMove right / left
Cmd+ASelect the data region; press again to select the whole grid
Cmd+SpaceSelect the active cell's column
Shift+SpaceSelect the active cell's row
EscapeClear the current selection
Delete / BackspaceClear the selected cells' contents (keeps structure/merges)
Shift+F10 / the Menu keyOpen the right-click context menu at the active cell

Editing

KeyAction
Enter / F2Begin editing the selected cell
Any printable characterBegin editing, replacing the cell's content
Enter / Shift+Enter (while editing)Commit and move down / up
Tab / Shift+Tab (while editing)Commit and move right / left
Escape (while editing)Cancel the edit
F4 (while editing a formula)Cycle $ anchors of the reference under the caret
Cmd+Z / Cmd+Shift+ZUndo / redo

Rows, columns, and handles

KeyAction
Alt+Left/Right (column gutter focused)Move that column one slot left/right
Alt+Up/Down (row gutter focused)Move that row one slot up/down
Enter / Space (gutter focused)Select the whole row/column (Shift extends)
Arrow keys (resize handle focused)Step the pending column width / row height
Home / End (resize handle focused)Jump to the minimum / maximum size
Enter (resize handle focused)Commit the pending size
Escape (resize handle focused)Cancel the pending resize
Cmd+D / Cmd+RFill down / fill right from the current selection

Context menu (once open)

KeyAction
Arrow Up/Down, Tab/Shift+TabMove the roving focus (wraps at either end)
Home / EndJump to the first / last enabled item
EscapeClose the menu and restore focus to what had it before

Known limitations

  • One table per block. A document is exactly one GFM table plus its trailing SHEET comment — no multi-sheet workbooks, sheet tabs, or cross-sheet references.

  • No dates. There are no date/time functions; date-like text is always opaque passthrough content, never parsed or computed on.

  • No wildcards in criteria. COUNTIF/SUMIF/AVERAGEIF criteria support comparison operators (">5", "<>x") but not */? wildcards.

  • No multiline cells. There is no Alt+Enter line break inside a cell — the in-cell editor is a single-line input, not a textarea.

  • Desktop only. The command palette entry, keyboard shortcut, and dialog are all desktop-gated; there is no touch/pointer-gesture support or mobile editing surface for sheets.

  • No virtualization. Very large sheets (roughly 1,000+ rows) are not specifically optimized for rendering/interaction performance.

  • No in-place (non-modal) editing, collaborative editing, find & replace inside the sheet, cell comments/notes, conditional formatting, data validation, or charts in the current version.