gustavo-hms/luar

is it possible to access val or opt inside lua script ?

sucrecacao opened this issue · 13 comments

Can I do something like:

kak.val('selection')
kak.opt('my_option')

Hi, @sucrecacao !

Unfortunately, no. To access Kakoune's state, you must pass the expansions as arguments to the lua command:

lua %val{selection} %opt{my_option} %{
    local selection = arg[1]
    local my_option = arg[2]
}

Or, equivalently,

lua %val{selection} %opt{my_option} %{
    local selection, my_option = args()
}

That means that, if you want to get the results of some command you just run from a lua block, you must use two consecutive blocks:

lua %{
    kak.execute_keys "x"
}
lua %val{selection} %{
    local selection = arg[1]
}

Or (since we didn't execute any logic in the first lua block)

execute-keys x
lua %val{selection} %{
    local selection = arg[1]
}

This is a limitation of the Kakoune API. The following would print the same result twice:

evaluate-commands %sh{
    echo "echo -debug $kak_selection"
    echo "execute-keys x"
    echo "echo -debug $kak_selection"
}

That is, both $kak_selection get expanded before the execution of the shell script.

To print the whole line selected by execute-keys x you must do

evaluate-commands %sh{
    echo "echo -debug $kak_selection"
    echo "execute-keys x"
}
evaluate-commands %sh{
    echo "echo -debug $kak_selection"
}

much like in the lua version.

Thanks you that was very enlightening .
I have used part of your comment to the wiki if that's alright : https://github.com/mawww/kakoune/wiki/Shell-scripting#passing-options-and-values-to-an-expansion

Could it be possible to send command directly to kakoune with kak -p ? Do you think it would be good practice ?

I'm thinking of stuff like:

lua %val{session} %val{client} %{
  require('Kak')
  kak = Kak:new(arg[1], arg[2])
  kak.execute_keys('x') -- run `echo 'execute-keys -client $1 "x"' | kak -p $2` 

}

I have used part of your comment to the wiki

Nice! Well done!

Could it be possible to send command directly to kakoune with kak -p ?

Yes, it is. Actually I considered doing something along these lines when I was writing luar.

Do you think it would be good practice?

Well... it depends on some different factors.

First of all, I didn't implemented luar that way because I want it to be simple. This plugin is minimalist, both in its API (it has just one command) and in its implementation, and I did it on purpose. It's meant to be used as a building block for other plugins and, as such, it must be as stable an bug free as possible. The bigger the code base, the bigger the surface one exposes to bugs. And something that discourages a developer is expending hours hunting a bug just to find in the end of the day that it was in the framework.

That means I probably wouldn't implement such a feature in luar. But, on the other hand, it could be implemented by an external lua module if you want to. It would be a nice feature having a collection of lua modules available to the Kakoune community implementing all sorts of useful tricks. Anyone interested in using them could simply require them inside a lua command:

lua %{
    local plugin1 = require "plugin1"
    local plugin2 = require "plugin2"
    ...
}

Feel free to implement something like that if you want. I can give you some help =)

But you must be aware that there's another problem. Kakoune offers no command to inspect its state, just expansions. Commands are not functions, they return no value. If you need to get some value from Kakoune, there's no way but using expansions. And with kak -p you can only send commands to Kakoune, that is: you can't get values back. But, to send commands from lua to Kakoune we don't need kak -p: luar already does it in a very simple (and to my taste elegant) way.

If you really want a way to get values back from within a lua %{} block, probably you would need some sort of FIFO, like connect.kak does. It's feasible, but not that straightforward.

First of all, I didn't implemented luar that way because I want it to be simple.

Good call, I was suprise how few loc you used for this plugin.

But in some particular case ( in my case, I'm trying to support symetric delimiter in occivink/kakoune-expand plugin ) it would be usefull and more clean to do everything inside lua:

Kak.execute_keys('')
a = Kak.get_selection()
table.insert(sels, a)
-- complex logic 
Kak.execute_keys('..')
b = Kak.get_selection()
table.instert(sels, b)
-- complex logic 

Since it's hard to have array-like behavior with shell, and it avoid popping a lua expanssion every 2 line in my kak script.

My module would use script similar to :send and :get from connect.kak, so the whole pipline would be:

luar -> my Kak module -> shell script (:get, :send) -> kakoune (kak -p)

I agree it might be messy if a bug appear in the pipeline, even more since kak -p doesn't return error message when called with invalid input ( mawww/kakoune#2292 ), but this is the price to pay when using unix-orthodox tools right ?

Let give it a try and see how it works out.

I can give you some help =)

