A small formula calculator for Omarchy's SUPER+SPACE menu. Summon it, type a formula, see the answer live, hit Enter to copy it to the clipboard.
Evaluation is a hand-rolled recursive-descent parser (Parser.js) — no
eval(), no shelling out to an external calculator.
Input.jsfilters keystrokes as you type. A character is only appended if it could ever lead to a valid expression — so you can never type your way into a dangling operator, an unmatched), or a second.in the same number.Parser.jsevaluates the finished formula:+ - * / ( ), decimals, and unary+/-.Calc.qmlwires the two into the actual menu surface.
There are two different places rejection happens, and they're tested differently:
Blocked at the keystroke level. Input.canAppend decides whether one
more character could ever lead to a valid expression, so these never make
it into the input field at all — the offending keystroke is just dropped:
| You type | What actually lands | Why |
|---|---|---|
.., 0..1 |
., 0.1 |
A second . in the same number can never become valid |
4), 2+) |
4, 2+ |
) needs an unmatched ( to its left |
(4)4, (4). |
(4) |
Nothing may follow ) except an operator — no implied multiplication |
3(4), (2)(3) |
34, (2) |
( can only start the expression or follow an operator/( — no implied multiplication |
*2, (*2) |
2, (2) |
* / / can't start a term |
Because of this filtering, a few strings that look invalid can only ever
be produced by calling Parser.evaluate directly, never by typing —
5**3 and 2.3.4 both get their duplicate operator/. dropped by
Input.js before they'd ever reach the parser as written (5**3 types
through as 5*3, 2.3.4 as 2.34). Their tests exist to pin down
Parser.evaluate's own grammar in isolation, not a reachable UI state.
Rejected at evaluation time. These type through fine — nothing stops the keystrokes — but the finished formula doesn't parse or doesn't evaluate. While typing, this just clears the result line silently; submitting it (Enter) shows "Not a valid formula":
| Input | Why |
|---|---|
2+, (3 |
Incomplete — parsed as far as it goes, then runs out of formula |
2/0 |
Division by zero, which is only knowable once you have both operands |
2+ and (3 are also exactly what the next section calls "not yet
complete" — same string, different moment. Still typing it shows the
muted hint; hitting Enter while it's still unfinished is what turns that
into the "Not a valid formula" error.
All of the above are covered in tests/tom-calc.test.js.
These aren't rejected — they're valid states the UI narrates instead of erroring on:
- Mid-formula, not yet complete (e.g.
2+,(3): the result line just clears and a muted "Waiting for a valid formula…" hint shows. This is expected while typing, not treated as a mistake. - Digit cap reached (15 significant digits on the current number, matching omacalc's own cap — doubles only carry ~15–17 significant digits of real precision anyway): further digits are refused and a "Too big — use a real calculator" message shows instead of silently truncating.
There's no installer yet — the plugin is loaded by symlinking this repo into Omarchy's plugin directory:
ln -s /path/to/tom-calc ~/.config/omarchy/plugins/tom-calcThen reload the shell so it picks up the new plugin:
omarchy restart shellOnce loaded, summon it directly with the shell IPC:
omarchy-shell shell summon tom-calc(toggle instead of summon closes it on a second call.) There's no
dedicated omarchy subcommand for it — that's reserved for first-party
plugins — so this generic IPC call is the mechanism.
node --testParser.js and Input.js are plain QML script files (no
module.exports); the test harness loads them into a vm context so their
top-level functions come back as properties on an object, the same way
QML's import "X.js" as Y exposes them.