A :lua command for Kakoune

Is there an equivalent to io.popen to run a process with the command and arguments?

Something similar to Process.run of Crystal.

You can just pass the full command (including arguments) to the first argument of io.open:

local buffers = io.popen("kcr get %val{buflist}"):read("a")

Passing the full command (including arguments) means to correctly escape the arguments.

I would rather like to not deal with the escaping.

Hmm… I didn’t get it. Could you give me an example?

If you have a file named, say, read me first.txt, and (for some reason) you try to count the lines by feeding it through wc:

io.popen("wc -l read me first.txt")

…then you’ll get a bunch of errors:

wc: read: No such file or directory
wc: me: No such file or directory
wc: first.txt: No such file or directory

To actually read the file, you need to quote the filename:

io.popen("wc -l 'read me first.txt'")

…or escape the spaces:

io.popen("wc -l read\\ me\\ first.txt")

That’s perfectly possible, but it’s kind of a frustrating API, since it makes doing the wrong thing easy and the right thing difficult. I don’t know what facilities Lua has available, but for example Python’s equivalent API lets you pass a list of strings and avoid the quoting issue entirely:

subprocess.Popen(["wc", "-l", "read me first.txt"])

If you get the filename read me first.txt from somewhere, you can stick it straight on the end of the list of command-line arguments, and be sure it won’t be molested or misparsed and wc will receieve it exactly as intended, no extra quoting or escaping required.

1 Like

I see… this is where the lua focus on being a lightweight embedded scripting language pays its price. There are many useful packages you can install to make scripts communicate with the outside world. I like this one: luachild - LuaRocks. Simple and effective. But the built-in tools are quite limited.

One workaround is to define a wrapper:

-- Define execute as a varargs function
function execute(...)
    local args = {}
    -- Iterate over the arguments
    for _, arg in ipairs({...}) do
        -- The %q format quotes strings as needed
        args[#args+1] = string.format("%q", arg)
    end
    return io.popen(table.concat(args, " ")):read("a")
end

Now, you can use it this way:

local lines = execute("wc", "-l", "read me first.txt")

Yes, some thing like that would definitely do the trick — although as a minor nitpick, %q isn’t exactly right: it quotes things according to Lua’s quoting rules, not POSIX shell’s quoting rules. For example, it doesn’t escape $ or backticks, which are important. I’m sure it wouldn’t be difficult to add a custom shell-quoting helper to your Lua plugin, though, just like I’m sure you already have a custom Kakoune-quoting helper.

and these rules are kinda stupid tbh…

You are right :sweat_smile:

It quotes each argument of a command. As far as I can anticipate, it’s enough.

@alexherbo2, going back to your question, perhaps the best advice I can give you is to keep in mind that the lua command is meant to be a complement of the %sh{} expansion, not a replacement of it.

Personally, I tend to use the lua command when I want to run some logic in my scripts (something I find cumbersome to do with shell scripting) and %sh{} when I want to run external tools, except for simple enough cases. Using it in this manner, the lua language is a good fit, for its speed, small footprint and portability. You can even combine both in many cases:

lua %sh{wc -l "read me first.txt"} %{
    local count = arg[1]
    ...
}

Unless you want to get realtime expansions from within a lua block using kcr, like @scr once asked me, in which case we need to find a good solution.

1 Like