Pykak: Script Kakoune with Python. Over 10x lower latency than using %sh{}

When creating Kakoune plugins, I frequently felt guilty starting an entire process using %sh{} to accomplish a task as simple as multiplying two numbers. This is what inspired me to create pykak, a plugin that allows plugin authors to script Kakoune with Python. The implementation relies on IPC instead of %sh{} (besides the initial call to start the pykak server).

Here’s an example that sorts selections:

def sort-sels %{ python %{
    sels = sorted(valq('selections'))
    keval('reg dquote %s; exec R' % quote(sels))
}}

And here’s an example plugin: GitHub - tomKPZ/counted.kak: Alternative key counts for Kakoune

Please give it a try and let me know what you think. I’d love to get some feedback!

12 Likes

This is pretty amazing, congrats! I am also learning a lot by inspecting the way you implemented things.

One question: Can some your custom fifo logic be replaced by $kak_command_fifo and $kak_response_fifo introduced recently? I can’t say I understand your implementation well enough to say myself but I was curious to see no mention.

1 Like

Hey, you made kakoune-smooth-scroll! I used your socket code in pykak, thanks for the code!

Command/response fifos are only available during %sh evaluations and get cleaned up when %sh ends. Since the python command shouldn’t use %sh, we create our own fifos when the pykak server is started.

1 Like

Impressive. I love the use of (two!) fifos to keep communicating with kakoune synchronously and that arbitrary kakoune data can be queried inside it. I have tried to make something like this so many times and have never been satisfied. I was so inspired by your work that I had to make my own spin on it. I reimplemented it as a library that can be imported in python. This way kakoune commands can be defined using decorated python functions, like this:

import libpykak as k
@k.cmd
def sort_sels(): 
    '''Sort the selection contents.'''
    sels = sorted(k.valq('selections'))
    # Put the selections into the default paste register,
    # then execute `R` to replace the selections.
    k.keval('reg dquote %s; exec R' % k.quote(sels))

When k.cmd is run the connection is initialized using the environment variable kak_session. It can be explicitly initialized by first running k.KakConnection.init("my-session").

The python function can be reimplemented using this:

import libpykak as k 
@k.cmd
def python(*args):
    args = list(args)
    code = args.pop()
    exec(code, globals(), {'args': args})

I have added the other details of exposing the same api and caching the python code objects in my repo:

6 Likes

That looks really cool, I especially like the use of a decorator to define kak functions from python! With that change, I’d be able to write counted.kak in pure python.

Have you considered upstreaming your changes?

I wish I understood how it works. I’m trying to wrap my head around it, but no luck so far. Why there are two FIFOs for example. All the Kakoune script magic does not help :sweat_smile:

Beside me being dummy: this is a great plugin. Seems to be the best way to avoid escaping hell when writing complex plugins. Performance is a cherry on top. I should experiment with it more.