From 60cf7896d2251c3d6dcb79261b7e9acae0be4357 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 2 Oct 2023 10:53:25 +0100 Subject: [PATCH] feat(diffopt): add support for whitespace flags in diffopt Resolves #696 --- doc/gitsigns.txt | 13 +++- lua/gitsigns/config.lua | 129 +++++++++++++++++++++++++++----------- lua/gitsigns/diff.lua | 4 +- lua/gitsigns/diff_ext.lua | 8 +-- lua/gitsigns/diff_int.lua | 56 +++++++++++------ 5 files changed, 148 insertions(+), 62 deletions(-) diff --git a/doc/gitsigns.txt b/doc/gitsigns.txt index d481a0c0..bb5936dd 100644 --- a/doc/gitsigns.txt +++ b/doc/gitsigns.txt @@ -626,7 +626,8 @@ show_deleted *gitsigns-config-show_deleted* diff_opts *gitsigns-config-diff_opts* Type: `table[extended]`, Default: derived from 'diffopt' - Diff options. + Diff options. If the default value is used, then changes to 'diffopt' are + automatically applied. Fields: ~ • algorithm: string @@ -646,6 +647,16 @@ diff_opts *gitsigns-config-diff_opts* • linematch: integer Enable second-stage diff on hunks to align lines. Requires `internal=true`. + • ignore_blank_lines: boolean + Ignore changes where lines are blank. + • ignore_whitespace_change: boolean + Ignore changes in amount of white space. + It should ignore adding trailing white space, + but not leading white space. + • ignore_whitespace: boolean + Ignore all white space changes. + • ignore_whitespace_change_at_eol: boolean + Ignore white space changes at end of line. base *gitsigns-config-base* Type: `string`, Default: index diff --git a/lua/gitsigns/config.lua b/lua/gitsigns/config.lua index ba03e911..09628c9b 100644 --- a/lua/gitsigns/config.lua +++ b/lua/gitsigns/config.lua @@ -1,17 +1,22 @@ ---- @class Gitsigns.SchemaElem +--- @class (exact) Gitsigns.SchemaElem --- @field type string|string[] +--- @field refresh? fun(cb: fun()) Function to refresh the config value --- @field deep_extend? boolean --- @field default any --- @field deprecated? boolean|{new_field:string,message:string,hard:boolean} --- @field default_help? string --- @field description string ---- @class Gitsigns.DiffOpts +--- @class (exact) Gitsigns.DiffOpts --- @field algorithm string --- @field internal boolean --- @field indent_heuristic boolean --- @field vertical boolean ---- @field linematch integer +--- @field linematch? integer +--- @field ignore_whitespace_change? true +--- @field ignore_whitespace? true +--- @field ignore_whitespace_change_at_eol? true +--- @field ignore_blank_lines? true --- @class Gitsigns.SignConfig --- @field show_count boolean @@ -99,15 +104,59 @@ local function resolve_default(v) end end +--- @return Gitsigns.DiffOpts +local function parse_diffopt() + --- @type Gitsigns.DiffOpts + local r = { + algorithm = 'myers', + internal = false, + indent_heuristic = false, + vertical = true, + } + + local optmap = { + ['indent-heuristic'] = 'indent_heuristic', + internal = 'internal', + iwhite = 'ignore_whitespace_change', + iblank = 'ignore_blank_lines', + iwhiteeol = 'ignore_whitespace_change_at_eol', + iwhiteall = 'ignore_whitespace', + } + + local diffopt = vim.opt.diffopt:get() --[[@as string[] ]] + for _, o in ipairs(diffopt) do + if optmap[o] then + r[optmap[o]] = true + elseif o == 'horizontal' then + r.vertical = false + elseif vim.startswith(o, 'algorithm:') then + r.algorithm = string.sub(o, ('algorithm:'):len() + 1) + elseif vim.startswith(o, 'linematch:') then + r.linematch = tonumber(string.sub(o, ('linematch:'):len() + 1)) + end + end + + return r +end + --- @type Gitsigns.Config M.config = setmetatable({}, { __index = function(t, k) if rawget(t, k) == nil then local field = M.schema[k] - if field then - rawset(t, k, resolve_default(field)) + if not field then + return + end + + rawset(t, k, resolve_default(field)) + + if field.refresh then + field.refresh(function() + rawset(t, k, resolve_default(field)) + end) end end + return rawget(t, k) end, }) @@ -377,35 +426,18 @@ M.schema = { diff_opts = { type = 'table', deep_extend = true, - default = function() - local r = { - algorithm = 'myers', - internal = false, - indent_heuristic = false, - vertical = true, - linematch = nil, - } - local diffopt = vim.opt.diffopt:get() --[[@as string[] ]] - for _, o in ipairs(diffopt) do - if o == 'indent-heuristic' then - r.indent_heuristic = true - elseif o == 'internal' then - if vim.diff then - r.internal = true - end - elseif o == 'horizontal' then - r.vertical = false - elseif vim.startswith(o, 'algorithm:') then - r.algorithm = string.sub(o, ('algorithm:'):len() + 1) - elseif vim.startswith(o, 'linematch:') then - r.linematch = tonumber(string.sub(o, ('linematch:'):len() + 1)) - end - end - return r + refresh = function(callback) + vim.api.nvim_create_autocmd('OptionSet', { + group = vim.api.nvim_create_augroup('gitsigns.config.diff_opts', {}), + pattern = 'diffopt', + callback = callback, + }) end, + default = parse_diffopt, default_help = "derived from 'diffopt'", description = [[ - Diff options. + Diff options. If the default value is used, then changes to 'diffopt' are + automatically applied. Fields: ~ • algorithm: string @@ -425,6 +457,16 @@ M.schema = { • linematch: integer Enable second-stage diff on hunks to align lines. Requires `internal=true`. + • ignore_blank_lines: boolean + Ignore changes where lines are blank. + • ignore_whitespace_change: boolean + Ignore changes in amount of white space. + It should ignore adding trailing white space, + but not leading white space. + • ignore_whitespace: boolean + Ignore all white space changes. + • ignore_whitespace_change_at_eol: boolean + Ignore white space changes at end of line. ]], }, @@ -847,6 +889,20 @@ local function handle_deprecated(cfg) end end +--- @param k string +--- @param v Gitsigns.SchemaElem +--- @param user_val any +local function build_field(k, v, user_val) + local config = M.config --[[@as table]] + + if v.deep_extend then + local d = resolve_default(v) + config[k] = vim.tbl_deep_extend('force', d, user_val) + else + config[k] = user_val + end +end + --- @param user_config Gitsigns.Config|nil function M.build(user_config) user_config = user_config or {} @@ -855,14 +911,13 @@ function M.build(user_config) validate_config(user_config) - local config = M.config --[[@as table]] for k, v in pairs(M.schema) do if user_config[k] ~= nil then - if v.deep_extend then - local d = resolve_default(v) - config[k] = vim.tbl_deep_extend('force', d, user_config[k]) - else - config[k] = user_config[k] + build_field(k, v, user_config[k]) + if v.refresh then + v.refresh(function() + build_field(k, v, user_config[k]) + end) end end end diff --git a/lua/gitsigns/diff.lua b/lua/gitsigns/diff.lua index d4f3c207..eb823de2 100644 --- a/lua/gitsigns/diff.lua +++ b/lua/gitsigns/diff.lua @@ -1,6 +1,6 @@ local config = require('gitsigns.config').config ---- @alias Gitsigns.Difffn fun(fa: string[], fb: string[], algorithm?: string, indent_heuristic?: boolean, linematch?: integer): Gitsigns.Hunk.Hunk[] +--- @alias Gitsigns.Difffn fun(fa: string[], fb: string[], linematch?: integer): Gitsigns.Hunk.Hunk[] --- @param a string[] --- @param b string[] @@ -28,5 +28,5 @@ return function(a, b, linematch) linematch0 = diff_opts.linematch end - return f(a, b, diff_opts.algorithm, diff_opts.indent_heuristic, linematch0) + return f(a, b, linematch0) end diff --git a/lua/gitsigns/diff_ext.lua b/lua/gitsigns/diff_ext.lua index e7dff623..794a0683 100644 --- a/lua/gitsigns/diff_ext.lua +++ b/lua/gitsigns/diff_ext.lua @@ -1,3 +1,4 @@ +local config = require('gitsigns.config').config local git_diff = require('gitsigns.git').diff local gs_hunks = require('gitsigns.hunks') @@ -24,10 +25,8 @@ end --- @async --- @param text_cmp string[] --- @param text_buf string[] ---- @param diff_algo string ---- @param indent_heuristic? boolean --- @return Gitsigns.Hunk.Hunk[] -function M.run_diff(text_cmp, text_buf, diff_algo, indent_heuristic) +function M.run_diff(text_cmp, text_buf) local results = {} --- @type Gitsigns.Hunk.Hunk[] -- tmpname must not be called in a callback @@ -57,7 +56,8 @@ function M.run_diff(text_cmp, text_buf, diff_algo, indent_heuristic) -- We can safely ignore the warning, we turn it off by passing the '-c -- "core.safecrlf=false"' argument to git-diff. - local out = git_diff(file_cmp, file_buf, indent_heuristic, diff_algo) + local opts = config.diff_opts + local out = git_diff(file_cmp, file_buf, opts.indent_heuristic, opts.algorithm) for _, line in ipairs(out) do if vim.startswith(line, '@@') then diff --git a/lua/gitsigns/diff_int.lua b/lua/gitsigns/diff_int.lua index 2aae8622..96c391ff 100644 --- a/lua/gitsigns/diff_int.lua +++ b/lua/gitsigns/diff_int.lua @@ -7,14 +7,19 @@ local M = {} --- @alias Gitsigns.Region {[1]:integer, [2]:string, [3]:integer, [4]:integer} --- @alias Gitsigns.RawHunk {[1]:integer, [2]:integer, [3]:integer, [4]:integer} ---- @alias Gitsigns.RawDifffn fun(a: string, b: string, algorithm?: string, indent_heuristic?: boolean, linematch?: integer): Gitsigns.RawHunk[] +--- @alias Gitsigns.RawDifffn fun(a: string, b: string, linematch?: integer): Gitsigns.RawHunk[] --- @type Gitsigns.RawDifffn -local run_diff_xdl = function(a, b, algorithm, indent_heuristic, linematch) +local run_diff_xdl = function(a, b, linematch) + local opts = config.diff_opts return vim.diff(a, b, { result_type = 'indices', - algorithm = algorithm, - indent_heuristic = indent_heuristic, + algorithm = opts.algorithm, + indent_heuristic = opts.indent_heuristic, + ignore_whitespace = opts.ignore_whitespace, + ignore_whitespace_change = opts.ignore_whitespace_change, + ignore_whitespace_change_at_eol = opts.ignore_whitespace_change_at_eol, + ignore_blank_lines = opts.ignore_blank_lines, linematch = linematch, }) --[[@as Gitsigns.RawHunk[] ]] end @@ -23,26 +28,43 @@ end local run_diff_xdl_async = async.wrap( --- @param a string --- @param b string - --- @param algorithm? string - --- @param indent_heuristic? boolean --- @param linematch? integer --- @param callback fun(hunks: Gitsigns.RawHunk[]) - function(a, b, algorithm, indent_heuristic, linematch, callback) + function(a, b, linematch, callback) + local opts = config.diff_opts + local function toflag(f, pos) + return f and bit.lshift(1, pos) or 0 + end + + local flags = toflag(opts.indent_heuristic, 0) + + toflag(opts.ignore_whitespace, 1) + + toflag(opts.ignore_whitespace_change, 2) + + toflag(opts.ignore_whitespace_change_at_eol, 3) + + toflag(opts.ignore_blank_lines, 4) + vim.loop .new_work( --- @param a0 string --- @param b0 string - --- @param algorithm0 string - --- @param indent_heuristic0 integer + --- @param algorithm string + --- @param flags0 integer --- @param linematch0 integer --- @return string - function(a0, b0, algorithm0, indent_heuristic0, linematch0) + function(a0, b0, algorithm, flags0, linematch0) + local function flagval(pos) + return bit.band(flags0, bit.lshift(1, pos)) ~= 0 + end + --- @diagnostic disable-next-line:return-type-mismatch return vim.mpack.encode(vim.diff(a0, b0, { result_type = 'indices', - algorithm = algorithm0, - indent_heuristic = indent_heuristic0, + algorithm = algorithm, linematch = linematch0, + indent_heuristic = flagval(0), + ignore_whitespace = flagval(1), + ignore_whitespace_change = flagval(2), + ignore_whitespace_change_at_eol = flagval(3), + ignore_blank_lines = flagval(4), })) end, --- @param r string @@ -50,18 +72,16 @@ local run_diff_xdl_async = async.wrap( callback(vim.mpack.decode(r) --[[@as Gitsigns.RawHunk[] ]]) end ) - :queue(a, b, algorithm, indent_heuristic, linematch) + :queue(a, b, opts.algorithm, flags, linematch) end, - 6 + 4 ) --- @param fa string[] --- @param fb string[] ---- @param diff_algo? string ---- @param indent_heuristic? boolean --- @param linematch? integer --- @return Gitsigns.Hunk.Hunk[] -function M.run_diff(fa, fb, diff_algo, indent_heuristic, linematch) +function M.run_diff(fa, fb, linematch) local run_diff0 --- @type Gitsigns.RawDifffn if config._threaded_diff and vim.is_thread then run_diff0 = run_diff_xdl_async @@ -72,7 +92,7 @@ function M.run_diff(fa, fb, diff_algo, indent_heuristic, linematch) local a = table.concat(fa, '\n') local b = table.concat(fb, '\n') - local results = run_diff0(a, b, diff_algo, indent_heuristic, linematch) + local results = run_diff0(a, b, linematch) local hunks = {} --- @type Gitsigns.Hunk.Hunk[] for _, r in ipairs(results) do