Skip to content

0.66.0

Choose a tag to compare

@github-actions github-actions released this 18 Jul 00:31
· 296 commits to master since this release
0.66.0
7a2ad26

Added

  • vim.regex() — ECMAScript regular expressions in Luavim.regex(pattern, flags?) creates a regex object exposing match_str, match_line, match_pos, replace, and test methods. Uses JavaScript's RegExp engine (not Vim regex syntax). Returns 0-based byte offsets matching Neovim's vim.regex() convention. Invalid patterns raise a Lua error catchable with pcall.
    • Plugin: src/lua/regex.ts (new), src/lua/api.ts (registration via injectRegex)
  • Fengari fork: __gc metamethods via FinalizationRegistry__gc metamethods on userdata are now invoked when the userdata becomes unreachable from JavaScript. Registration happens at lua_setmetatable time (only when the metatable contains __gc). Finalizers are drained at three points: outermost luaD_pcall return, collectgarbage("collect"), and lua_close. Errors in __gc are silently swallowed (PUC-Rio semantics). Finalization order is unspecified. Tables with __gc are not finalized (userdata only). Environments without FinalizationRegistry gracefully degrade (no registration, no errors).
    • Fork: ~/Repos/fengari/src/lstate.js (finalizer infrastructure on global_State, drainFinalizers, lua_close drain + unregister), ~/Repos/fengari/src/lapi.js (lua_setmetatable split LUA_TUSERDATA/LUA_TTABLE, FR registration), ~/Repos/fengari/src/ldo.js (drain point in luaD_pcall), ~/Repos/fengari/src/lbaselib.js (collectgarbage("collect") drain integration)
  • Fengari fork: collectgarbage() no longer crashes — all 8 collectgarbage modes now return safe values instead of throwing luaL_error("lua_gc not implemented"). "count" returns 0, 0 (no memory tracking). "collect" drains the __gc finalizer queue. "isrunning" returns false. All other modes return 0. Previously, any Lua code calling collectgarbage() crashed the entire init sequence.
    • Fork: ~/Repos/fengari/src/lbaselib.js
  • Native JS error propagation via lua_atnativeerror — the plugin now installs a lua_atnativeerror handler that converts native JS errors (TypeError, RangeError, etc.) to extractable Lua strings. Previously, native JS errors thrown inside fengari C functions were pushed as lightuserdata and lost — lua_tolstring returned null, producing generic "Unknown Lua error" messages. The handler extracts Error.message (or String(e) for non-Error values) and pushes it as a Lua string. Covers all threads including coroutines (handler is on global_State).
    • Plugin: src/lua/engine.ts (lua_atnativeerror handler in createSandboxedState), src/lua/types.d.ts (lua_touserdata, lua_atnativeerror, lua_pushinteger type declarations)

