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