feat(plugin): per-plugin KV storage#1187
Conversation
Adds matcha.store_set, store_get, store_delete, store_keys to the Lua plugin API per floatpane#510. Per-plugin scoping is preserved by tracking the active plugin context during plugin load, hook invocation, and keybinding callbacks. The manager attributes hooks/keybindings to their owning plugin so the storage API resolves to ~/.config/matcha/plugins/<plugin>/data.json even when multiple plugins call the same API. - plugin/storage.go: pluginStore (mutex + atomic write + 0o600 mode) - plugin/api.go: register store_set / store_get / store_delete / store_keys - plugin/plugin.go: currentPlugin tracking, KeyBinding plugin attribution - plugin/hooks.go: registeredHook captures plugin attribution; CallHook swaps currentPlugin per callback so isolation holds across plugins - plugin/storage_test.go: round-trip, persistence, concurrent writes, file mode 0o600 - plugin/api_storage_test.go: Lua-level integration + cross-plugin isolation - plugin/README.md: persistent storage section with the issue example Closes floatpane#510.
|
Pushed 138272f. The Windows failures came down to t.Setenv("HOME", t.TempDir()) not isolating anything on Windows because os.UserHomeDir() reads %USERPROFILE%, not $HOME, so every test in the package shared the real Windows profile and bled state. Added a setTestHome helper that sets both, and guarded the 0o600 mode assertions on Windows since NTFS does not honor Unix permissions the same way. go test ./plugin/... passes locally on macOS. |
- flush(): unique tmp file via os.CreateTemp + explicit Chmod(0600) before rename, preventing collision when two pluginStore instances target the same plugin and ensuring 0600 mode survives overwrite - newPluginStore: reject plugin names that aren't [a-zA-Z0-9_-]+, blocking path traversal via crafted plugin names - currentStore now returns (*pluginStore, error); Lua API distinguishes missing plugin context from real store init failures and surfaces the underlying error to the plugin author - tests: cover overwrite mode preservation, invalid name rejection, and Lua-level error propagation on store init failure
Tests use t.Setenv("HOME", t.TempDir()) for isolation, but
os.UserHomeDir() reads %USERPROFILE% on Windows, not $HOME.
Result: every test in plugin/ shared the same real Windows
profile directory and bled state into each other.
Add setTestHome() helper that sets both HOME and USERPROFILE,
and replace the inline Setenv calls. Guard the file-mode
assertions in TestPluginStoreFileMode and the Overwrite variant
behind runtime.GOOS != "windows" since NTFS does not honor
Unix permission bits the same way (0o600 reads back as 0o666).
Verified: go test ./plugin/... passes locally on macOS.
138272f to
e1dce9d
Compare
|
@mvanhorn update documentation at |
Per @andrinoff's review on PR floatpane#1187: add the matcha.store_set / store_get / store_delete / store_keys functions to docs/docs/Features/Plugins.md so the user-facing plugin docs match what plugin/README.md already describes.
|
Pushed bd3079b - added the matcha.store_set / store_get / store_delete / store_keys section to docs/docs/Features/Plugins.md (after matcha.notify, before Events). plugin/README.md already had the storage API + Persistent storage section in this PR. |
What?
matcha.store_set(key, value),store_get(key),store_delete(key),store_keys()to the Lua plugin API~/.config/matcha/plugins/<plugin_name>/data.jsonwith mode0o600, JSON-encoded, atomic write via unique temp file +RenameregisteredHook{fn, plugin})^[a-zA-Z0-9_-]+$is accepted as a path component, blocking traversalManagerinstances, concurrent writes, file mode0o600(including survives overwrite), invalid plugin name rejection, Lua-level error propagation on corrupt JSON, hook/keybinding plugin attributionscreenshots/cmd/plugin_storage_demo/main.goloads the same plugin in twoManagerinstances against the sameHOMEto prove cross-session persistenceSimulated demo (Remotion) — matcha theme, scripted UI, not a live capture.
Why?
This is the maintainer-spec from issue #510:
The four function names, JSON-per-plugin file format, and config-dir path all match the spec verbatim:
#510 is the gating issue for the broader plugin-API cluster (#511 hooks, #512 lifecycle, #513 account info, #514 multi-field prompts) - persistent storage is the most-requested missing piece for the existing 35-plugin marketplace.
Notes
The implementation extends the per-plugin attribution model so future plugin APIs (account info, lifecycle hooks) can reuse the same
currentPluginplumbing inManager.Closes #510.
This contribution was developed with AI assistance.