Presenting a new fast command line plugin manager

Hi, everyone!

I want to share with you a project I developed and I’ve been privately using for over three years now: a command line plugin manager called Almoxarife that installs and updates your plugins in parallel.

Different from @jdugan6240 & @almr kak-bundle, this one is not itself a plugin, so it doesn’t need to be automanaged. It is instead an external executable you can run from everywhere.

overview

I believe it fits well with Kakoune’s philosophy of integrating itself with its environment by making it very easy to use external tools.

To get started, just download the binary from its releases page and run

al --config

Then, every time you want to check for updates, run

al

And that’s it!

What’s wrong with kak-bundle?

Nothing, really. kak-bundle is a wonderful project, and it even has more features than Almoxarife. So, if you are happy with it, you should keep using it.

I’ve built Almoxarife mainly for my personal usage. It started as a simple hacky lua script some time before kak-bundle was made public. I did it because the venerable plug.kak was slowing down my Kakoune’s startup time and I found it annoying. Then, when kak-bundle first debuted, I was already using my script and get used to its workflow. Eventually, I started rewriting it in a language with better multi-threading support.

Why only now?

Well, the truth is that I didn’t want to compete with kak-bundle. I’m not a big fan of having competing technologies fighting for space in a very small community like ours.

But recently some friends started using Kakoune, so I decided to release Almoxarife publicly so that it’s easier to distribute new releases for them.

And, given that Almoxarife and kak-bundle use different approaches, having both may be useful to bring new ideas to the community.

@alexherbo2 's plug.kak was a source of inspiration for Almoxarife, so maybe innovation is a good reason for having different tools for the same thing.

How it works

Different from other editors, you don’t really need to use Kakoune API to manage your plugins, since you can rely on its bult-in autoload functionality. That’s something I think other editors don’t have.

Almoxarife works on top of that functionality, creating symlinks as needed, cloning
new plugins, installing plugins’ specific configuration, pulling for updates and deleting unused code.

Some highlights

Here are some of its most prominent features:

I hope you find it interesting.

3 Likes

Interesting - a spiritual successor to cork.kak.

I’m curious how you handle dependent plugins whilst using autoload. Kakoune uses find to find the .kak files in autoload, and sources them in the order in which find returns them. This doesn’t give you control over the order in which they’re loaded, which is why kak-bundle doesn’t use it (that and allowing the user to specify their own loading logic). If you’ve found a way around that limitation, I’d love to hear about it.

This looks great for workflows where you’re updating your plugins as part of a systemwide upgrade, which kak-bundle is much less suited for (it works, but it’s clunky).

I was not aware of cork.kak until I took a look at your benchmarks to see how Almoxarife would perform comparatively. And indeed there are some similarities between it and Almoxarife.

Regarding benchmarks, initially I was quite naively supposing Almoxarife would have faster startup times due to the fact that it’s not a plugin itself and doesn’t define any command. But kak-bundle and Almoxarife have both comparable startup times. Good work! I think they both approached the minimum feasible startup time for Kakoune with a bunch of plugins.

Mine is still faster to install and update, since kak-bundle artificially limits the number of process to only 4 (thought that’s configurable). Since the operating system is very good at handling a large number of processes/threads, and they are all IO-bounded, I don’t see why kak-bundle puts an upper bound on it, unless you reached a corner case I couldn’t anticipate.

It does two complementary things.

First, it doesn’t clone the repos inside the autoload dir, but inside $XDG_DATA_HOME/almoxarife. Then, for each plugin that’s not disabled, a symlink is created in autoload. By not symlinking a disabled plugin and all of its children, we take care of half of the problem.

The second thing it does is requiring children modules only when a parent module is loaded. That takes care of the second half: loading order. But this second part works well only when the plugin is wrapped inside a module, and is a noop otherwise.

This is best seen with an example. So, suppose we have the following configuration file:

luar:
  location: https://github.com/gustavo-hms/luar
  config: set-option global luar_interpreter luajit

  objetiva:
    location: https://github.com/gustavo-hms/objetiva
    config: |
      map global object x '<a-;>objetiva-line<ret>'
      map global object m '<a-;>objetiva-matching<ret>'
      map global object h '<a-;>objetiva-case<ret>'
      map global normal h ': objetiva-case-move<ret>'
      map global normal H ': objetiva-case-expand<ret>'
      map global normal <a-h> ': objetiva-case-move-previous<ret>'
      map global normal <a-H> ': objetiva-case-expand-previous<ret>'

  mru-files:
    location: https://gitlab.com/kstr0k/mru-files.kak
    config: set-option global mru_files_max 100
    disabled: true

    peneira:
      location: https://github.com/gustavo-hms/peneira

      peneira-filters:
        location: https://codeberg.org/mbauhardt/peneira-filters
        config: |
          map global normal <c-p> ': peneira-filters-mode<ret>'

That is: we have a dependency tree rooted at luar. Since peneira has an optional dependency on mru-files, we mark the former as a child of the latter. And peneira-filters is set as a child of peneira.

Almoxarife will then generate an almoxarife.kak file with the following content:

try %[
    # We require the `luar` module inside of a `try`, since we have no way to know
    # whether an arbitrary plugin is wrapped inside of a module.
    require-module luar
] catch %[
    # If luar doesn't provide a module, we define a fake one and require it just
    # to be able to fire the `ModuleLoaded` hook.
    provide-module luar ''
    require-module luar
]

# Once luar is loaded, write its configuration as defined in the yaml file.
set-option global luar_interpreter luajit

# Use the `ModuleLoaded` hook to load children on demand.
hook -once global ModuleLoaded luar %[
    try %[
        # The objetiva module is loaded after luar.
        require-module objetiva

        # No `catch` is needed since objetiva has no children.
    ]

    map global object x '<a-;>objetiva-line<ret>' -docstring line
    map global object m '<a-;>objetiva-matching<ret>' -docstring matching
    map global object h '<a-;>objetiva-case<ret>' -docstring case
    map global normal h ': objetiva-case-move<ret>'
    map global normal H ': objetiva-case-expand<ret>'
    map global normal <a-h> ': objetiva-case-move-previous<ret>'
    map global normal <a-H> ': objetiva-case-expand-previous<ret>'
]

No reference to mru-files and its children since it is disabled in the yaml file.

As I said, that works pretty well when the plugins are wrapped inside their own module (as my plugins do). Otherwise, Almoxarife can’t ensure a proper loading order.

1 Like

Oh, modules. Yeah, that would work, wouldn’t it (assuming of course that the dependent plugin uses modules, like you said).

I don’t plan on using this, personally, as I’m quite happy with kak-bundle (functionality wise - code wise it needs a refactor). However, it seems that these two serve slightly different use cases, so I don’t think we’re stepping on each other’s toes too much.

At the very least our situation is miles better than Neovim’s dozen or so plugin managers… oof…

To be honest, I don’t remember why it’s 4 by default either. A default of say 16 would make sense I think. I do want the user to have a say in how many processes are run, in case they are doing something network intensive at the time, but usually there are no issues, you are right. kak-bundle is designed around the concept of giving the user as much power as possible over how plugins are handled, and this seemed to fit that design goal.

right. Nowadays one might do something like fifo -scroll al instead of having a dedicated integration