Use nvim's internal diff via ffi!

WIP

Enabled with config._use_internal_diff
This commit is contained in:
Lewis Russell 2021-03-07 23:36:58 +00:00
parent e659f776d0
commit 7abe534b74
12 changed files with 462 additions and 28 deletions

View File

@ -24,6 +24,8 @@ local gs_hunks = require("gitsigns/hunks")
local create_patch = gs_hunks.create_patch
local process_hunks = gs_hunks.process_hunks
local diff = require('gitsigns.diff')
local gsd = require("gitsigns/debug")
local dprint = gsd.dprint
@ -119,11 +121,16 @@ local update = async(function(bufnr)
end
local stage = bcache.has_conflicts and 1 or 0
await(git.get_staged, bcache.toplevel, bcache.relpath, stage, bcache.staged)
await_main()
local buftext = api.nvim_buf_get_lines(bufnr, 0, -1, false)
bcache.hunks = await(git.run_diff, bcache.staged, buftext, config.diff_algorithm)
if config._use_internal_diff then
local staged_text = await(git.get_staged_text, bcache.toplevel, bcache.relpath, stage)
bcache.hunks = diff.run_diff(staged_text, buftext, config.diff_algorithm)
else
await(git.get_staged, bcache.toplevel, bcache.relpath, stage, bcache.staged)
bcache.hunks = await(git.run_diff, bcache.staged, buftext, config.diff_algorithm)
end
local status = gs_hunks.get_summary(bcache.hunks)
status.head = bcache.abbrev_head

View File

@ -179,6 +179,18 @@ local schema = {
]],
},
_use_internal_diff = {
type = 'boolean',
default = false,
description = [[
Experimental
Use Neovim's built in xdiff library for running diffs.
This uses LuaJIT's FFI interface.
]],
},
debug_mode = {
type = 'boolean',
default = false,

156
lua/gitsigns/diff.lua Normal file
View File

@ -0,0 +1,156 @@
local create_hunk = require("gitsigns/hunks").create_hunk
local ffi = require("ffi")
ffi.cdef([[
typedef struct s_mmbuffer { const char *ptr; long size; } mmbuffer_t;
typedef struct s_xpparam {
unsigned long flags;
// See Documentation/diff-options.txt.
char **anchors;
size_t anchors_nr;
} xpparam_t;
typedef long (__stdcall *find_func_t)(
const char *line,
long line_len,
char *buffer,
long buffer_size,
void *priv
);
typedef int (__stdcall *xdl_emit_hunk_consume_func_t)(
long start_a, long count_a, long start_b, long count_b,
void *cb_data
);
typedef struct s_xdemitconf {
long ctxlen;
long interhunkctxlen;
unsigned long flags;
find_func_t find_func;
void *find_func_priv;
xdl_emit_hunk_consume_func_t hunk_func;
} xdemitconf_t;
typedef struct s_xdemitcb {
void *priv;
int (__stdcall *outf)(void *, mmbuffer_t *, int);
} xdemitcb_t;
int xdl_diff(
mmbuffer_t *mf1,
mmbuffer_t *mf2,
xpparam_t const *xpp,
xdemitconf_t const *xecfg,
xdemitcb_t *ecb
);
]])
local MMBuffer = {}
local function mmbuffer(lines)
local text = table.concat(lines, '\n') .. '\n'
return ffi.new('mmbuffer_t', text, #text)
end
local XPParam = {}
local function xpparam(diff_algo)
local daflag = 0
if diff_algo == 'minimal' then daflag = 1
elseif diff_algo == 'patience' then daflag = math.floor(2 ^ 14)
elseif diff_algo == 'histogram' then daflag = math.floor(2 ^ 15)
end
return ffi.new('xpparam_t', daflag)
end
local Long = {}
local XDEmitConf = {}
local M = {}
function M.run_diff(fa, fb, diff_algo)
local results = {}
local hunk_func = ffi.cast('xdl_emit_hunk_consume_func_t', function(
start_a, count_a,
start_b, count_b)
table.insert(results, create_hunk(
tonumber(start_a) + 1, tonumber(count_a),
tonumber(start_b) + 1, tonumber(count_b)))
return 0
end)
local emitconf = ffi.new('xdemitconf_t')
emitconf.hunk_func = hunk_func
local res = ffi.C.xdl_diff(
mmbuffer(fa),
mmbuffer(fb),
xpparam(diff_algo),
emitconf,
ffi.new('xdemitcb_t'))
assert(res, 'DIFF bad result')
hunk_func:free()
for _, hunk in ipairs(results) do
hunk.head = ('@@ -%d,%d +%d,%d @@'):format(
hunk.removed.start, hunk.removed.count,
hunk.added.start, hunk.added.count)
if hunk.removed.count > 0 then
for i = hunk.removed.start, hunk.removed.start + hunk.removed.count - 1 do
table.insert(hunk.lines, '-' .. (fa[i] or ''))
end
end
if hunk.added.count > 0 then
for i = hunk.added.start, hunk.added.start + hunk.added.count - 1 do
table.insert(hunk.lines, '+' .. (fb[i] or ''))
end
end
end
return results
end
return M

View File

@ -73,6 +73,27 @@ function M.get_staged(toplevel, relpath, stage, output)
end
end
function M.get_staged_text(toplevel, relpath, stage)
return function(callback)
local result = {}
run_job({
command = 'git',
args = {
'--no-pager',
'show',
':' .. tostring(stage) .. ':' .. relpath,
},
cwd = toplevel,
on_stdout = function(_, line)
table.insert(result, line)
end,
on_exit = function()
callback(result)
end,
})
end
end
function M.run_blame(file, toplevel, lines, lnum)
return function(callback)
local results = {}

View File

@ -1,21 +1,12 @@
local M = {}
function M.parse_diff_line(line)
local diffkey = vim.trim(vim.split(line, '@@', true)[2])
local pre, now = unpack(vim.tbl_map(function(s)
return vim.split(string.sub(s, 2), ',')
end, vim.split(diffkey, ' ')))
local removed = { start = tonumber(pre[1]), count = tonumber(pre[2]) or 1 }
local added = { start = tonumber(now[1]), count = tonumber(now[2]) or 1 }
function M.create_hunk(start_a, count_a, start_b, count_b)
local removed = { start = start_a, count = count_a }
local added = { start = start_b, count = count_b }
local hunk = {
start = added.start,
head = line,
lines = {},
removed = removed,
added = added,
@ -34,6 +25,25 @@ function M.parse_diff_line(line)
hunk.dend = added.start + math.min(added.count, removed.count) - 1
hunk.type = "change"
end
return hunk
end
function M.parse_diff_line(line)
local diffkey = vim.trim(vim.split(line, '@@', true)[2])
local pre, now = unpack(vim.tbl_map(function(s)
return vim.split(string.sub(s, 2), ',')
end, vim.split(diffkey, ' ')))
local hunk = M.create_hunk(
tonumber(pre[1]), tonumber(pre[2]) or 1,
tonumber(now[1]), tonumber(now[2]) or 1)
hunk.head = line
return hunk
end

21
teal/ffi.d.tl Normal file
View File

@ -0,0 +1,21 @@
local record Cdefs
xdl_diff: (function(...:any): number)
end
-- C callback
local record CCB
free: function(CCB)
set: function(CCB, function)
end
local record ffi
cdef: function(string)
new: function(string, ...:any)
string: function(any, number): string
gc: function(any, any): any
C: Cdefs
cast: function(string, function): CCB
end
return ffi

View File

@ -24,6 +24,8 @@ local gs_hunks = require("gitsigns/hunks")
local create_patch = gs_hunks.create_patch
local process_hunks = gs_hunks.process_hunks
local diff = require('gitsigns.diff')
local gsd = require("gitsigns/debug")
local dprint = gsd.dprint
@ -119,11 +121,16 @@ local update = async(function(bufnr: number)
end
local stage = bcache.has_conflicts and 1 or 0
await(git.get_staged, bcache.toplevel, bcache.relpath, stage, bcache.staged)
await_main()
local buftext = api.nvim_buf_get_lines(bufnr, 0, -1, false)
bcache.hunks = await(git.run_diff, bcache.staged, buftext, config.diff_algorithm) as {Hunk}
if config._use_internal_diff then
local staged_text = await(git.get_staged_text, bcache.toplevel, bcache.relpath, stage) as {string}
bcache.hunks = diff.run_diff(staged_text, buftext, config.diff_algorithm)
else
await(git.get_staged, bcache.toplevel, bcache.relpath, stage, bcache.staged)
bcache.hunks = await(git.run_diff, bcache.staged, buftext, config.diff_algorithm) as {Hunk}
end
local status: StatusObj = gs_hunks.get_summary(bcache.hunks)
status.head = bcache.abbrev_head

View File

@ -179,6 +179,18 @@ local schema: {string:SchemaElem} = {
]]
},
_use_internal_diff = {
type = 'boolean',
default = false,
description = [[
Experimental
Use Neovim's built in xdiff library for running diffs.
This uses LuaJIT's FFI interface.
]]
},
debug_mode = {
type = 'boolean',
default = false,

156
teal/gitsigns/diff.tl Normal file
View File

@ -0,0 +1,156 @@
local create_hunk = require("gitsigns/hunks").create_hunk
local ffi = require("ffi")
ffi.cdef[[
typedef struct s_mmbuffer { const char *ptr; long size; } mmbuffer_t;
typedef struct s_xpparam {
unsigned long flags;
// See Documentation/diff-options.txt.
char **anchors;
size_t anchors_nr;
} xpparam_t;
typedef long (__stdcall *find_func_t)(
const char *line,
long line_len,
char *buffer,
long buffer_size,
void *priv
);
typedef int (__stdcall *xdl_emit_hunk_consume_func_t)(
long start_a, long count_a, long start_b, long count_b,
void *cb_data
);
typedef struct s_xdemitconf {
long ctxlen;
long interhunkctxlen;
unsigned long flags;
find_func_t find_func;
void *find_func_priv;
xdl_emit_hunk_consume_func_t hunk_func;
} xdemitconf_t;
typedef struct s_xdemitcb {
void *priv;
int (__stdcall *outf)(void *, mmbuffer_t *, int);
} xdemitcb_t;
int xdl_diff(
mmbuffer_t *mf1,
mmbuffer_t *mf2,
xpparam_t const *xpp,
xdemitconf_t const *xecfg,
xdemitcb_t *ecb
);
]]
local record MMBuffer
userdata
ptr: number
size: number
end
local function mmbuffer(lines: {string}): MMBuffer
local text = table.concat(lines, '\n')..'\n'
return ffi.new('mmbuffer_t', text, #text)
end
local record XPParam
userdata
flags: number -- unsigned long flags;
-- char **anchors;
-- size_t anchors_nr;
end
local function xpparam(diff_algo: string): XPParam
local daflag = 0 -- myers
if diff_algo == 'minimal' then daflag = 1
elseif diff_algo == 'patience' then daflag = math.floor(2^14)
elseif diff_algo == 'histogram' then daflag = math.floor(2^15)
end
return ffi.new('xpparam_t', daflag)
end
local record Long
userdata
end
local record XDEmitConf
userdata
hunk_func: function(Long, Long, Long, Long, any): number
end
-- local DIFF_FILLER = 0x001 -- display filler lines
-- local DIFF_IBLANK = 0x002 -- ignore empty lines
-- local DIFF_ICASE = 0x004 -- ignore case
-- local DIFF_IWHITE = 0x008 -- ignore change in white space
-- local DIFF_IWHITEALL = 0x010 -- ignore all white space changes
-- local DIFF_IWHITEEOL = 0x020 -- ignore change in white space at EOL
-- local DIFF_HORIZONTAL = 0x040 -- horizontal splits
-- local DIFF_VERTICAL = 0x080 -- vertical splits
-- local DIFF_HIDDEN_OFF = 0x100 -- diffoff when hidden
-- local DIFF_INTERNAL = 0x200 -- use internal xdiff algorithm
-- local DIFF_CLOSE_OFF = 0x400 -- diffoff when closing window
-- local DIFF_FOLLOWWRAP = 0x800 -- follow the wrap option
local M = {}
function M.run_diff(fa: {string}, fb: {string}, diff_algo: string): {Hunk}
local results: {Hunk} = {}
local hunk_func = ffi.cast('xdl_emit_hunk_consume_func_t', function(
start_a: Long, count_a: Long,
start_b: Long, count_b: Long
): number
table.insert(results, create_hunk(
tonumber(start_a)+1, tonumber(count_a),
tonumber(start_b)+1, tonumber(count_b)
))
return 0
end)
local emitconf = ffi.new('xdemitconf_t') as XDEmitConf
emitconf.hunk_func = hunk_func as function(Long, Long, Long, Long): number
local res = ffi.C.xdl_diff(
mmbuffer(fa),
mmbuffer(fb),
xpparam(diff_algo),
emitconf,
ffi.new('xdemitcb_t')
)
assert(res, 'DIFF bad result')
hunk_func:free()
for _, hunk in ipairs(results) do
hunk.head = ('@@ -%d,%d +%d,%d @@'):format(
hunk.removed.start, hunk.removed.count,
hunk.added.start , hunk.added.count
)
if hunk.removed.count > 0 then
for i = hunk.removed.start, hunk.removed.start+hunk.removed.count-1 do
table.insert(hunk.lines, '-'..(fa[i] or ''))
end
end
if hunk.added.count > 0 then
for i = hunk.added.start, hunk.added.start+hunk.added.count-1 do
table.insert(hunk.lines, '+'..(fb[i] or ''))
end
end
end
return results
end
return M

View File

@ -73,6 +73,27 @@ function M.get_staged(toplevel: string, relpath: string, stage: number, output:
end
end
function M.get_staged_text(toplevel: string, relpath: string, stage: number): cb_function
return function(callback: function({string}))
local result = {}
run_job {
command = 'git',
args = {
'--no-pager',
'show',
':'..tostring(stage)..':'..relpath,
},
cwd = toplevel,
on_stdout = function(_, line: string)
table.insert(result, line)
end,
on_exit = function()
callback(result)
end
}
end
end
function M.run_blame(file: string, toplevel: string, lines: {string}, lnum: number): cb_function
return function(callback: function({string:string}))
local results: {string} = {}

View File

@ -1,21 +1,12 @@
local M = {}
function M.parse_diff_line(line: string): Hunk
local diffkey = vim.trim(vim.split(line, '@@', true)[2])
-- diffKey: "-xx,n +yy"
-- pre: {xx, n}, now: {yy}
local pre, now = unpack(vim.tbl_map(function(s: string): {string}
return vim.split(string.sub(s, 2), ',')
end, vim.split(diffkey, ' ')) as {{string}})
local removed = { start = tonumber(pre[1]), count = tonumber(pre[2]) or 1 }
local added = { start = tonumber(now[1]), count = tonumber(now[2]) or 1 }
function M.create_hunk(start_a: number, count_a: number, start_b: number, count_b: number): Hunk
local removed = { start = start_a, count = count_a }
local added = { start = start_b, count = count_b }
local hunk: Hunk = {
start = added.start,
head = line,
lines = {},
removed = removed,
added = added
@ -34,6 +25,25 @@ function M.parse_diff_line(line: string): Hunk
hunk.dend = added.start + math.min(added.count, removed.count) - 1
hunk.type = "change"
end
return hunk
end
function M.parse_diff_line(line: string): Hunk
local diffkey = vim.trim(vim.split(line, '@@', true)[2])
-- diffKey: "-xx,n +yy"
-- pre: {xx, n}, now: {yy}
local pre, now = unpack(vim.tbl_map(function(s: string): {string}
return vim.split(string.sub(s, 2), ',')
end, vim.split(diffkey, ' ')) as {{string}})
local hunk = M.create_hunk(
tonumber(pre[1]), tonumber(pre[2]) or 1,
tonumber(now[1]), tonumber(now[2]) or 1
)
hunk.head = line
return hunk
end

View File

@ -55,6 +55,7 @@ global record Config
max_file_length: number
update_debounce: number
status_formatter: function(StatusObj): string
_use_internal_diff: boolean
end
global enum SignType