That would be great, I never used lua before. Also I'm strugguling finding a name for the module, maybe kak_interactive or kak_connect ?

Ok I have done the module. It work but it has performance issue: for ~20 kakoune call ( kak.send or kak.get) it take 0.3 second. Not great.

It's connect.kak's :get script that slow down the process, as it has to write and read on a file.

Sorry for the late reply!

I don't know if you still want to explore these ideas, but let me make some commentaries anyway =)

I'm strugguling finding a name for the module, maybe kak_interactive or kak_connect

Lua modules by convention use single words with all lower case letters.

it would be usefull and more clean to do everything inside lua:

Kak.execute_keys('')
a = Kak.get_selection()
table.insert(sels, a)
-- complex logic 
Kak.execute_keys('..')
b = Kak.get_selection()
table.instert(sels, b)
-- complex logic 

Also, take into account that luar already provides a buit in module called kak you can use to send commands to Kakoune. The missing part is only inspecting its state. So I think you don't need to reimplement the kak module. You can focus on the extracting values part.

That said, your module could be called perhaps introspector or kakstate or kakget, I don't know. In any case, the require function used to load a Lua module actually returns the module, and then we store it in a variable. That means we can name it whatever we want in our code independently of the name the module's author uses:

local state = require "kakstate"
local selections = state.val.selections
-- ...

Regarding instrospection, I think it would be a good idea to try to adhere as much as possible to the Kakoune API used for expansions. So, instead of having a function named mymodule.get_selections, I would prefer mymodule.val.selections (and mymodule.opt.my_option, mymodule.reg.slash...). It would make life much easier for users of your module, since they wouldn't need to learn yet another API. It's the path I followed in luar: to use it, you just need to know the plain Kakoune API and nothing else.

Ok I have done the module. It work but it has performance issue: for ~20 kakoune call ( kak.send or kak.get) it take 0.3 second. Not great.

It's connect.kak's :get script that slow down the process, as it has to write and read on a file.

I don't know if it can be fixed, but it would be easier to say something if I could inspect the code...

Also, take into account that luar already provides a buit in module called kak you can use to send commands to Kakoune.

Yes but if I understand well they will be all stacked together and called once the luar command ended ?
So, using the API as you define in your previous comment:

execute-keys '%' -- select the whole buffer
luar {
 kak.execute_keys "x"  -- this is not executed in the kak session yet
 s = kak.val.selection() -- this is not the line selected with `x`, but the selection that existed before the luar call: the whole buffer
print s -- print the whole buffer, instead of the line
} -- now 'x' is executed
echo %val{selection}  --the current line

This is my understanding of how it would work.

If it's the case, maybe it should be two separate plugin: one who work like shell expression and another one that work interactively. But mixing them together might be confusing

It is quite a hard problem. One good thing of Luar is to avoid back-and-forth between Kakoune and the shell to get states, so it is fast.

It is quite a hard problem

What problem ?Making it work in reasonable time ?

Yep. Each time we want to execute a command and get the resulting states, we have to get out of Lua and pay a shell call, as the lua command invokes sh.

This is my understanding of how it would work.

If it's the case, maybe it should be two separate plugins

If your understanding is correct (and probably it is), then I agree it makes more sense to make another plugin.

But your thoughts made me realise that I don't really know what Kakoune does in such a case. I mean, with expansions it's easy to reason about. If I see something like in our previous example:

evaluate-commands %sh{
    echo "echo -debug $kak_selection"
    echo "execute-keys x"
    echo "echo -debug $kak_selection"
}

I see clearly that Kakoune simply gets the string inside the %sh{} and run a find-and-replace of these $kak_selection before executing the code the string contains.

It's less clear to me, on the other hand, what Kakoune does when, inside the code (being it %sh{} or lua %{}), there's a logic to communicate with Kakoune itself. Does Kakoune accumulate all the lines being printed to stdout before running them? Or does Kakoune reads the data from the pipe as a stream, asynchronously, and executes the lines on demmand?

I think it can be closed now, right?