Changed

  • Fengari fork: sprintf-js replaced with custom formatter — the sprintf-js npm dependency (sole runtime dependency) has been replaced with a purpose-built luaSprintf function in the fork's src/lstrlib.js. The fork now ships with zero runtime dependencies. Output is byte-identical to the previous implementation for all standard format patterns.
    • Fork: ~/Repos/fengari/src/lstrlib.js, ~/Repos/fengari/package.json, ~/Repos/fengari/DIFFERENCES.md
  • Fengari fork: integers widened from 32-bit to 53-bitmath.maxinteger is now 9007199254740991 (2^53 - 1). Arithmetic operations use full 53-bit Number precision. string.packsize("j") returns 8 (was 4). tonumber("1099511627776") now returns the integer (was nil). Bitwise operations remain 32-bit (JavaScript platform limitation). See ~/Repos/fengari/DIFFERENCES.md § "Integer widening" for the full change list and remaining limitations.
    • Fork: ~/Repos/fengari/src/luaconf.js, ~/Repos/fengari/src/llimits.js, ~/Repos/fengari/src/lvm.js, ~/Repos/fengari/src/lobject.js, ~/Repos/fengari/src/lstrlib.js, ~/Repos/fengari/src/ltable.js, ~/Repos/fengari/src/lapi.js, ~/Repos/fengari/src/ldo.js, ~/Repos/fengari/src/lmathlib.js, ~/Repos/fengari/src/lbaselib.js
  • Coroutine↔Promise bridge for async Lua execution — Lua callbacks (keymap functions, autocmd handlers, timer callbacks, user commands) can now call async APIs that yield the coroutine and resume when the Promise resolves. The bridge uses fengari's lua_yieldk continuations with a CoroutineRunner managing thread lifecycle, instruction hooks, timeouts (10s), and concurrency limits (16 concurrent operations). pcall correctly catches async errors across yield/resume boundaries.
    • Plugin: src/lua/coroutine-runner.ts (new: CoroutineRunner + AsyncRegistry), src/lua/engine.ts (evalLuaAsync, INSTRUCTION_LIMIT export), src/lua/types.d.ts (7 new fengari type declarations: lua_newthread, lua_resume, lua_yieldk, lua_status, lua_xmove, lua_isyieldable, LUA_YIELD)
  • vim.ob.fs.read(path) and vim.ob.fs.readlines(path) — read vault files from Lua. read returns a string, readlines returns a table of lines. Both yield internally via the coroutine bridge. Errors are catchable with pcall. Works in keymap callbacks, autocmd handlers, timer callbacks, and user commands. Also works at top level in init.lua. Blocked in snippet f()/d() nodes (raises "async APIs cannot be called from snippet nodes").
    • Plugin: src/lua/obsidian-api.ts (read/readlines C-functions), src/lua/loader.ts (fsRead callback via adapter.read + readExternalFile for absolute paths), src/lua/api.ts (fsRead on VimApiCallbacks)
  • require() for multi-file Lua configsrequire('mymodule') loads lua/mymodule.lua from the vault root. Dot-separated names resolve to subdirectories (require('utils.strings')lua/utils/strings.lua). Modules are cached in package.loaded. Circular requires detected via sentinel. Security: path traversal (..), absolute paths, and backslash paths are rejected.
    • Plugin: src/lua/package.ts (new: package table, sandboxed load(), Lua-implemented require()), src/lua/engine.ts (load kept in disabled list with re-enable note)
  • load(chunk) re-enabled with sandboxingload() compiles a string chunk and returns the compiled function (or nil + error). dofile and loadfile remain disabled. The instruction count hook applies to loaded code.
    • Plugin: src/lua/package.ts (injectSandboxedLoad)
  • evalLuaAsync for async init.lua execution — top-level init.lua code can now call async APIs like vim.ob.fs.read. The init.lua chunk runs inside a coroutine via evalLuaAsync, which compiles on the main state and delegates to invokeAsyncCapable. autocmdManager.activate() fires only after all yields complete.
    • Plugin: src/lua/engine.ts (evalLuaAsync), src/lua/loader.ts (evalLuaawait evalLuaAsync)
  • Callback sites refactored for async capability — all 4 Lua callback invocation sites now use CoroutineRunner.invokeAsyncCapable when a runner is available, with fallback to the original lua_pcall path when not. Existing sync callbacks work identically.
    • Plugin: src/lua/api.ts (keymap, user command, autocmd callbacks), src/lua/timers.ts (invokeLuaCallback + 5 call sites)
  • Snippet async guardf() and d() snippet node evaluations are wrapped with runner.setAsyncBlocked(true/false) to prevent async API calls during snippet expansion.
    • Plugin: src/snippets/dynamic-bridge.ts (guards in recomputeIfNeeded and expandDynamicSnippet)

