Kakoune’s extension system is designed to be modular rather than hyper-efficient, and while this allows plugins to be written without ceremony (“write a .kak file, stick it in this directory”), it does mean that Kakoune’s startup time suffers as it loads every detail of every plugin.
There have been a number of solutions proposed for this problem:
-
A standard filesystem structure for plugins so that Kakoune (or a third-party plugin manager) can be smarter about which code is loaded and when
- Very similar to what Vim does, so it’s a proven approach
- Requires documentation and enforcement of the structure
- Makes it writing a plugin more difficult
-
A central configuration of which filetypes require which scripts loaded so that dependencies can automatically be loaded
- Simpler than imposing a structure on every plugin
- Only really addresses filetypes; there may be other things that benefit from lazy loading
- It’s not clear where such a configuration would exist, or how it would be maintained
-
A module/dependency system where scripts explicitly state what other scripts they depend on
- Scripts without dependencies are still simple
- Works for all kinds of dependencies, not just filetypes
- Would require changes to many existing scripts
- Would require a bunch extra logic in Kakoune itself
-
Scripts can just avoid defining highlighters until a file of the relevant type is loaded
- Existing scripts work unmodified
- No extra logic required in Kakoune
- Doesn’t work for scripts with dependencies, unless the script quickly sets the filetype option to different values to trigger their hooks, which is a bit of an ugly hack
I had another idea on IRC today, so I figured I should write it down to see what people think. This idea has two parts: the PluginEvent hook, and a convention for lazy loading.
The PluginEvent hook
Most of Kakoune’s hooks indicate that something has changed in Kakoune’s internals, and plugins can’t add or modify Kakoune’s internals, so they don’t need to add new hooks. On the other hand, sometimes plugins want to interact with each other, and for that purpose custom hooks would be great.
A non-lazy-loading related example: kakoune-cargo runs the cargo build-process in the toolsclient, because that seemed to be the Kakoune way, but the other day somebody contacted me and asked me if there was a way to automatically jump to the first error when compilation finished. When cargo runs in the toolsclient, I don’t want that, because I might be doing something else while the build is in progress and I don’t want to be interrupted, but I can see that in other circumstances that would be a useful behaviour. Unfortunately, it’s not easy to make configurable, because “what happens when compilation finishes” is a BufCloseFifo hook inside a printf
command inside a %sh{}
block, and not really the place for checking a boolean option, or reading a command to eval from a string option, or anything like that.
If Kakoune provided a PluginEvent hook, my plugin could say something like:
hook -once buffer BufCloseFifo %{
plugin-event "cargo-complete:%arg{@}"
}
…and the end-user could put this configuration into their kakrc:
hook global PluginEvent cargo-complete:(check|build|test|doc).* %{ cargo-next-error }
Lazy loading
To make lazy loading possible, I propose the following convention:
-
A plugin that wants to be lazily loaded should put all its expensive setup operations into one or more files with the
.kakp
extension (the P is for Plugin), so they won’t be automatically found and loaded -
The plugin should include a regular
.kak
file that adds hooks to source the.kakp
files:declare-option str foo_plugin_source_path %sh{ dirname $kak_source } hook -once global PluginEvent foo_load_highlighting %{ source %opt{foo_plugin_source_path}/highlighting.kakp }
-
The plugin can trigger PluginEvent from any other hooks it likes:
hook global WinSetOption filetype=foo %{ plugin-event foo_load_highlighting add-highlighter window/foo ref foo }
-
Any other plugin that wants to use a lazily-loaded resource can trigger PluginEvent in the same way.
As a result, “the list of PluginEvent patterns hooked” becomes part of a plugin’s API, in the same way that “the list of options declared” and “the list of commands defined” is today.
Of course, a plugin could just do the expensive operations in the PluginEvent hook itself, but if they’re expensive enough that you need to make them lazy, you probably don’t want Kakoune to have to read through them at startup even if it doesn’t execute them.
Conclusion
Such a system would allow Kakoune to have lazy loading without any loading-specific functionality (just one extra hook and a command to trigger it, but those aren’t specific to lazy loading). Existing plugins keep working unmodified, simple plugins stay simple, and this system can be used for all kinds of dependencies, not just highlighters.
Of course, a dependency system is inherently complex, and a simple solution just means the complexity is pushed elsewhere. In this case, I believe the complexity goes to:
- plugin authors have to document and support what PluginEvent hook patterns they respond to
- but they already have to document and support options and commands, so this isn’t a big deal
- plugin authors have to decide when to shift from eager to lazy loading, instead of getting it for free
- but we’ve gotten this far without lazy loading at all, and Pareto says only a few plugins will benefit from it
- another global namespace for plugins to trip over each other in
- but we already have this problem for options and commands