DEV Community

jamestthompson3
jamestthompson3

Posted on • Originally published at teukka.tech on

(Nvim) Lua for Javascripters: Spawning Processes

Goals

This should be a quick reference guide for those familiar with NodeJS on how to execute the same async tasks in Lua using luv. This is aimed towards use cases inside Neovim, but is not limited to those cases.

Task

We want to spawn a child task to convert a markdown document into HTML using pandoc. This could be used as part of a publishing flow for a blog, for writing notes, or for implementing a markdown previewer.

// in Node we could do it like this:
const {spawn} = require('child_process');
const [sourceFile, destinationFile] = process.argv.slice(2)

const convert = spawn('pandoc', [sourceFile, '--from', 'gfm', '--to', 'html5', '-o', destinationFile, '-s', '--highlight-style', 'tango'])

convert.stderr.on('data', (data) => {
  console.error(data);
});

convert.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});
Enter fullscreen mode Exit fullscreen mode

In Lua, the code looks very similar, but a bit more verbose:

-- The same as before, but this time we want to generate the file names 
-- based on the file we are currently editing instead of passing them as commandline args

function convert()
  -- cut off the `.md` part of the file
  local destinationFile = vim.fn.expand('%:t:r')
  -- the name of the file you're editing
  local sourceFile = api.nvim_buf_get_name(0)
  spawn('pandoc', {
    args = {sourceFile, '--from', 'gfm', '--to', 'html5', '-o', string.format('%s.html', destinationFile), '-s', '--highlight-style', 'tango'},
  }, 
  {stdout = function()end, stderr = function(data) print(data) end},
  function(code) -- we want to call this function when the process is done
    print('child process exited with code ' .. string.format('%d', code))
  end)
end

function spawn(cmd, opts, input, onexit)
  local handle, pid
  -- open an new pipe for stdout
  local stdout = vim.loop.new_pipe(false)
  -- open an new pipe for stderr
  local stderr = vim.loop.new_pipe(false)
  handle, pid = vim.loop.spawn(cmd, vim.tbl_extend("force", opts, {stdio = {stdout; stderr;}}), 
  function(code, signal)
    -- call the exit callback with the code and signal
    onexit(code, signal)
    -- stop reading data to stdout
    vim.loop.read_stop(stdout)
    -- stop reading data to stderr
    vim.loop.read_stop(stderr)
    -- safely shutdown child process
    safe_close(handle)
    -- safely shutdown stdout pipe
    safe_close(stdout)
    -- safely shutdown stderr pipe
    safe_close(stderr)
  end)
  -- read child process output to stdout
  vim.loop.read_start(stdout, input.stdout)
  -- read child process output to stderr
  vim.loop.read_start(stderr, input.stderr)
end

function safe_close(handle)
  if not vim.loop.is_closing(handle) then
    vim.loop.close(handle)
  end
end
Enter fullscreen mode Exit fullscreen mode

One of the major differences is that in Lua you are responsible for cleaning up both the process handle and any pipes you have open to receive data from that handle. It then becomes useful to create a more generalized spawn function to handle all of these things under the hood, allowing you to just call spawn in a similar manner to the NodeJS API.

Top comments (2)

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️ • Edited

One of the major differences is that in Lua you are responsible for cleaning up both the process handle and any pipes you have open to receive data from that handle.

That's not entirely true: This is not related to Lua itself, but to the nvim API. Lua itself doesn't really have a concept of pipes or processes or anything like that.

There's also no real reason to actually do all of this in Lua: you can just use RPC protocol that nvim comes with to do all of the logic in javascript in a separate process.

It's also simpler to just write this kind of stuff in vimscript, most of the time.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Minor nitpick:

stderr = function(data) print(data) end
Enter fullscreen mode Exit fullscreen mode

this could just be

stderr = print
Enter fullscreen mode Exit fullscreen mode

:D