mirror of
https://github.com/lewis6991/gitsigns.nvim
synced 2025-02-23 00:07:04 +00:00
Use nvim's internal diff via ffi!
WIP Enabled with config._use_internal_diff
This commit is contained in:
parent
e659f776d0
commit
7abe534b74
@ -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
|
||||
|
@ -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
156
lua/gitsigns/diff.lua
Normal 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
|
@ -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 = {}
|
||||
|
@ -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
21
teal/ffi.d.tl
Normal 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
|
@ -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
|
||||
|
@ -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
156
teal/gitsigns/diff.tl
Normal 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
|
@ -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} = {}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user