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
orkak.get
) it take0.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?