* Fix extended task state widget click behavior in widgets
* Add mouse click handler on extended task state span to prevent
CodeMirror from moving selection to widget source on normal click
* Let `Alt+Click` pass to parent div for the "cursor to source" behavior
matching simple checkbox `Alt+Click` behavior
* Skip state cycling on `Alt+Click`
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Prevent task state corruption from concurrent cycling on mobile
On mobile, rapid taps on an enhanced task state (e.g. `[PLANNED]`) could
fire multiple events before the first `taskCycleAtPos` completed. Add
a reentrancy guard to `taskCycleAtPos` so concurrent calls are dropped.
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
---------
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Fix extended task rendering in widgets
This patch addresses several issues.
1. Extended tasks like `[PLANNED]` or `[TODO]` were not rendering
correctly when generated via widgets. The HTML renderer was producing
plain text without brackets or proper styling, while simple `[x]`
tasks worked fine. The fix produces the same DOM structure as
CodeMirror producing coherent styling everywhere.
2. Widget checkboxes are not interactive now. Since widget tasks have no
backing state handler clicking them only caused a misleading visual
toggle with no effect. The `disabled` attribute prevents this
confusion and the dimmed appearanceit signals to the user that the
checkbox is read-only in this context which is the truth.
To see it in action try the following Markdown snippet in SilverBullet:
```
- [x] #foo [att: i]
- [PLANNED] bar #bar [att: i]
${'- [x] #foo [att: i]\n- [PLANNED] bar #bar [att: i]'}
```
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Revert and just prevent widget checkboxes from toggling visually
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Support custom task states in widgets and indexer
Custom task states like `[PLANNED]` or `[IN PROGRESS]` now render
correctly in *widgets*, matching the same DOM structure as CodeMirror
uses in the editor. Widget-rendered tasks that reference a real task
(via `page@pos`) are fully interactive — clicking an extended state
cycles it through the configured states and updates the source task.
Tasks without a reference are disabled to prevent misleading visual
feedback.
The task indexer now respects the `done` flag on custom states defined
via `taskState.define`, so queries like `where not t.done` correctly
exclude tasks in states marked as done. Previously only the built-in
simple tasks (`- [x]` or `- [X]`) states were treated as completed. The
`Task: Remove Completed` command already handled custom done states and
continues to work unchanged.
State cycling now preserves the definition order from `taskState.define`
calls instead of sorting alphabetically. A new `order` field allows
explicit control over the cycle sequence. Without it, states cycle in
definition order. With it, states are sorted by the `order` field value.
This gives users full control over the progression, for example:
PLANNED --> IN PROGRESS --> FINISHED
The `taskState.define` API now validates its input against a schema:
`name` (string, required), `done` (boolean, optional), and `order`
(number, optional).
The `expandMarkdown` no longer injects "fake" `page@pos` references into
tasks rendered via `widget.markdownBlock` or `widget.new` which was
causing broken links and unwanted interactivity on static task content.
The `task:stateChange` event is dispatched when toggling tasks from
widgets, ensuring Lua listeners (such as some custom completed timestamp
management) fire correctly regardless of where the toggle originates.
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
---------
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
Based on https://www.markdownlang.com/extended/footnotes.html
* Both "official" [^1] and [^1]: def
* And inline ^[My foot note]
Features:
* Properly parsed in markdown parser
* Syntax highlighting
* Live Preview to ... when on-hover preview of content, on-click moves
cursor to definition
* Reference completion
* Invalid references are marked
* Fix table row parser
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Remove all `{[command button]}` remnants
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Render `Attribute` nodes in HTML like CodeMirror does
This patch
- ensures always render `Attribute`,
- produces `sb-attribute` span with sub-spans matching CodeMirror DOM,
- removee `preserveAttributes` from Markdown render options and callers.
Finally the attributes looks the same everywhere (table, text, widget).
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
---------
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Fix table row parser
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Remove all `{[command button]}` remnants
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Add tests
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Fix extended task rendering in case of following EoL
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
---------
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
Lua `${...}` expression results are rendered using two parallel paths:
- **HTML path** (display path) for visual display in the editor and
- **Markdown path** (copy path) for the *copy button* output.
Every Lua value is classified as one of:
- **Scalar:** number, string, boolean, or `nil`
- **Empty table:** `{}`
- **Array:** a `LuaTable` with only sequential integer keys (no string keys)
- **Array of tables:** an array where every element is a `LuaTable`
- **Record:** a `LuaTable` with any string key (may also have integer keys)
Scalars are rendered as inline Markdown - wiki links, tags, bold, and
other inline syntax are resolved, `nil` renders as `<span data-empty>`.
Empty tables render as `<table data-table-empty>`.
An array of tables renders as a multi-row `<table>`. Column headers
are the union of all element keys. Each cell value recurses through
the same classification.
An array of scalars (or a mix of scalars and tables) renders as `<ul>`
with one `<li>` per element. Each item value recurses.
A record renders as a single-row `<table>` with one column per key.
Cell values recurse.
Every `<td>` carries `data-table-cell-type` and optionally
`data-table-cell-empty`. Every `<li>` carries `data-list-item-type` and
optionally `data-list-item-empty`. The outermost element carries
`data-type`. These attributes allow CSS styling of rendered widgets.
Nesting is **unlimited**. Tables inside cells, lists inside cells,
tables inside list items all render correctly in HTML.
Scalars render as plain text. Lua `nil` renders as `nil`. Empty tables
render as `*(empty table)*`.
An array of tables renders as a pipe-delimited Markdown table. Nested
values in cells are rendered as Lua literals via `toStringAsync()`
(e.g. `{1, 2, 3}` or `{a = 1}`), since Markdown table cells cannot
contain block-level content.
An array of scalars renders as a Markdown list (`- item`). When a list
item is itself multi-line (a sublist) it uses an empty bullet with
two-space indented content on the next line:
```
- 1
- 2
-
- nested 1
- nested 2
```
Nested tables or arrays **beyond the first list nesting** level are
rendered as Lua literals.
A record renders as a single-row Markdown table.
Column headers follow the order returned by `LuaTable.keys`: string keys
in their insertion order, followed by integer keys in ascending order.
For example, `{a = 1, b = 2, 3, d = 4}` produces columns `a
| b | d | 1`. This is consequence of `LuaTable` storing string keys
and the array part separately (for performance).
Arrays mixing plain scalars and record-like tables (e.g. `{1, 2, 3,
{a=3}}`) were previously rendered as a single flat table due to an
internal JS conversion (`toJS`). They now render as a list, with record
elements shown as nested tables - which more accurately reflects their
Lua structure. This is the only divergence from the original
presentation.
To restore the old tabular layout, wrap all elements as records with
a consistent key:
```lua
-- Before (was a flat table, now a list with nested table):
${{1, 2, 3, {a=3}}}
-- After (explicit records, renders as a table):
${{{value=1}, {value=2}, {value=3}, {a=3}}}
```
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Don't know what's going on with the Deno docker image, disabling the deno.lock file for now
* Another try
* Space Lua: Align numeric and table semantics with Lua
This change improves Space Lua compatibility with standard Lua 5.4,
focusing on numeric subtypes and table behavior. The test suite is
extended to lock in the expected semantics and should pass under both
Space Lua and a Lua interpreter.
SUMMARY OF CHANGES
------------------
Tighten Lua compatibility across evaluator and runtime:
- correct metamethod dispatch (`__index`, `__newindex`, `__call`,
comparison metamethods),
- loop limits,
- raw metamethod lookups.
Rework numeric semantics to preserve Lua **integer** vs **float**
behavior:
- $0$ vs $0.0$ and $-0.0$,
- explicit zero kind representation, and
- updates arithmetic/bitwise coercions accordingly.
Improve parser correctness by rejecting **unary plus** with aligned Lua
errors and better parsing errors reporting.
Fix `stdlib` behavior to match that of Lua:
- `table` function `concat`, `insert`, `remove`, `sort` and `unpack`
gain metamethod awareness and enforce Lua errors,
- `ipairs` iteration updated to stop on first nil and honor `__index`,
- `tonumber` updated to Luae conversion using `luaToNumberDetailed`,
- `math.modf` return corrected,
- `math.type` accuracy improvements for float/integer and $-0.0$,
- `math.pi` added.
Fix numeric subtypes for `/` and `^` operators so `math.type` matches
Lua results using tagging as well as unary `-`.
Expand test coverage:
- new `metamethods_test.lua` for Lua metamethod/operator semantics,
- extend arithmetic and length tests for zero-kind propagation and
`rawlen` vs `__len` metamethod,
- Extend `math` test suite to test proper Lua alignment (`math.type` and
more), and
- update context error expectations for Lua error messages.
RATIONALE
---------
Lua differs from JavaScript by having two numeric subtypes: **integer**
and **float**. Operators depend on the subtype: `+`, `-`, `*`, `//` and
`%` use integer mode when both operands are integers and float mode
otherwise. Bitwise operators require integers and `math.type(x)` reports
"integer" or "float". JavaScript has one numeric primitive type
(`number`) so a plain number value cannot record whether Lua considers
a value to be a float when the value has no fractional part (for example
$2.0$).
Lua also differs from "everything is IEEE 754 double" because the rules
are defined in terms of integer and float subtypes. Float operations
preserve IEEE 754 behavior including `NaN`, infinities and signed zero
($-0.0$) which affects results like $1/0.0$ versus $1/-0.0$. Integer
arithmetic does not preserve $-0$ and collapses it to $0$. Lua integer
arithmetic is exact within its integer range while JavaScript `number`
cannot exactly represent all integers in that range.
Lua numbers are integers or floats. Numeric strings coerce to integer or
float based on _lexical_ form. Each arithmetic operator selects the
result subtype from the operator rules and operand subtypes. Integer
only operators (bitwise and `//` as integer division) require integer
representability. Mixed arithmetic promotes to float as needed. Two
operators are **always float** typed: division (`/`) and exponentiation
(`^`) produce floats even if both operands are integers and even if the
numeric value has no fractional part. `math.type` reports that internal
subtype.
Tables are associative arrays and assigning `nil` removes a key. The
length operator `#` uses `__len` metamethod if present otherwise it uses
the raw length rule. `rawlen(table)` ignores `__len`. Without `__len`
Lua defines `#` as some boundary `N` such that `table[N]` is not `nil`
and `table[N+1]` is `nil`. If the table has holes (missing or `nil`
entries in the positive integer key sequence) the boundary may be non
unique so `#` is stable only for proper sequences without holes.
PERFORMANCE NOTES
-----------------
Numeric changes add small checks to preserve Lua integer and float
subtype semantics and avoid allocations except when the subtype would
otherwise be lost.
Some table operations may be slower due to stricter Lua 5.4 behavior
especially around length and sequence boundary handling which currently
requires extra metadata tracking and scans and cannot be avoided without
a completely different internal table representation.
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Another try
* Replace `LuaFloatTag` plain objects with boxed Number for float tagging
* Restore pre-merge eval/numeric architecture and fix regressions
This commit restores the original branch architecture.
On top of the restored foundation, float-typed integer results (e.g.
`1.0 + 1.0` = `2.0`, `0.0 // 1.0` = `0.0`) are now correctly tagged via
`makeLuaFloat` so that `tostring` and `math.type` report them as floats.
The *unary minus* fast path for float literals and the `tonumber`
function also preserve float tagging.
Performance regressions from the merge are addressed by avoiding
`Number` boxing for non-integer floats (`3.14` needs no tag — it is
unambiguously float), adding *string key* fast paths in `LuaTable`
`has`/`rawGet`/`rawSet` to skip numeric normalization for the dominant
case, and inlining a `typeof` check in math standard library functions
to avoid function call overhead on plain numbers.
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Replace boxed `Number` float tagging with plain tagged objects
* Replace `new Number()` boxing with plain tagged float objects for Lua
float type tracking.
* Integer-valued floats that need type disambiguation are now
represented as `{ value: number, isFloat: true }` instead of boxed
`Number` objects with a symbol property.
* Pre-allocated singletons are used for positive and negative float
zeros to avoid allocation entirely in common cases.
* Updated all detection, unwrapping, and coercion paths across
`numeric.ts`, `runtime.ts`, `eval.ts`, `stdlib.ts`, and `stdlib/`
modules to use the new `isTaggedFloat` type guard.
* Removed all `instanceof Number` checks.
* Deleted the `FloatKind` symbol and eliminated redundant helpers
`isLuaFloat`, `isFloatTag`, `getZeroBoxKind` and `toPlainNumber` that
became dead code.
* Simplified `math.type`, `luaToString`, `luaEquals`, `luaTypeName` and
various other key normalization paths.
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Add fast paths in `coerceNumericPair` for tagged float operands
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Fix copy/paste typo
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Avoid extra `LuaEnv` allocations in "For" and "ForIn" loops
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* [Space Lua] Reuse loop variable environment in `for` and `for-in` loops
Numeric `for` and generic `for-in` loops allocated a fresh `LuaEnv` on
every iteration to hold loop variables. But this is only necessary when
a closure inside the loop body captures the loop variable.
The optimization uses a two-level check computed at parse time. If no
function definition exists in the loop's subtree, environment reuse is
safe. When a function definition is present a deeper analysis walks the
block to determine whether any function body references the loop
variable names without them being shadowed by its own parameters. When
a closure captures a loop variable the loop fall back to per-iteration
allocation.
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* [Space Lua] Format numbers using standard Lua rules
Standard Lua formats floats via C `sprintf("%.14g")` (14 significant
digits, scientific notation when shorter, exponent padded to 2 digits,
and a guaranteed `.0` suffix for integer-valued floats).
* Replace the old `luaFormatNumber` with JS `toPrecision(14)`-based
implementation that reproduces this behavior.
* Integrates it so that `${}` expressions in the UI also display
numbers correctly.
* Fixes tagged floats (`{ value, isFloat }`) were being stripped by
`luaValueToJS` or matched as plain objects before reaching the number
formatter. This caused `${}` expressions to render raw JS numbers.
Examples:
```
- ${tostring(2^63)}
- ${2^63}
- ${(2^63)}
```
All of the the above examples show correct `9.2233720368548e+18` now.
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* [Space Lua] Fix `string.format` for floats and tagged numbers
Unwrap tagged floats before `printf`, handle `inf`/`-inf`/`-nan` in
`formatDouble`, and fix `%g` producing `0e+00` for zero. Hopefuly it's
enough to gain Lua formatting.
Tests:
```
- ${string.format("%.14g", 0.0)} - `0`
- ${string.format("%.14g", 1.0)} - `1`
- ${string.format("%.14g", 1/3)} - `0.33333333333333`
- ${string.format("%.14g", math.pi)} - `3.1415926535898`
- ${string.format("%.14g", 1e-10)} - `1e-10`
- ${string.format("%.14g", 1e18)} - `1e+18`
- ${string.format("%.14g", 2^63)} - `9.2233720368548e+18`
- ${string.format("%.14g", 2^53)} - `9.007199254741e+15`
- ${string.format("%.14g", 1.7976931348623e+308)} - `1.7976931348623e+308`
- ${string.format("%.14g", 5e-324)} - `4.9406564584125e-324`
- ${string.format("%.14g", 0/0)} - `-nan`
- ${string.format("%.14g", 1/0)} - `inf`
- ${string.format("%.14g", -1/0)} - `-inf`
```
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* [Space Lua] Add and integrate new `luaFormat` utility and test suite
* Add `luaFormat` string formatting function compatible with Lua, and
integrate it across the codebase as a replacement for prior formatting
approaches.
* Add extensive test suite.
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Fix check
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* [Space Lua] Add `%a`/`%A` and `%q` format specifiers to `string.format`
Implement hexadecimal floating-point (`%a`/`%A`) and quoted literal
(`%q`) specifiers.
* Add `%a`/`%A` as IEEE 754 double decomposition with full flag, width
and precision support.
* Add `%q` as producind valid Lua literals for strings, numbers,
booleans and nil.
* Use `Math.PI` for `math.pi` to preserve full double precision.
* Remove Deno based test suite and replace it with native Lua test
suite.
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* [Space Lua] Add `%p` format specifier to `string.format`
In standard Lua, `%p` formats the internal C heap address of a value,
producing output like `0x55a3bc4e2f10`. It works on tables, functions,
threads, strings, and userdata (GC-ed objects). For `nil`, booleans, and
numbers it returns `(null)`.
In Space Lua, there are no *raw memory addresses* since the runtime is
JavaScript. Instead, `%p` assigns a *stable sequential integer* to each
object via a `WeakMap`, formatted as a 14-digit zero-padded hex value.
The key difference is that identifiers are deterministic and sequential
rather than random-looking heap addresses:
```lua
local t = {}
print(string.format("identifier: %p", t)) -- 0x00000000000001
print(string.format("the same: %p", t)) -- 0x00000000000001
print(string.format("another: %p", {}) -- 0x00000000000002
```
For strings, a regular `Map` is used so identical string content always
produces the same identifier.
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Revert merge changes to the deno.json
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Replace `interface` with `type`
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Remove `has_math()` relict function test
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Refactor loop to map
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Remove `Deno.remove("deno.lock")` weirdness
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
* Refactor: Early return undefined in `astNumberKind` instead of assigning
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
---------
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
Co-authored-by: Zef Hemel <zef@zef.me>
When the vim extension is enabled and in "normal" mode, some default
bindings collide with Silverbullets commands (eg: `ctrl-p` - Share,
`ctrl-o` - Open Document, `ctrl-e` - Export, `ctrl-g h` - Home). These
vim bindings are not always functional (they may appear to do nothing
depending on vims state). This gives the impression that Silverbullet is
bugged and wont trigger commands. In "insert" mode, most bindings are
passed through correctly with some exceptions (eg: `ctrl-o`).
By splitting the command keybindings into their own keymap extension and
loading it before the vim extension, we can ensure Silverbullet commands
always trigger.
This does mean some vim bindings are overshadowed, but if a binding
triggers a Silverbullet command, its generally obvious that *something*
happened and the user should be able to recognise why the vim binding
failed and then rebind the command or vim mapping.
* Make any frontmatter <scheme>:// URI's clickable
http/https links are opened in a new tab/window (via target="_blank"),
other schemes are opened in the current tab, as the browser will prompt
the user to open another application (or do this automatically).
Per https://www.rfc-editor.org/rfc/rfc3986#section-3.1, the scheme must
follow the pattern:
scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
* Make `mailto:...` strings clickable in frontmatter
- Introduced `currentPageMeta` method to encapsulate access to the current page's metadata.
- Updated references to page metadata throughout the codebase to use the new method, improving code readability and maintainability.
- Adjusted related components in the UI and linting logic to utilize the new method for consistency.