Tests

  • 11 tests in ~/Repos/fengari/test/collectgarbage.test.js: all 8 modes return safe values, pcall succeeds, invalid mode errors
  • 7 tests in ~/Repos/fengari/test/atnativeerror.test.js: TypeError/RangeError extraction, string/number throws, pure Lua error unaffected, handler covers coroutine threads, without-handler baseline
  • 10 tests in ~/Repos/fengari/test/gc-finalizers.test.js: userdata __gc drain, no-overhead without __gc, tables not registered, error swallowing, metatable nil/change unregister, recursive drain guard, lua_close drain, post-close guard, no-FR graceful degradation
  • 8 tests in ~/Repos/fengari/test/53bit-integers.test.js: sprintf replacement (format specifiers, flags, hex float), 53-bit integer constants/boundaries, wide arithmetic, string parsing/formatting, table keying, pack/unpack with SZINT=8, 32-bit bitwise verification, wide for-loop
  • 9 unit tests in test/unit/lua/regex.test.ts: constructor validation, match_str (offsets + nil), match_line alias, match_pos from offset, replace with captures + global flag, test boolean, flags (case-insensitive), invalid pattern error, missing pattern error
  • 8 spike tests in ~/Repos/fengari/test/coroutine-promise-bridge.test.js: lua_yieldk continuations, lua_isyieldable, pcall across yield, instruction hooks, error propagation, sequential yields, Lua-level vs C-level coroutines
  • 11 unit tests in test/unit/lua/coroutine-runner.test.ts: sync path, yield/resume, rejected Promise, pcall error catch, instruction limit, timeout, concurrency limit, destroyAll, sequential async, snippet guard, thread-targeted hooks
  • 7 unit tests in test/unit/lua/eval-lua-async.test.ts: sync code, syntax errors, top-level async, sequential async, pcall at top level, side effects across yield, instruction limit
  • 6 unit tests in test/unit/lua/fs-read.test.ts: file read, pcall error catch, empty file, readlines, sequential reads, snippet guard
  • 10 unit tests in test/unit/lua/package-require.test.ts: module loading, caching, subdirectory resolution, circular require, missing module, path traversal, syntax error, runtime error, load() compilation, load() error
  • 22 e2e tests in test/specs/lua-require.e2e.ts: functional behavior (7), error handling (4), sandbox security (11)

Documentation

  • CHANGELOG.md: Added fengari fork improvements (sprintf, 53-bit integers, __gc, collectgarbage, atnativeerror), vim.regex(), coroutine bridge, async Lua APIs, require(), load(), evalLuaAsync entries
  • KNOWN_LIMITATIONS.md: 32-bit integer limitation → Implemented (widened to 53-bit), hrtime overflow claim corrected; JS RegExp item 5 → Implemented; sprintf item 7 → Implemented (zero deps); vault file reading → Implemented; coroutine bridge item 1 → Implemented (Phases 1–3); require() item 2 → Implemented; load() item 6 → Implemented; __gc item 4 → Implemented (userdata via FinalizationRegistry); error message quality item 8 → Implemented (atnativeerror handler); collectgarbage item 10 → Implemented (safe no-ops); fengari improvement opportunities priority table updated (9/10 implemented, only weak tables remaining)
  • README.md: Updated Lua configuration feature bullet with vim.regex(), async file reading, multi-file configs, and __gc userdata finalization
  • CONTRIBUTING.md: Added regex.ts, coroutine-runner.ts and package.ts to codebase structure
  • AGENTS.md: Updated fengari fork section — sprintf-js removed (zero deps), 53-bit integers, vim.regex(), async bridge, require(), load(), __gc via FinalizationRegistry, collectgarbage safe no-ops, native error propagation via atnativeerror
  • docs/configuration/lua-config.md: Added vim.regex() API reference section, vim.ob.fs.read/readlines to fs table, require() and load() sections, collectgarbage behavior, updated unsupported APIs list
  • ~/Repos/fengari/DIFFERENCES.md: Updated behavioral differences table (collectgarbage, __gc), updated inherited limitations (collectgarbage and __gc addressed)
  • ~/Repos/fengari/DIFFERENCES.md: Added "Integer widening" section, sprintf replacement documentation, updated behavioral differences table, updated files modified list

Full Changelog: 0.65.0...0.66.0