Refactor: async jobs and diff component

- Use neovims job api instead of uv.spawn (Probably a bit less
  performent but should be more stable)
- Fix async jobs leaking pipes
- Fix wrong diff counts .Used to occur because job might not be dead
  after closing it . And that still alive job may send data over stdout
  corrupting the cache.
- lots of variable renames in diff.
- utils/async.lua has been replaced by utils/job.lua
This commit is contained in:
shadmansaleh 2021-08-08 14:47:20 +06:00
parent 66b0736fd4
commit cb5c4c031e
3 changed files with 62 additions and 76 deletions

View File

@ -1,18 +1,18 @@
-- Copyright (c) 2020-2021 shadmansaleh
-- MIT license, see LICENSE for more details.
local async = require 'lualine.utils.async'
local utils = require 'lualine.utils.utils'
local highlight = require 'lualine.highlight'
local Job = require'lualine.utils.job'
local Diff = require('lualine.component'):new()
-- Vars
-- variable to store git diff stats
Diff.git_diff = nil
-- accumulates async output to process in the end
Diff.diff_data = ''
-- variable to store git_diff getter async function
Diff.get_git_diff = nil
-- accumulates output from diff process
Diff.diff_output_cache = {}
-- variable to store git_diff job
Diff.diff_job = nil
-- default colors
Diff.default_colors = {
added = '#f0e130',
@ -65,7 +65,7 @@ Diff.new = function(self, options, child)
if type(new_instance.options.source) ~= 'function' then
-- setup internal source
vim.cmd [[
autocmd lualine BufEnter * lua require'lualine.components.diff'.update_git_diff_getter()
autocmd lualine BufEnter * lua require'lualine.components.diff'.update_diff_args()
autocmd lualine BufWritePost * lua require'lualine.components.diff'.update_git_diff()
]]
end
@ -117,7 +117,7 @@ end
-- }
-- error_code = { added = -1, modified = -1, removed = -1 }
function Diff.get_sign_count()
Diff.update_git_diff_getter()
Diff.update_diff_args()
Diff.update_git_diff()
return Diff.git_diff or {added = -1, modified = -1, removed = -1}
end
@ -126,7 +126,7 @@ end
function Diff.process_diff(data)
-- Adapted from https://github.com/wbthomason/nvim-vcs.lua
local added, removed, modified = 0, 0, 0
for line in vim.gsplit(data, '\n') do
for _, line in ipairs(data) do
if string.find(line, [[^@@ ]]) then
local tokens = vim.fn.matchlist(line,
[[^@@ -\v(\d+),?(\d*) \+(\d+),?(\d*)]])
@ -150,47 +150,48 @@ function Diff.process_diff(data)
Diff.git_diff = {added = added, modified = modified, removed = removed}
end
-- Updates the async function for current file
function Diff.update_git_diff_getter()
-- stop older function properly before overwritting it
if Diff.get_git_diff then
Diff.get_git_diff:stop()
end
-- Updates the job args
function Diff.update_diff_args()
-- Donn't show git diff when current buffer doesn't have a filename
if #vim.fn.expand('%') == 0 then
Diff.get_git_diff = nil;
Diff.diff_args = nil;
Diff.git_diff = nil;
return
end
Diff.get_git_diff = async:new({
Diff.diff_args = {
cmd = string.format(
[[git -C %s --no-pager diff --no-color --no-ext-diff -U0 -- %s]],
vim.fn.expand('%:h'), vim.fn.expand('%:t')),
on_stdout = function(_, data)
if data then Diff.diff_data = Diff.diff_data .. data end
if next(data) then
Diff.diff_output_cache = vim.list_extend(Diff.diff_output_cache, data)
end
end,
on_stderr = function(_, data)
if data then
data = table.concat(data, '\n')
if #data > 1 or (#data == 1 and #data[1] > 0) then
Diff.git_diff = nil
Diff.diff_data = ''
Diff.diff_output_cache = {}
end
end,
on_exit = function()
if Diff.diff_data ~= '' then
Diff.process_diff(Diff.diff_data)
if #Diff.diff_output_cache > 0 then
Diff.process_diff(Diff.diff_output_cache)
else
Diff.git_diff = {added = 0, modified = 0, removed = 0}
end
end
})
}
Diff.update_git_diff()
end
-- Update git_diff veriable
function Diff.update_git_diff()
if Diff.get_git_diff then
Diff.diff_data = ''
Diff.get_git_diff:start()
if Diff.diff_args then
Diff.diff_output_cache = {}
if Diff.diff_job then Diff.diff_job:stop() end
Diff.diff_job = Job(Diff.diff_args)
if Diff.diff_job then Diff.diff_job:start() end
end
end

View File

@ -1,51 +0,0 @@
local M = {}
function M:new(args)
args = args or {}
for index, arg in pairs(args) do self[index] = arg end
setmetatable(args, self)
self.__index = self
return args
end
local function close_pipe(pipe)
if pipe ~= nil and not pipe:is_closing() then pipe:close() end
end
function M.close_all()
close_pipe(M.stdin)
close_pipe(M.stderr)
close_pipe(M.stdout)
close_pipe(M.handle)
end
function M.init_options()
local options = {}
local args = vim.fn.split(M.cmd, ' ')
M.stdin = vim.loop.new_pipe(false)
M.stdout = vim.loop.new_pipe(false)
M.stderr = vim.loop.new_pipe(false)
options.command = table.remove(args, 1)
options.args = args
options.stdio = {M.stdin, M.stdout, M.stderr}
if M.cwd then options.cwd = M.cwd end
if M.env then options.env = M.env end
if M.detach then options.detach = M.detach end
return options
end
function M.start()
local options = M.init_options()
M.handle = vim.loop.spawn(options.command, options, vim.schedule_wrap(M.stop))
if M.on_stdout then M.stdout:read_start(vim.schedule_wrap(M.on_stdout)) end
if M.on_stderr then M.stderr:read_start(vim.schedule_wrap(M.on_stderr)) end
end
function M.stop(code, signal)
if M.on_exit then M.on_exit(code, signal) end
if M.on_stdout and M.stdout then M.stdout:read_stop() end
if M.on_stderr and M.stderr then M.stderr:read_stop() end
M.close_all()
end
return M

36
lua/lualine/utils/job.lua Normal file
View File

@ -0,0 +1,36 @@
-- Wrapper arround job api
local Job = setmetatable({
start = function(self)
self.job_id = vim.fn.jobstart(self.args.cmd, self.args)
return self.job_id > 0
end,
stop = function(self)
if self.killed then return end
if self.job_id and self.job_id > 0 then vim.fn.jobstop(self.job_id) end
self.job_id = 0
self.killed = true
end,
-- Wraps callbacks so they are only called when job is alive
-- This avoids race conditions
wrap_cb_alive = function(self, name)
local original_cb = self.args[name]
if original_cb then
self.args[name] = function(...)
if not self.killed then return original_cb(...) end
end
end
end
}, {
__call = function(self, args)
args = vim.deepcopy(args or {})
if type(args.cmd) == 'string' then args.cmd = vim.split(args.cmd, ' ') end
self.__index = self
local job = setmetatable({args = args}, self)
job:wrap_cb_alive('on_stdout')
job:wrap_cb_alive('on_stderr')
job:wrap_cb_alive('on_stdin')
return job
end,
})
return Job