gustavo-hms / luar

Script Kakoune using Lua
GNU Lesser General Public License v3.0
45 stars 3 forks source link

do kak module methods return values? #20

Closed eko234 closed 2 years ago

eko234 commented 2 years ago

Hi, I'm trying out luar, and I wanted to know if the methods provided by the kak module actually return values or if they might take higher order functions...

I'm particularly interested in using stuff like on-key and being able to stay in a lua block to define some commands, I'd guess it doesn't, but also wanted to know your perspective on the subject.

gustavo-hms commented 2 years ago

Hi, @eko234! Sorry for taking so long to reply. It took me a time to elaborate on your question.

First of all: happy new year!

Regarding your question, the kak module is minimalist by design: it's conceived to replicate Kakoune's command API and nothing else. That way, if you know how to call commands, you know kak module's API. It's the same idea behind Kakoune scripting itself. That means that the functions provided by the kak module return nothing, since Kakoune commands are statements, not expressions.

The only additional thing functions in the kak module do is automatically converting their arguments from Lua's primitive types (like numbers and booleans) to Kakoune's equivalents.

That said, I must say that, in my experience writing scripts using luar, I've found the mechanisms provided by the kak module flexible enough. Let me present you some tricks I've been using.

Dynamic code generation

Since the kak module gives you access to all Kakoune commands, you can, in particular, call define-command. That alone gives you extreme flexibility to generate code based, for instance, on external data, if you need to do so.

define-command register-my-plugin-commands %{
    lua %{
        if os.getenv("TERM") == "xterm-kitty" then
            kak.define_command("show-context", [[
                kitty-terminal-tab ...
            ]])
        else
            kak.define_command("show-context", [[
                new ...
            ]])
        end
    }
}

Call custom commands within lua

Personally, I tend to not use the kind of trick shown above, because usually there are better alternatives. One of them is factoring out your code into different commands and calling them as needed:

define-command -hidden show-context-on-new-tab %{
    kitty-terminal-tab ...
}

define-command -hidden show-context-on-new-window %{
    new ...
}

define-command show-context %{
    lua %{
        if os.getenv("TERM") == "xterm-kitty" then
            kak.show_context_on_new_tab()
        else
            kak.show_context_on_new_window()
        end
    }
}

The true power comes when you realise lua is just a regular Kakoune command, and you can mix and match lua code and Kakoune code. That leads to a better code organisation.

Higher order commands

As I said, luar automatically converts Lua primitive types to Kakoune's types, making it possible to pass numbers, strings and booleans as arguments to Kakoune commands. But you can't pass higher order types, like tables and functions. So, there aren't such things as higher order functions in the kak module.

On the other hand, Kakoune does have what we could call higher order commands: commands that receive a string as argument and interpret it as a code block, in a way as ergonomic as a Ruby or SmallTalk code block. All of that is possible because Kakoune offers us the excelent evaluate-commands command. And you can take advantage of it to implement many interesting patterns.

Take for instance the peneira command I've written for the plugin of the same name: it accepts three arguments, the second of which is supposed to be a shell command it executes to give back a list of candidates; and the third one is a kakscript block it executes when the user selects one of those candidates. Inside that kakscript block, the expansion %arg{1} refers to the selected candidate. As you can see, it's a command that receives as argument a code block.

Peneira as a source of examples

As a last suggestion, I'd recomend you to take a look at the peneira plugin's code. It's a good use case for luar, since there I use many interesting tricks. Here I list some of them:

Command flags parsing

I use a lua block to parse flags for a wrapping command just to call another wrapped hidden command with the arguments in the right order.

Dinamic code generation

I needed to do some black magic string manipulation to generate kakscript code dinamically in order to make shell expansions to work in the second argument of the peneira command.

Organize lua code inside modules

I defined a separe lua module I then require inside the kakscript code, thus avoiding having a single big and messy lua block and, additionally, allowing me to reuse the same code in different places.

Mix lua and kakscript code

I mix and match Lua code and Kakscript code with no fear whenever I see a need for it.

All in all, I don't feel myself limited by the simplicity of the kak module's API, quite on the contrary: since it fits so well inside Kakoune's scripting model, it feels almost like native code, not as a poor man's FFI.

I hope it can help. If you need extra clarifications, please ask.

eko234 commented 2 years ago

Happy new year for your too and thanks for your very complete answer!

gustavo-hms commented 2 years ago

We are currently mixing languages here, but that's OK hahaha!

I just want to point out that, if performance is a concern, my advice is to simply try to make a prototype using idiomatic Kakoune and Lua codes with luar and, only if you experience performance issues, resort to do everything in Lua using some dark magic.

In my experience writing peneira (the most performance-sensitive project I wrote using luar), I didn't experience performance problems. The lua interpreter is not only very fast to execute code (possibly it's the fastest interpreter I know), but it also has very fast startup times. So, even if I have to call luar multiple times (and, by that, spawn a new lua interpreter each time), that wasn't responsible for any slow down in my program.