Hans – Public Notes

Asynchronous Black for Python in Neovim

Update: please use this awesome plug-in instead of this post: GitHub - stevearc/conform.nvim: Lightweight yet powerful formatter plugin for Neovim

I searched a bit on the internet for a non-blocking formatter using Black in Neovim, while keeping the jumplist intact. Since I couldn't find it, I coded this with help from a LLM. It's not perfect, but it does the job for me.

function async_black()
  local bufnr = vim.api.nvim_get_current_buf()

  local stdout = uv.new_pipe(false)
  local stderr = uv.new_pipe(false)
  local stdin = uv.new_pipe(false)

  local handle = uv.spawn('black', {
    args = {'--fast', '-q', '-'},
    stdio = {stdin, stdout, stderr},
    cwd = vim.loop.cwd(),
    detached = true,
  }, function(code, signal)
    -- print("black process exited with code: " .. code .. ", signal: " .. signal)
  end)

  local data = {}
  uv.read_start(stdout, function(err, chunk)
    assert(not err, err)
    if chunk then
      table.insert(data, chunk)
    else
      local all_data = table.concat(data)
      vim.schedule(function()
        local save_view = vim.fn.winsaveview()
        local curpos = vim.fn.getcurpos()
        local save_jumplist = vim.fn.getjumplist()[1]
        vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.split(all_data:sub(1, -2), '\n'))
        for i = 1, #save_jumplist do
          if save_jumplist[i].bufnr == bufnr then
            vim.cmd('normal! ' .. save_jumplist[i].lnum .. 'G' .. save_jumplist[i].col .. '|')
          end
        end
        vim.cmd('normal! ' .. curpos[2] .. 'G' .. curpos[3] .. '|')
        vim.fn.winrestview(save_view)
      end)
    end
  end)

  uv.read_start(stderr, function(err, data)
    assert(not err, err)
    if data then
      print("stderr data: " .. data)
    end
  end)

  local content = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
  local all_text = table.concat(content, '\n')
  stdin:write(all_text)
  stdin:shutdown()
end

vim.cmd([[
  augroup async_black
    autocmd!
    autocmd FileType python autocmd BufWritePost <buffer> lua async_black()
  augroup END
]])