From acdd74bc6cc88afffe2586e2b8ec8e7746d9acc9 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 1 Apr 2022 12:26:19 +0100 Subject: [PATCH] feat(diffthis): various improvements - When running diffthis (or vim-fugitive's Gdiffsplit), hunk operations to the index will now update the diff buffer. - Similar to vim-fugitive's Gdiffsplit, buffers of a file in the git index can now be edited. When the buffer is written, it updates the git index accordingly. Likewise the index buffer can also be reloaded (via 'edit'). - Moved all diffthis code to a separate module. Fixes #501 --- doc/gitsigns.txt | 3 + gitsigns.nvim-scm-1.rockspec | 1 + lua/gitsigns/actions.lua | 100 ++++----------------- lua/gitsigns/async.lua | 2 + lua/gitsigns/cache.lua | 10 +++ lua/gitsigns/debounce.lua | 6 +- lua/gitsigns/diffthis.lua | 162 +++++++++++++++++++++++++++++++++++ lua/gitsigns/git.lua | 15 +++- lua/gitsigns/manager.lua | 2 +- lua/gitsigns/util.lua | 24 ++++++ teal/gitsigns/actions.tl | 108 +++++------------------ teal/gitsigns/async.tl | 2 + teal/gitsigns/cache.tl | 10 +++ teal/gitsigns/debounce.tl | 6 +- teal/gitsigns/diffthis.tl | 162 +++++++++++++++++++++++++++++++++++ teal/gitsigns/git.tl | 15 +++- teal/gitsigns/manager.tl | 2 +- teal/gitsigns/util.tl | 24 ++++++ types/types.d.tl | 23 ++++- 19 files changed, 499 insertions(+), 178 deletions(-) create mode 100644 lua/gitsigns/diffthis.lua create mode 100644 teal/gitsigns/diffthis.tl diff --git a/doc/gitsigns.txt b/doc/gitsigns.txt index 5346567..7cfbdbc 100644 --- a/doc/gitsigns.txt +++ b/doc/gitsigns.txt @@ -182,6 +182,9 @@ diffthis({base}) *gitsigns.diffthis()* Perform a |vimdiff| on the given file with {base} if it is given, or with the currently set base (index by default). + If {base} is the index, then the opened diff buffer is editable + and any written changes will update the index accordingly. + Examples: > " Diff against the index :Gitsigns diffthis diff --git a/gitsigns.nvim-scm-1.rockspec b/gitsigns.nvim-scm-1.rockspec index 07afca4..fd0f3e5 100644 --- a/gitsigns.nvim-scm-1.rockspec +++ b/gitsigns.nvim-scm-1.rockspec @@ -40,6 +40,7 @@ build = { ['gitsigns.current_line_blame'] = 'lua/gitsigns/current_line_blame.lua', ['gitsigns.debounce'] = 'lua/gitsigns/debounce.lua', ['gitsigns.debug'] = 'lua/gitsigns/debug.lua', + ['gitsigns.diffthis'] = 'lua/gitsigns/diffthis.lua', ['gitsigns.diff_ext'] = 'lua/gitsigns/diff_ext.lua', ['gitsigns.diff_int'] = 'lua/gitsigns/diff_int.lua', ['gitsigns.diff_int.xdl_diff_ffi'] = 'lua/gitsigns/diff_int/xdl_diff_ffi.lua', diff --git a/lua/gitsigns/actions.lua b/lua/gitsigns/actions.lua index e9ecdd1..2f44e26 100644 --- a/lua/gitsigns/actions.lua +++ b/lua/gitsigns/actions.lua @@ -115,6 +115,13 @@ local function get_cursor_hunk(bufnr, hunks) return gs_hunks.find_hunk(lnum, hunks) end +local function update(bufnr) + manager.update(bufnr) + if vim.wo.diff then + require('gitsigns.diffthis').update(bufnr) + end +end + @@ -165,7 +172,7 @@ M.stage_hunk = mk_repeatable(void(function(range) table.insert(bcache.staged_diffs, hunk) bcache.compare_text = nil - manager.update(bufnr) + update(bufnr) end)) @@ -249,7 +256,7 @@ M.undo_stage_hunk = void(function() bcache.git_obj:stage_hunks({ hunk }, true) bcache.compare_text = nil - manager.update(bufnr) + update(bufnr) end) @@ -283,7 +290,7 @@ M.stage_buffer = void(function() end bcache.compare_text = nil - manager.update(bufnr) + update(bufnr) end) @@ -311,7 +318,7 @@ M.reset_buffer_index = void(function() bcache.compare_text = nil scheduler() - manager.update(bufnr) + update(bufnr) end) local function process_nav_opts(opts) @@ -452,22 +459,6 @@ local function noautocmd(f) end -local function strip_cr(xs0) - for i = 1, #xs0 do - if xs0[i]:sub(-1) ~= '\r' then - - return xs0 - end - end - - local xs = vim.deepcopy(xs0) - for i = 1, #xs do - xs[i] = xs[i]:sub(1, -2) - end - return xs -end - - M.preview_hunk = noautocmd(function() @@ -479,7 +470,7 @@ M.preview_hunk = noautocmd(function() local hlines = gs_hunks.patch_lines(hunk) if vim.bo[cbuf].fileformat == 'dos' then - hlines = strip_cr(hlines) + hlines = util.strip_cr(hlines) end local lines = { @@ -677,17 +668,10 @@ M.blame_line = void(function(opts) end end) -local function calc_base(base) - if base and base:sub(1, 1):match('[~\\^]') then - base = 'HEAD' .. base - end - return base -end - local function update_buf_base(buf, bcache, base) bcache.base = base bcache.compare_text = nil - manager.update(buf, bcache) + update(buf) end @@ -724,7 +708,7 @@ end M.change_base = void(function(base, global) - base = calc_base(base) + base = util.calc_base(base) if global then config.base = base @@ -765,61 +749,13 @@ end -M.diffthis = void(function(base) - local bufnr = current_buf() - local bcache = cache[bufnr] - if not bcache then return end - - if api.nvim_win_get_option(0, 'diff') then return end - - local ff = vim.bo[bufnr].fileformat - - local text - local err - local comp_rev = bcache:get_compare_rev(calc_base(base)) - - if base then - text, err = bcache.git_obj:get_show_text(comp_rev) - if ff == 'dos' then - text = strip_cr(text) - end - if err then - print(err) - return - end - scheduler() - else - text = bcache:get_compare_text() - end - - local ft = api.nvim_buf_get_option(bufnr, 'filetype') - - local bufname = string.format( - 'gitsigns://%s/%s', - bcache.git_obj.repo.gitdir, - comp_rev .. ':' .. bcache.git_obj.relpath) - vim.cmd('diffthis') - vim.cmd(table.concat({ - 'keepalt', 'aboveleft', - config.diff_opts.vertical and 'vertical' or '', - 'split', bufname, - }, ' ')) - - local dbuf = current_buf() - - api.nvim_buf_set_option(dbuf, 'modifiable', true) - util.set_lines(dbuf, 0, -1, text) - api.nvim_buf_set_option(dbuf, 'modifiable', false) - - api.nvim_buf_set_option(dbuf, 'filetype', ft) - api.nvim_buf_set_option(dbuf, 'buftype', 'nowrite') - api.nvim_buf_set_option(dbuf, 'bufhidden', 'wipe') - - vim.cmd('diffthis') -end) +M.diffthis = function(base) + local diffthis = require('gitsigns.diffthis') + diffthis.run(base, config.diff_opts.vertical) +end local function hunks_to_qflist(buf_or_filename, hunks, qflist) for i, hunk in ipairs(hunks) do diff --git a/lua/gitsigns/async.lua b/lua/gitsigns/async.lua index 7acd7be..574aca5 100644 --- a/lua/gitsigns/async.lua +++ b/lua/gitsigns/async.lua @@ -5,6 +5,7 @@ local fun1 = {} local fun2 = {} local fun2_2 = {} local fun3 = {} +local fun4 = {} local Async = {} @@ -18,6 +19,7 @@ local Async = {} + local async_thread = { threads = {}, } diff --git a/lua/gitsigns/cache.lua b/lua/gitsigns/cache.lua index 43aab9d..9838afd 100644 --- a/lua/gitsigns/cache.lua +++ b/lua/gitsigns/cache.lua @@ -29,6 +29,7 @@ local M = {CacheEntry = {}, CacheObj = {}, } + local CacheEntry = M.CacheEntry @@ -48,6 +49,15 @@ CacheEntry.get_compare_rev = function(self, base) return string.format(':%d', stage) end +CacheEntry.get_diffthis_bufname = function(self, rev) + rev = rev or self:get_compare_rev() + return string.format( + 'gitsigns://%s/%s', + self.git_obj.repo.gitdir, + rev .. ':' .. self.git_obj.relpath) + +end + CacheEntry.get_compare_text = function(self) if self.compare_text then return self.compare_text diff --git a/lua/gitsigns/debounce.lua b/lua/gitsigns/debounce.lua index 9363433..1da1008 100644 --- a/lua/gitsigns/debounce.lua +++ b/lua/gitsigns/debounce.lua @@ -51,7 +51,7 @@ end -function M.throttle_by_id(fn) +function M.throttle_by_id(fn, schedule) local scheduled = {} local running = {} return function(id, ...) @@ -59,7 +59,9 @@ function M.throttle_by_id(fn) return end - scheduled[id] = true + if not running[id] or schedule then + scheduled[id] = true + end if running[id] then return end diff --git a/lua/gitsigns/diffthis.lua b/lua/gitsigns/diffthis.lua new file mode 100644 index 0000000..8957aa7 --- /dev/null +++ b/lua/gitsigns/diffthis.lua @@ -0,0 +1,162 @@ +local api = vim.api + +local void = require('gitsigns.async').void +local scheduler = require('gitsigns.async').scheduler +local awrap = require('gitsigns.async').wrap + +local gs_cache = require('gitsigns.cache') +local cache = gs_cache.cache +local CacheEntry = gs_cache.CacheEntry + +local nvim = require('gitsigns.nvim') +local util = require('gitsigns.util') +local manager = require('gitsigns.manager') + +local throttle_by_id = require('gitsigns.debounce').throttle_by_id + +local input = awrap(vim.ui.input, 2) + +local M = {} + + + + +local bufread = void(function(bufnr, dbufnr, base, bcache) + local comp_rev = bcache:get_compare_rev(util.calc_base(base)) + local text + if util.calc_base(base) == util.calc_base(bcache.base) then + text = bcache:get_compare_text() + else + local err + text, err = bcache.git_obj:get_show_text(comp_rev) + if err then + print(err) + return + end + if vim.bo[bufnr].fileformat == 'dos' then + text = util.strip_cr(text) + end + scheduler() + end + + local modifiable = vim.bo[dbufnr].modifiable + vim.bo[dbufnr].modifiable = true + util.set_lines(dbufnr, 0, -1, text) + + vim.bo[dbufnr].modifiable = modifiable + vim.bo[dbufnr].modified = false + vim.bo[dbufnr].filetype = vim.bo[bufnr].filetype + vim.bo[dbufnr].bufhidden = 'wipe' + + vim.cmd('diffthis') +end) + +local bufwrite = void(function(bufnr, dbufnr, base, bcache) + local buftext = util.buf_lines(dbufnr) + bcache.git_obj:stage_lines(buftext) + vim.bo[dbufnr].modified = false + + + if util.calc_base(base) == util.calc_base(bcache.base) then + bcache.compare_text = buftext + manager.update(bufnr, bcache) + end +end) + +M.run = void(function(base, vertical) + local bufnr = vim.api.nvim_get_current_buf() + local bcache = cache[bufnr] + if not bcache then + return + end + + if vim.wo.diff then + return + end + + local comp_rev = bcache:get_compare_rev(util.calc_base(base)) + local bufname = bcache:get_diffthis_bufname(comp_rev) + + vim.cmd('diffthis') + + vim.cmd(table.concat({ + 'keepalt', 'aboveleft', + vertical and 'vertical' or '', + 'split', bufname, + }, ' ')) + + local dbuf = vim.api.nvim_get_current_buf() + + bufread(bufnr, dbuf, base, bcache) + + if comp_rev == ':0' then + vim.bo[dbuf].buftype = 'acwrite' + + nvim.autocmd('BufReadCmd', { + group = 'gitsigns', + buffer = dbuf, + callback = function() + bufread(bufnr, dbuf, base, bcache) + end, + }) + + nvim.autocmd('BufWriteCmd', { + group = 'gitsigns', + buffer = dbuf, + callback = function() + bufwrite(bufnr, dbuf, base, bcache) + end, + }) + else + vim.bo[dbuf].buftype = 'nowrite' + vim.bo[dbuf].modifiable = false + end +end) + + +local function should_reload(bufnr) + if not vim.bo[bufnr].modified then + return true + end + local response + while not vim.tbl_contains({ 'O', 'L' }, response) do + response = input({ + prompt = 'Warning: The git index has changed and the buffer was changed as well. [O]K, (L)oad File:', + }) + end + return response == 'L' +end + + +M.update = throttle_by_id(void(function(bufnr) + if not vim.wo.diff then + return + end + + local bcache = cache[bufnr] + + + + local bufname = bcache:get_diffthis_bufname() + + for _, w in ipairs(api.nvim_list_wins()) do + if api.nvim_win_is_valid(w) then + local b = api.nvim_win_get_buf(w) + local bname = api.nvim_buf_get_name(b) + if bname == bufname or vim.startswith(bname, 'fugitive://') then + if should_reload(b) then + api.nvim_buf_call(b, function() + + + + + vim.cmd('doautocmd BufReadCmd') + vim.cmd('diffthis') + end) + end + end + end + end +end)) + +return M diff --git a/lua/gitsigns/git.lua b/lua/gitsigns/git.lua index ecd1f70..52f1569 100644 --- a/lua/gitsigns/git.lua +++ b/lua/gitsigns/git.lua @@ -107,6 +107,7 @@ local M = {BlameInfo = {}, Version = {}, Repo = {}, FileProps = {}, Obj = {}, } + local in_git_dir = function(file) @@ -451,7 +452,7 @@ Obj.ensure_file_in_index = function(self) else - local info = table.concat({ self.mode_bits, self.object_name, self.relpath }, ',') + local info = string.format('%s,%s,%s', self.mode_bits, self.object_name, self.relpath) self:command({ 'update-index', '--add', '--cacheinfo', info }) end @@ -459,6 +460,18 @@ Obj.ensure_file_in_index = function(self) end end +Obj.stage_lines = function(self, lines) + local stdout = self:command({ + 'hash-object', '-w', '--path', self.relpath, '--stdin', + }, { writer = lines }) + + local new_object = stdout[1] + + self:command({ + 'update-index', '--cacheinfo', string.format('%s,%s,%s', self.mode_bits, new_object, self.relpath), + }) +end + Obj.stage_hunks = function(self, hunks, invert) self:ensure_file_in_index() self:command({ diff --git a/lua/gitsigns/manager.lua b/lua/gitsigns/manager.lua index 28a5e02..a1ba03f 100644 --- a/lua/gitsigns/manager.lua +++ b/lua/gitsigns/manager.lua @@ -328,7 +328,7 @@ end -M.update = throttle_by_id(update0) +M.update = throttle_by_id(update0, true) M.setup = function() M.update_debounced = debounce_trailing(config.update_debounce, void(M.update)) diff --git a/lua/gitsigns/util.lua b/lua/gitsigns/util.lua index fc065bc..95d4d20 100644 --- a/lua/gitsigns/util.lua +++ b/lua/gitsigns/util.lua @@ -107,4 +107,28 @@ function M.copy_array(x) return r end + +function M.strip_cr(xs0) + for i = 1, #xs0 do + if xs0[i]:sub(-1) ~= '\r' then + + return xs0 + end + end + + local xs = vim.deepcopy(xs0) + for i = 1, #xs do + xs[i] = xs[i]:sub(1, -2) + end + return xs +end + +function M.calc_base(base) + if base and base:sub(1, 1):match('[~\\^]') then + base = 'HEAD' .. base + end + return base +end + + return M diff --git a/teal/gitsigns/actions.tl b/teal/gitsigns/actions.tl index 77c9523..e94c2a7 100644 --- a/teal/gitsigns/actions.tl +++ b/teal/gitsigns/actions.tl @@ -47,7 +47,7 @@ local record M blame_line : function -- function() change_base : function(base: string, global: boolean) reset_base : function(global: boolean) - diffthis : function -- function(base: string) + diffthis : function(base: string) record QFListOpts use_location_list: boolean @@ -115,6 +115,13 @@ local function get_cursor_hunk(bufnr: integer, hunks: {Hunk}): Hunk, integer return gs_hunks.find_hunk(lnum, hunks) end +local function update(bufnr: integer) + manager.update(bufnr) + if vim.wo.diff then + require('gitsigns.diffthis').update(bufnr) + end +end + --- Stage the hunk at the cursor position, or all lines in the --- given range. If {range} is provided, all lines in the given --- range are staged. This supports partial-hunks, meaning if a @@ -165,7 +172,7 @@ M.stage_hunk = mk_repeatable(void(function(range: {integer, integer}) table.insert(bcache.staged_diffs, hunk) bcache.compare_text = nil -- Invalidate - manager.update(bufnr) + update(bufnr) end)) --- Reset the lines of the hunk at the cursor position, or all @@ -249,7 +256,7 @@ M.undo_stage_hunk = void(function() bcache.git_obj:stage_hunks({hunk}, true) bcache.compare_text = nil -- Invalidate - manager.update(bufnr) + update(bufnr) end) --- Stage all hunks in current buffer. @@ -283,7 +290,7 @@ M.stage_buffer = void(function() end bcache.compare_text = nil -- Invalidate - manager.update(bufnr) + update(bufnr) end) --- Unstage all hunks for current buffer in the index. Note: @@ -311,7 +318,7 @@ M.reset_buffer_index = void(function() bcache.compare_text = nil -- Invalidate scheduler() - manager.update(bufnr) + update(bufnr) end) local function process_nav_opts(opts: NavHunkOpts) @@ -451,22 +458,6 @@ local function noautocmd(f: function()): function() end end --- Strip '\r' from the EOL of each line only if all lines end with '\r' -local function strip_cr(xs0: {string}): {string} - for i = 1, #xs0 do - if xs0[i]:sub(-1) ~= '\r' then - -- don't strip, return early - return xs0 - end - end - -- all lines end with '\r', need to strip - local xs = vim.deepcopy(xs0) - for i = 1, #xs do - xs[i] = xs[i]:sub(1, -2) - end - return xs -end - --- Preview the hunk at the cursor position in a floating --- window. M.preview_hunk = noautocmd(function() @@ -479,7 +470,7 @@ M.preview_hunk = noautocmd(function() local hlines = gs_hunks.patch_lines(hunk) if vim.bo[cbuf].fileformat == 'dos' then - hlines = strip_cr(hlines) + hlines = util.strip_cr(hlines) end local lines = { @@ -677,17 +668,10 @@ M.blame_line = void(function(opts: boolean | BlameOpts) end end) -local function calc_base(base: string): string - if base and base:sub(1, 1):match('[~\\^]') then - base = 'HEAD'..base - end - return base -end - local function update_buf_base(buf: integer, bcache: CacheEntry, base: string) bcache.base = base bcache.compare_text = nil - manager.update(buf, bcache) + update(buf) end --- Change the base revision to diff against. If {base} is not @@ -724,7 +708,7 @@ end --- For a more complete list of ways to specify bases, see --- |gitsigns-revision|. M.change_base = void(function(base: string, global: boolean) - base = calc_base(base) + base = util.calc_base(base) if global then config.base = base @@ -752,6 +736,9 @@ end --- Perform a |vimdiff| on the given file with {base} if it is --- given, or with the currently set base (index by default). --- +--- If {base} is the index, then the opened diff buffer is editable +--- and any written changes will update the index accordingly. +--- --- Examples: > --- " Diff against the index --- :Gitsigns diffthis @@ -765,61 +752,10 @@ end --- --- Attributes: ~ --- {async} -M.diffthis = void(function(base: string) - local bufnr = current_buf() - local bcache = cache[bufnr] - if not bcache then return end - - if api.nvim_win_get_option(0, 'diff') then return end - - local ff = vim.bo[bufnr].fileformat - - local text: {string} - local err: string - local comp_rev = bcache:get_compare_rev(calc_base(base)) - - if base then - text, err = bcache.git_obj:get_show_text(comp_rev) - if ff == 'dos' then - text = strip_cr(text) - end - if err then - print(err) - return - end - scheduler() - else - text = bcache:get_compare_text() - end - - local ft = api.nvim_buf_get_option(bufnr, 'filetype') - - local bufname = string.format( - 'gitsigns://%s/%s', - bcache.git_obj.repo.gitdir, - comp_rev..':'..bcache.git_obj.relpath - ) - - vim.cmd'diffthis' - - vim.cmd(table.concat({ - 'keepalt', 'aboveleft', - config.diff_opts.vertical and 'vertical' or '', - 'split', bufname - }, ' ')) - - local dbuf = current_buf() - - api.nvim_buf_set_option(dbuf, 'modifiable', true) - util.set_lines(dbuf, 0, -1, text) - api.nvim_buf_set_option(dbuf, 'modifiable', false) - - api.nvim_buf_set_option(dbuf, 'filetype', ft) - api.nvim_buf_set_option(dbuf, 'buftype', 'nowrite') - api.nvim_buf_set_option(dbuf, 'bufhidden', 'wipe') - - vim.cmd'diffthis' -end) +M.diffthis = function(base: string) + local diffthis = require('gitsigns.diffthis') + diffthis.run(base, config.diff_opts.vertical) +end local function hunks_to_qflist(buf_or_filename: number|string, hunks: {Hunk}, qflist: {vim.fn.QFItem}) for i, hunk in ipairs(hunks) do diff --git a/teal/gitsigns/async.tl b/teal/gitsigns/async.tl index df3e5b9..71b1a10 100644 --- a/teal/gitsigns/async.tl +++ b/teal/gitsigns/async.tl @@ -5,11 +5,13 @@ local type fun1 = function (A1) local type fun2 = function (A1,A2) local type fun2_2 = function (A1,A2) : R1,R2 local type fun3 = function (A1,A2,A3) +local type fun4 = function (A1,A2,A3,A4) local record Async void: function (fun0 ): fun0 void: function (fun1 ): fun1 void: function(fun2): fun2 + void: function(fun4): fun4 wrap: function(function(A1,A2, function(R1,R2)), integer): fun2_2 wrap: function (function(A1,A2,A3,function()) , integer): fun3 diff --git a/teal/gitsigns/cache.tl b/teal/gitsigns/cache.tl index be5fd9c..83565da 100644 --- a/teal/gitsigns/cache.tl +++ b/teal/gitsigns/cache.tl @@ -18,6 +18,7 @@ local record M get_compare_rev: function(CacheEntry, base: string): string get_compare_text: function(self: CacheEntry): {string} + get_diffthis_bufname: function(self: CacheEntry, rev: string): string new: function(CacheEntry): CacheEntry destroy: function(CacheEntry) end @@ -48,6 +49,15 @@ CacheEntry.get_compare_rev = function(self: CacheEntry, base: string): string return string.format(':%d', stage) end +CacheEntry.get_diffthis_bufname = function(self: CacheEntry, rev: string): string + rev = rev or self:get_compare_rev() + return string.format( + 'gitsigns://%s/%s', + self.git_obj.repo.gitdir, + rev..':'..self.git_obj.relpath + ) +end + CacheEntry.get_compare_text = function(self: CacheEntry): {string} if self.compare_text then return self.compare_text diff --git a/teal/gitsigns/debounce.tl b/teal/gitsigns/debounce.tl index d3273e3..7b734d1 100644 --- a/teal/gitsigns/debounce.tl +++ b/teal/gitsigns/debounce.tl @@ -51,7 +51,7 @@ end -- --@param fn (function) Function to throttle --@returns (function) throttled function. -function M.throttle_by_id(fn: function): function +function M.throttle_by_id(fn: function, schedule: boolean): function local scheduled: {any:boolean} = {} local running: {any:boolean} = {} return function(id: any, ...) @@ -59,7 +59,9 @@ function M.throttle_by_id(fn: function): function -- If fn is already scheduled, then drop return end - scheduled[id] = true + if not running[id] or schedule then + scheduled[id] = true + end if running[id] then return end diff --git a/teal/gitsigns/diffthis.tl b/teal/gitsigns/diffthis.tl new file mode 100644 index 0000000..76c7290 --- /dev/null +++ b/teal/gitsigns/diffthis.tl @@ -0,0 +1,162 @@ +local api = vim.api + +local void = require('gitsigns.async').void +local scheduler = require('gitsigns.async').scheduler +local awrap = require('gitsigns.async').wrap + +local gs_cache = require('gitsigns.cache') +local cache = gs_cache.cache +local CacheEntry = gs_cache.CacheEntry + +local nvim = require('gitsigns.nvim') +local util = require('gitsigns.util') +local manager = require('gitsigns.manager') + +local throttle_by_id = require('gitsigns.debounce').throttle_by_id + +local input = awrap(vim.ui.input, 2) + +local record M + run: function + update: function +end + +local bufread = void(function(bufnr: integer, dbufnr: integer, base: string, bcache: CacheEntry) + local comp_rev = bcache:get_compare_rev(util.calc_base(base)) + local text: {string} + if util.calc_base(base) == util.calc_base(bcache.base) then + text = bcache:get_compare_text() + else + local err: string + text, err = bcache.git_obj:get_show_text(comp_rev) + if err then + print(err) + return + end + if vim.bo[bufnr].fileformat == 'dos' then + text = util.strip_cr(text) + end + scheduler() + end + + local modifiable = vim.bo[dbufnr].modifiable + vim.bo[dbufnr].modifiable = true + util.set_lines(dbufnr, 0, -1, text) + + vim.bo[dbufnr].modifiable = modifiable + vim.bo[dbufnr].modified = false + vim.bo[dbufnr].filetype = vim.bo[bufnr].filetype + vim.bo[dbufnr].bufhidden = 'wipe' + + vim.cmd'diffthis' +end) + +local bufwrite = void(function(bufnr: integer, dbufnr: integer, base: string, bcache: CacheEntry) + local buftext = util.buf_lines(dbufnr) + bcache.git_obj:stage_lines(buftext) + vim.bo[dbufnr].modified = false + -- If diff buffer base matches the bcache base then also update the + -- signs. + if util.calc_base(base) == util.calc_base(bcache.base) then + bcache.compare_text = buftext + manager.update(bufnr, bcache) + end +end) + +M.run = void(function(base: string, vertical: boolean) + local bufnr = vim.api.nvim_get_current_buf() + local bcache = cache[bufnr] + if not bcache then + return + end + + if vim.wo.diff then + return + end + + local comp_rev = bcache:get_compare_rev(util.calc_base(base)) + local bufname = bcache:get_diffthis_bufname(comp_rev) + + vim.cmd'diffthis' + + vim.cmd(table.concat({ + 'keepalt', 'aboveleft', + vertical and 'vertical' or '', + 'split', bufname + }, ' ')) + + local dbuf = vim.api.nvim_get_current_buf() + + bufread(bufnr, dbuf, base, bcache) + + if comp_rev == ':0' then + vim.bo[dbuf].buftype = 'acwrite' + + nvim.autocmd('BufReadCmd', { + group = 'gitsigns', + buffer = dbuf, + callback = function() + bufread(bufnr, dbuf, base, bcache) + end + }) + + nvim.autocmd('BufWriteCmd', { + group = 'gitsigns', + buffer = dbuf, + callback = function() + bufwrite(bufnr, dbuf, base, bcache) + end + }) + else + vim.bo[dbuf].buftype = 'nowrite' + vim.bo[dbuf].modifiable = false + end +end) + + +local function should_reload(bufnr: integer): boolean + if not vim.bo[bufnr].modified then + return true + end + local response: string + while not vim.tbl_contains({'O', 'L'}, response) do + response = input{ + prompt = 'Warning: The git index has changed and the buffer was changed as well. [O]K, (L)oad File:' + } + end + return response == 'L' +end + +-- This function needs to be throttled as there is a call to vim.ui.input +M.update = throttle_by_id(void(function(bufnr: integer) + if not vim.wo.diff then + return + end + + local bcache = cache[bufnr] + + -- Note this will be the bufname for the currently set base + -- which are the only ones we want to update + local bufname = bcache:get_diffthis_bufname() + + for _, w in ipairs(api.nvim_list_wins()) do + if api.nvim_win_is_valid(w) then + local b = api.nvim_win_get_buf(w) + local bname = api.nvim_buf_get_name(b) + if bname == bufname or vim.startswith(bname, 'fugitive://') then + if should_reload(b) then + api.nvim_buf_call(b, function(): nil + + + + + vim.cmd('doautocmd BufReadCmd') + vim.cmd('diffthis') + end) + end + end + end + end +end)) + +return M diff --git a/teal/gitsigns/git.tl b/teal/gitsigns/git.tl index f9c367a..b8e2d9e 100644 --- a/teal/gitsigns/git.tl +++ b/teal/gitsigns/git.tl @@ -103,6 +103,7 @@ local record M get_show_text : function(Obj, string): {string}, string ensure_file_in_index : function(Obj) stage_hunks : function(Obj, {Hunk}, boolean) + stage_lines : function(Obj, {string}) has_moved : function(Obj): string new : function(string): Obj end @@ -451,7 +452,7 @@ Obj.ensure_file_in_index = function(self: Obj) else -- Update the index with the common ancestor (stage 1) which is what bcache -- stores - local info = table.concat({self.mode_bits, self.object_name, self.relpath}, ',') + local info = string.format('%s,%s,%s', self.mode_bits, self.object_name, self.relpath) self:command{'update-index', '--add', '--cacheinfo', info} end @@ -459,6 +460,18 @@ Obj.ensure_file_in_index = function(self: Obj) end end +Obj.stage_lines = function(self: Obj, lines: {string}) + local stdout = self:command({ + 'hash-object', '-w', '--path', self.relpath, '--stdin' + }, { writer = lines }) + + local new_object = stdout[1] + + self:command{ + 'update-index', '--cacheinfo', string.format('%s,%s,%s', self.mode_bits, new_object, self.relpath) + } +end + Obj.stage_hunks = function(self: Obj, hunks: {Hunk}, invert: boolean) self:ensure_file_in_index() self:command({ diff --git a/teal/gitsigns/manager.tl b/teal/gitsigns/manager.tl index 787c014..bf6652c 100644 --- a/teal/gitsigns/manager.tl +++ b/teal/gitsigns/manager.tl @@ -328,7 +328,7 @@ end -- Since updates are asynchronous we need to make sure an update isn't performed -- whilst another one is in progress. If this happens then schedule another -- update after the current one has completed. -M.update = throttle_by_id(update0) as function(integer, CacheEntry) +M.update = throttle_by_id(update0, true) as function(integer, CacheEntry) M.setup = function() M.update_debounced = debounce_trailing(config.update_debounce, void(M.update)) as function(integer) diff --git a/teal/gitsigns/util.tl b/teal/gitsigns/util.tl index 8920d37..1bacf82 100644 --- a/teal/gitsigns/util.tl +++ b/teal/gitsigns/util.tl @@ -107,4 +107,28 @@ function M.copy_array(x: {T}): {T} return r end +-- Strip '\r' from the EOL of each line only if all lines end with '\r' +function M.strip_cr(xs0: {string}): {string} + for i = 1, #xs0 do + if xs0[i]:sub(-1) ~= '\r' then + -- don't strip, return early + return xs0 + end + end + -- all lines end with '\r', need to strip + local xs = vim.deepcopy(xs0) + for i = 1, #xs do + xs[i] = xs[i]:sub(1, -2) + end + return xs +end + +function M.calc_base(base: string): string + if base and base:sub(1, 1):match('[~\\^]') then + base = 'HEAD'..base + end + return base +end + + return M diff --git a/types/types.d.tl b/types/types.d.tl index 0fb45c1..3f937ac 100644 --- a/types/types.d.tl +++ b/types/types.d.tl @@ -137,7 +137,7 @@ local record api nvim_list_runtime_paths: function(): {string} nvim_list_tabpages: function(): {any} nvim_list_uis: function(): any - nvim_list_wins: function(): {number} + nvim_list_wins: function(): {integer} nvim_load_context: function({string:any}): any nvim_open_win: function(number, boolean, {string:any}): integer nvim_out_write: function(string) @@ -178,7 +178,7 @@ local record api nvim_win_call: function(number, (function(): T)): T nvim_win_close: function(number, boolean) nvim_win_del_var: function(number, string) - nvim_win_get_buf: function(number): number + nvim_win_get_buf: function(integer): integer nvim_win_get_config: function(number): {string:any} nvim_win_get_cursor: function(number): {integer} nvim_win_get_height: function(integer): integer @@ -209,6 +209,7 @@ global record vim executable: function(string): integer expand: function(string): string getcwd: function(): string + input: function(string, string): string record QFItem bufnr: integer @@ -290,6 +291,20 @@ global record vim record BufOption {BufOption} fileformat: string + filetype: string + modifiable: boolean + modified: boolean + + enum BufHidden + '' 'hide' 'unload' 'delete' 'wipe' + end + + bufhidden: BufHidden + + enum BufType + '' 'acwrite' 'help' 'nofile' 'nowrite' 'quickfix' 'terminal' 'prompt' + end + buftype: BufType end bo: BufOption @@ -489,6 +504,10 @@ global record vim wait: function(number, function, number, boolean) + record ui + input: function({string:any}, function(string)) + end + record VersionDetails api_compatible: integer api_level: integer