diff --git a/lua/lualine/components/diff.lua b/lua/lualine/components/diff.lua index 91bb6b6..bba4adf 100644 --- a/lua/lualine/components/diff.lua +++ b/lua/lualine/components/diff.lua @@ -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 diff --git a/lua/lualine/utils/async.lua b/lua/lualine/utils/async.lua deleted file mode 100644 index 9c89db8..0000000 --- a/lua/lualine/utils/async.lua +++ /dev/null @@ -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 diff --git a/lua/lualine/utils/job.lua b/lua/lualine/utils/job.lua new file mode 100644 index 0000000..675184e --- /dev/null +++ b/lua/lualine/utils/job.lua @@ -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