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:
parent
66b0736fd4
commit
cb5c4c031e
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue